diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index fc0107346..25d4120a4 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [21.5.0](https://github.com/chanzuckerberg/sci-components/compare/@czi-sds/components@21.4.0...@czi-sds/components@21.5.0) (2024-10-09) + +### Features + +- **icons:** add new s and xs sizes for the Book icon ([#873](https://github.com/chanzuckerberg/sci-components/issues/873)) ([a256f8c](https://github.com/chanzuckerberg/sci-components/commit/a256f8cc45d7f895130d9b326bace180f6a717e9)) + +# [21.4.0](https://github.com/chanzuckerberg/sci-components/compare/@czi-sds/components@21.3.0...@czi-sds/components@21.4.0) (2024-10-09) + +### Features + +- **segmentedcontrol:** add disabled state in button definition and controlled/uncontrolled state ([#869](https://github.com/chanzuckerberg/sci-components/issues/869)) ([d59af72](https://github.com/chanzuckerberg/sci-components/commit/d59af72ced81f4e4577aa85b6cd272a343170931)) + # [21.3.0](https://github.com/chanzuckerberg/sci-components/compare/@czi-sds/components@21.2.0...@czi-sds/components@21.3.0) (2024-10-02) ### Bug Fixes diff --git a/packages/components/package.json b/packages/components/package.json index 4ee1927bc..91e30d095 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@czi-sds/components", - "version": "21.3.0", + "version": "21.5.0", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.cjs.d.ts", diff --git a/packages/components/src/common/svgs/IconBookSmall.svg b/packages/components/src/common/svgs/IconBookSmall.svg new file mode 100644 index 000000000..4bc09c191 --- /dev/null +++ b/packages/components/src/common/svgs/IconBookSmall.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/components/src/core/Icon/map.ts b/packages/components/src/core/Icon/map.ts index ab371ca1d..5b2cca653 100644 --- a/packages/components/src/core/Icon/map.ts +++ b/packages/components/src/core/Icon/map.ts @@ -5,6 +5,7 @@ import { ReactComponent as IconBarChartHorizontal3Small } from "../../common/svg import { ReactComponent as IconBarChartVertical3Small } from "../../common/svgs/IconBarChartVertical3Small.svg"; import { ReactComponent as IconBarChartVertical4Small } from "../../common/svgs/IconBarChartVertical4Small.svg"; import { ReactComponent as IconBookLarge } from "../../common/svgs/IconBookLarge.svg"; +import { ReactComponent as IconBookSmall } from "../../common/svgs/IconBookSmall.svg"; import { ReactComponent as IconCheckCircleLarge } from "../../common/svgs/IconCheckCircleLarge.svg"; import { ReactComponent as IconCheckCircleSmall } from "../../common/svgs/IconCheckCircleSmall.svg"; import { ReactComponent as IconCheckSmall } from "../../common/svgs/IconCheckSmall.svg"; @@ -144,7 +145,7 @@ export interface IconNameToSizes { BarChartHorizontal3: "xs" | "s"; BarChartVertical3: "xs" | "s"; BarChartVertical4: "xs" | "s"; - Book: "l" | "xl"; + Book: "xs" | "s" | "l" | "xl"; Check: "xs" | "s"; CheckCircle: "xs" | "s" | "l" | "xl"; ChevronDown: "xs" | "s" | "l" | "xl"; @@ -280,7 +281,7 @@ export const iconMap: Props = { }, Book: { largeIcon: IconBookLarge, - smallIcon: null, + smallIcon: IconBookSmall, }, Check: { largeIcon: null, diff --git a/packages/components/src/core/SegmentedControl/__storybook__/index.stories.tsx b/packages/components/src/core/SegmentedControl/__storybook__/index.stories.tsx index ee43fd0b7..9262bbb36 100644 --- a/packages/components/src/core/SegmentedControl/__storybook__/index.stories.tsx +++ b/packages/components/src/core/SegmentedControl/__storybook__/index.stories.tsx @@ -3,6 +3,7 @@ import { BADGE } from "@geometricpanda/storybook-addon-badges"; import { SegmentedControl } from "./stories/default"; import { SEGMENTED_CONTROL_EXCLUDED_CONTROLS } from "./constants"; import { TestDemo } from "./stories/test"; +import { ControlledSegmentedControlDemo } from "./stories/controlledSegmentedControl"; export default { argTypes: { @@ -24,15 +25,92 @@ export default { export const Default = { args: { buttonDefinition: [ - { icon: "List", tooltipText: "List A", value: "A" }, - { icon: "List", tooltipText: "List B", value: "B" }, - { icon: "List", tooltipText: "List C", value: "C" }, - { icon: "List", tooltipText: "List D", value: "D" }, + { + icon: "List", + tooltipText: "List A", + value: "A", + }, + { + icon: "List", + tooltipText: "List B", + value: "B", + }, + { + icon: "List", + tooltipText: "List C", + value: "C", + }, + { + icon: "List", + tooltipText: "List D", + value: "D", + }, ], }, render: SegmentedControl, }; +// Disabled Buttons + +export const WithDisabledButton = { + args: { + buttonDefinition: [ + { + icon: "LinesHorizontal3", + tooltipText: "List A", + value: "A", + }, + { + disabled: true, + icon: "LinesHorizontal3", + tooltipText: "List B", + value: "B", + }, + { + icon: "LinesHorizontal3", + tooltipText: "List C", + value: "C", + }, + { + icon: "LinesHorizontal3", + tooltipText: "List D", + value: "D", + }, + ], + }, + render: SegmentedControl, +}; + +// Controlled + +export const ControlledSegmentedControl = { + args: { + buttonDefinition: [ + { + icon: "List", + tooltipText: "List A", + value: "A", + }, + { + icon: "List", + tooltipText: "List B", + value: "B", + }, + { + icon: "List", + tooltipText: "List C", + value: "C", + }, + { + icon: "List", + tooltipText: "List D", + value: "D", + }, + ], + }, + render: ControlledSegmentedControlDemo, +}; + // Test export const Test = { diff --git a/packages/components/src/core/SegmentedControl/__storybook__/stories/controlledSegmentedControl.tsx b/packages/components/src/core/SegmentedControl/__storybook__/stories/controlledSegmentedControl.tsx new file mode 100644 index 000000000..c58f92584 --- /dev/null +++ b/packages/components/src/core/SegmentedControl/__storybook__/stories/controlledSegmentedControl.tsx @@ -0,0 +1,20 @@ +import { Args } from "@storybook/react"; +import { useState } from "react"; +import RawSegmentedControl from "src/core/SegmentedControl"; + +export const ControlledSegmentedControlDemo = (props: Args): JSX.Element => { + const { buttonDefinition, ...rest } = props; + + const [value, setValue] = useState("C"); + return ( + { + console.log(newValue); + setValue(newValue); + }} + value={value} + {...rest} + /> + ); +}; diff --git a/packages/components/src/core/SegmentedControl/__storybook__/stories/default.tsx b/packages/components/src/core/SegmentedControl/__storybook__/stories/default.tsx index 676d63086..086a26ce5 100644 --- a/packages/components/src/core/SegmentedControl/__storybook__/stories/default.tsx +++ b/packages/components/src/core/SegmentedControl/__storybook__/stories/default.tsx @@ -4,11 +4,5 @@ import RawSegmentedControl from "src/core/SegmentedControl"; export const SegmentedControl = (props: Args): JSX.Element => { const { buttonDefinition, ...rest } = props; - return ( - - ); + return ; }; diff --git a/packages/components/src/core/SegmentedControl/__tests__/__snapshots__/index.test.tsx.snap b/packages/components/src/core/SegmentedControl/__tests__/__snapshots__/index.test.tsx.snap index 763bdf155..918720e40 100644 --- a/packages/components/src/core/SegmentedControl/__tests__/__snapshots__/index.test.tsx.snap +++ b/packages/components/src/core/SegmentedControl/__tests__/__snapshots__/index.test.tsx.snap @@ -1,15 +1,152 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` ControlledSegmentedControl story renders snapshot 1`] = ` +
+ + + + +
+`; + exports[` Default story renders snapshot 1`] = `
`; + +exports[` WithDisabledButton story renders snapshot 1`] = ` +
+ + + + +
+`; diff --git a/packages/components/src/core/SegmentedControl/index.tsx b/packages/components/src/core/SegmentedControl/index.tsx index 7e4d3511c..392441f1b 100644 --- a/packages/components/src/core/SegmentedControl/index.tsx +++ b/packages/components/src/core/SegmentedControl/index.tsx @@ -3,35 +3,52 @@ import React from "react"; import Icon, { IconNameToSizes } from "src/core/Icon"; import Tooltip from "src/core/Tooltip"; import { StyledSegmentedControl } from "./style"; + // one prop is array of objects: with icon name and tooltip text. They need to make // first item in array first button, etc export interface SingleButtonDefinition { + disabled?: boolean; icon: keyof IconNameToSizes | React.ReactElement; tooltipText?: string; value: string; } -interface SegmentedControlExtraProps extends ToggleButtonGroupProps { +export interface SegmentedControlProps extends ToggleButtonGroupProps { buttonDefinition: SingleButtonDefinition[]; } /** * @see https://mui.com/material-ui/react-toggle-button/ */ -export type SegmentedControlProps = SegmentedControlExtraProps & - ToggleButtonGroupProps; const SegmentedControl = (props: SegmentedControlProps) => { - const { buttonDefinition } = props; - const leftmost = buttonDefinition[0]?.value; - const [active, setActive] = React.useState(leftmost); + const { + buttonDefinition, + value: valueProp, + onChange: onChangeProp, + ...restProps + } = props; + + const initialValue = + buttonDefinition.find((button) => !button.disabled)?.value || null; + + const [active, setActive] = React.useState(initialValue); + + // (masoudmanson): Add Controlled/Uncontrolled Component pattern + const isControlled = valueProp !== undefined; + const value = isControlled ? valueProp : active; const handleActive = ( event: React.MouseEvent, newActive: string | null ) => { if (newActive !== null) { - setActive(newActive); + if (!isControlled) { + setActive(newActive); + } + if (onChangeProp) { + onChangeProp(event, newActive); + } } }; @@ -39,34 +56,49 @@ const SegmentedControl = (props: SegmentedControlProps) => { {buttonDefinition.map((button: SingleButtonDefinition) => { - const { icon, tooltipText, value } = button; + const { + icon, + tooltipText, + value: buttonValue, + disabled = false, + } = button; - const iconItem = () => { - if (icon) { - if (typeof icon !== "string") { - return icon; - } else { - return ; - } - } - }; + const iconItem = icon ? ( + typeof icon !== "string" ? ( + icon + ) : ( + + ) + ) : null; + + const toggleButton = ( + + {iconItem} + + ); - return ( + // (masoudmanson): If the button is disabled, we don't want to show the tooltip. + return disabled ? ( + toggleButton + ) : ( - - {iconItem()} - + {toggleButton} ); })} diff --git a/packages/components/src/core/SegmentedControl/style.ts b/packages/components/src/core/SegmentedControl/style.ts index 61a9720a3..cc9eeae3f 100644 --- a/packages/components/src/core/SegmentedControl/style.ts +++ b/packages/components/src/core/SegmentedControl/style.ts @@ -17,6 +17,10 @@ export const StyledSegmentedControl = styled(ToggleButtonGroup, { const semanticColors = getSemanticColors(props); return ` + .${toggleButtonClasses.root}.${toggleButtonClasses.disabled} { + border-color: ${semanticColors?.base?.border}; + } + .${toggleButtonClasses.root}.${toggleButtonClasses.selected} { background-color: ${semanticColors?.base?.fillOpen}; color: ${semanticColors?.accent?.iconSelected};