diff --git a/packages/components/view-switcher/src/view-switcher-button.tsx b/packages/components/view-switcher/src/view-switcher-button.tsx index 27ded7d70e..e2a0c0ae73 100644 --- a/packages/components/view-switcher/src/view-switcher-button.tsx +++ b/packages/components/view-switcher/src/view-switcher-button.tsx @@ -3,6 +3,7 @@ import { type MouseEvent, type KeyboardEvent, type ReactElement, + FC, } from 'react'; import { css } from '@emotion/react'; import { designTokens } from '@commercetools-uikit/design-system'; @@ -10,22 +11,40 @@ import AccessibleButton from '@commercetools-uikit/accessible-button'; import { warning } from '@commercetools-uikit/utils'; import { getButtonStyles } from './view-switcher.styles'; +/** + * Props for the ViewSwitcherButton component + */ export type TViewSwitcherButtonProps = { + /** The text content to be displayed inside the button */ children?: string; + /** An icon element to be displayed inside the button */ icon?: ReactElement; + /** Indicates if the button is in active state */ isActive?: boolean; + /** Indicates if the button is disabled */ isDisabled?: boolean; + /** Indicates if the button should be rendered in condensed mode */ isCondensed?: boolean; + /** Indicates if this button is the first in a group of buttons */ isFirstButton?: boolean; + /** Indicates if this button is the last in a group of buttons */ isLastButton?: boolean; + /** Accessible label for the button (required when only icon is provided) */ label?: string; + /** Value associated with the button */ value: string; + /** Handler called when the button is clicked */ onClick?: ( - event: MouseEvent | KeyboardEvent + event: + | MouseEvent + | KeyboardEvent + | string ) => void; }; -const ViewSwitcherButton = (props: TViewSwitcherButtonProps) => { +export type TViewSwitcherButtonElement = FC; + +const ViewSwitcherButton: TViewSwitcherButtonElement = (props) => { warning( Boolean(props.children || props.icon), 'uikit/ViewSwitcherButton: You need to provide at least the `children` to render inside the button or an `icon`.' @@ -68,5 +87,5 @@ const ViewSwitcherButton = (props: TViewSwitcherButtonProps) => { ); }; -ViewSwitcherButton.displayName = 'ViewSwitcherButton'; +ViewSwitcherButton.displayName = 'ViewSwitcher.Button'; export default ViewSwitcherButton; diff --git a/packages/components/view-switcher/src/view-switcher.stories.tsx b/packages/components/view-switcher/src/view-switcher.stories.tsx index ad44f890c5..f37b096568 100644 --- a/packages/components/view-switcher/src/view-switcher.stories.tsx +++ b/packages/components/view-switcher/src/view-switcher.stories.tsx @@ -6,8 +6,8 @@ const meta: Meta = { title: 'components/ViewSwitcher', component: ViewSwitcher.Group, subcomponents: { - // @ts-ignore - Button: ViewSwitcher.Button, + // @ts-expect-error + 'ViewSwitcher.Button': ViewSwitcher.Button, }, argTypes: { children: { control: false }, diff --git a/packages/components/view-switcher/src/view-switcher.tsx b/packages/components/view-switcher/src/view-switcher.tsx index 6f08abfd94..8ec8f4c361 100644 --- a/packages/components/view-switcher/src/view-switcher.tsx +++ b/packages/components/view-switcher/src/view-switcher.tsx @@ -3,17 +3,16 @@ import { isValidElement, cloneElement, useState, - type ReactNode, type ReactElement, } from 'react'; import isNil from 'lodash/isNil'; import { css } from '@emotion/react'; -import ViewSwitcherButton from './view-switcher-button'; +import ViewSwitcherButton, { + TViewSwitcherButtonProps, +} from './view-switcher-button'; import { warning } from '@commercetools-uikit/utils'; -type TReactChild = { - type?: { displayName: string }; -} & ReactElement; +type TViewSwitcherButtonElement = ReactElement; export type TViewSwitcherProps = { /** @@ -23,7 +22,7 @@ export type TViewSwitcherProps = { /** * Pass one or more `ViewSwitcher.Button` components. */ - children: ReactNode; + children: TViewSwitcherButtonElement | TViewSwitcherButtonElement[]; /** * Will be triggered whenever a `ViewSwitcher.Button` is selected. Called with the ViewSwitcherButton value. * This function is only required when the component is controlled. @@ -65,20 +64,20 @@ const ViewSwitcher = (props: TViewSwitcherProps) => { ); warning( - (props.children as TReactChild[]).length > 0, + (props.children as TViewSwitcherButtonElement[]).length > 0, 'ViewSwitcher.Group must contain at least one ViewSwitcher.Button' ); const viewSwitcherElements = Children.map(props.children, (child, index) => { if ( child && - isValidElement(child) && - (child as TReactChild).type.displayName === ViewSwitcherButton.displayName + isValidElement(child) && + child.type === ViewSwitcherButton ) { const isButtonActive = (isControlledComponent ? props.selectedValue : selectedButton) === child.props.value; - const clonedChild = cloneElement(child as TReactChild, { + return cloneElement(child, { onClick: () => { if (!isControlledComponent) { setSelectedButton(child.props.value); @@ -92,10 +91,8 @@ const ViewSwitcher = (props: TViewSwitcherProps) => { isCondensed: props.isCondensed, isActive: isButtonActive, isFirstButton: index === 0, - isLastButton: - index === ((props.children as TReactChild[]).length ?? 1) - 1, + isLastButton: index === Children.count(props.children) - 1, }); - return clonedChild; } return child; }); @@ -111,4 +108,6 @@ const ViewSwitcher = (props: TViewSwitcherProps) => { ); }; +ViewSwitcher.displayName = 'ViewSwitcher.Group'; + export default ViewSwitcher;