From d59af72ced81f4e4577aa85b6cd272a343170931 Mon Sep 17 00:00:00 2001 From: Masoud Amjadi Date: Wed, 9 Oct 2024 12:05:26 -0400 Subject: [PATCH] feat(segmentedcontrol): add disabled state in button definition and controlled/uncontrolled state (#869) --- .../__storybook__/index.stories.tsx | 86 +++++- .../stories/controlledSegmentedControl.tsx | 20 ++ .../__storybook__/stories/default.tsx | 8 +- .../__snapshots__/index.test.tsx.snap | 281 +++++++++++++++++- .../src/core/SegmentedControl/index.tsx | 82 +++-- .../src/core/SegmentedControl/style.ts | 4 + 6 files changed, 440 insertions(+), 41 deletions(-) create mode 100644 packages/components/src/core/SegmentedControl/__storybook__/stories/controlledSegmentedControl.tsx 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};