From 6921b2c824ce47ea4f5a3115617ffe42ab5c809c Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Sat, 10 Jan 2026 14:05:22 +0100 Subject: [PATCH] chore: support render function, document it, remove now unused POC context Signed-off-by: Gabriel Dugny --- .../src/content/toggle-button-group.props.ts | 36 ++-- docs-ui/src/content/toggle-button.mdx | 13 ++ docs-ui/src/content/toggle-button.props.ts | 122 +++++++++++- packages/ui/report.api.md | 2 - .../ToggleButton/ToggleButton.stories.tsx | 180 ++++++++++++------ .../components/ToggleButton/ToggleButton.tsx | 24 ++- .../ui/src/components/ToggleButton/types.ts | 3 +- .../ToggleButtonGroup.module.css | 1 + .../ToggleButtonGroup/ToggleButtonGroup.tsx | 37 +--- 9 files changed, 300 insertions(+), 118 deletions(-) diff --git a/docs-ui/src/content/toggle-button-group.props.ts b/docs-ui/src/content/toggle-button-group.props.ts index d1c9c915dc..b8f12941de 100644 --- a/docs-ui/src/content/toggle-button-group.props.ts +++ b/docs-ui/src/content/toggle-button-group.props.ts @@ -30,6 +30,7 @@ export const toggleButtonGroupUsageSnippet = `import { ToggleButtonGroup, Toggle Dogs Cats + Birds `; export const toggleButtonGroupSingleSnippet = ` @@ -45,9 +46,9 @@ export const toggleButtonGroupMultipleSnippet = ``; export const toggleButtonGroupVerticalSnippet = ` - Low - Medium - High + Morning + Afternoon + Evening `; export const toggleButtonGroupDisabledSnippet = ` @@ -62,16 +63,29 @@ export const toggleButtonGroupDisallowEmptySnippet = `Three `; -export const toggleButtonGroupIconsSnippet = ` - Cloud - Starred - Next +export const toggleButtonGroupIconsSnippet = `import { RiCloudLine, RiStarFill, RiStarLine, RiArrowRightSLine } from '@remixicon/react'; + + + } /> + } + /> + }> + Star + + }> + Next + `; -export const toggleButtonGroupIconsOnlySnippet = ` - - - +export const toggleButtonGroupIconsOnlySnippet = `import { RiCloudLine, RiStarLine, RiArrowRightSLine } from '@remixicon/react'; + + + } /> + } /> + } /> `; export const toggleButtonGroupSurfacesSnippet = ` diff --git a/docs-ui/src/content/toggle-button.mdx b/docs-ui/src/content/toggle-button.mdx index 593d97b02a..78750ec365 100644 --- a/docs-ui/src/content/toggle-button.mdx +++ b/docs-ui/src/content/toggle-button.mdx @@ -10,6 +10,7 @@ import { toggleButtonIconsSnippet, toggleButtonDisabledSnippet, toggleButtonControlledSnippet, + toggleButtonFunctionChildrenSnippet, } from './toggle-button.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -88,6 +89,18 @@ import { ToggleButtonDefinition } from '../utils/definitions'; code={toggleButtonControlledSnippet} /> +### Dynamic Content with Function Children + +The `children` prop can be a function that receives render props, allowing you to dynamically customize the button content based on component state (such as `isSelected`, `isDisabled`, `isHovered`, etc.). + +} + code={toggleButtonFunctionChildrenSnippet} +/> + diff --git a/docs-ui/src/content/toggle-button.props.ts b/docs-ui/src/content/toggle-button.props.ts index 9df1a24791..9115b29603 100644 --- a/docs-ui/src/content/toggle-button.props.ts +++ b/docs-ui/src/content/toggle-button.props.ts @@ -23,7 +23,12 @@ export const toggleButtonPropDefs: Record = { defaultSelected: { type: 'boolean' }, onChange: { type: 'enum', values: ['(isSelected: boolean) => void'] }, isDisabled: { type: 'boolean', default: 'false' }, - children: { type: 'enum', values: ['ReactNode'] }, + children: { + type: 'enum', + values: ['ReactNode', '(values: ToggleButtonRenderProps) => ReactNode'], + description: + 'The children of the component. A function may be provided to alter the children based on component state (such as `isSelected`, `isDisabled`, `isHovered`, etc.).', + }, ...classNamePropDefs, ...stylePropDefs, }; @@ -70,9 +75,12 @@ export const toggleButtonSizesSnippet = ` Medium `; -export const toggleButtonIconsSnippet = ` - Favorite - Confirm +export const toggleButtonIconsSnippet = `import { RiStarLine, RiStarFill, RiCheckLine } from '@remixicon/react'; + + + }>Favorite + } defaultSelected>Starred + }>Confirm `; export const toggleButtonDisabledSnippet = ` @@ -80,8 +88,108 @@ export const toggleButtonDisabledSnippet = ` Selected `; -export const toggleButtonControlledSnippet = `const [selected, setSelected] = useState(false); +export const toggleButtonControlledSnippet = `import { useState } from 'react'; +import { RiStarFill, RiStarLine } from '@remixicon/react'; - - {selected ? 'On' : 'Off'} +const [selected, setSelected] = useState(false); + + : } +> + {selected ? 'Starred' : 'Not starred'} `; + +export const toggleButtonFunctionChildrenSnippet = `import { RiStarFill, RiStarLine } from '@remixicon/react'; + + + Example 1: Selection State + + + {({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')} + + + {({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')} + + + + + + Example 2: Multiple States + + + {({ isSelected, isHovered }) => { + const states = []; + if (isSelected) states.push('on'); + else states.push('off'); + if (isHovered) states.push('hovered'); + return \`Email (\${states.join(', ')})\`; + }} + + + {({ isSelected, isHovered }) => { + const states = []; + if (isSelected) states.push('on'); + else states.push('off'); + if (isHovered) states.push('hovered'); + return \`Push (\${states.join(', ')})\`; + }} + + + + + + Example 3: Conditional Icons + + + {({ isSelected }) => ( + <> + {isSelected ? : } + {isSelected ? 'Starred' : 'Star'} + + )} + + + + + + Example 4: Status Indicators + + + {({ isSelected }) => ( + + + Active + + )} + + + {({ isSelected }) => ( + + + Inactive + + )} + + + +`; diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index afd08072ee..b7b6604421 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1856,8 +1856,6 @@ export interface ToggleButtonGroupProps // @public export interface ToggleButtonProps extends ToggleButtonProps_2 { - // (undocumented) - children?: ReactNode; // (undocumented) iconEnd?: ReactElement; // (undocumented) diff --git a/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx b/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx index f4be5cb46b..14183e6ede 100644 --- a/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx +++ b/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx @@ -16,13 +16,11 @@ import preview from '../../../../../.storybook/preview'; import { ToggleButton } from './ToggleButton'; -import { ToggleButtonGroup } from '../ToggleButtonGroup/ToggleButtonGroup'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { useState } from 'react'; import { RiCheckLine, - RiHeartLine, RiStarFill, RiStarLine, RiCloudLine, @@ -150,20 +148,6 @@ export const IconsAndText = meta.story({ ), }); -export const MixedIcons = meta.story({ - render: () => ( - - }> - Cloud - - } /> - }> - Next - - - ), -}); - export const Disabled = meta.story({ render: () => ( @@ -194,52 +178,128 @@ export const Controlled = meta.story({ }, }); -export const GroupSingle = meta.story({ - render: () => ( - - Apples - Oranges - Bananas - - ), +export const FunctionChildren = meta.story({ + render: () => { + const [disabled, setDisabled] = useState(false); + return ( + + + + {({ isDisabled, isSelected }) => + isDisabled + ? `Disabled ${isSelected ? '(Selected)' : '(Unselected)'}` + : `Enabled ${isSelected ? '(Selected)' : '(Unselected)'}` + } + + setDisabled(!disabled)} + > + {disabled ? 'Enable' : 'Disable'} + + + + Toggle the button to change the disabled state and see text update + + + ); + }, }); -export const GroupMultiple = meta.story({ - render: () => ( - - Dogs - Cats - Rabbits - - ), -}); +export const DynamicContent = meta.story({ + render: () => { + return ( + + + Example 1: Selection State + + + {({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')} + + + {({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')} + + + -export const GroupWithIcons = meta.story({ - render: () => ( - - }> - Chill - - }> - Focus - - }> - Party - - - ), -}); + + Example 2: Multiple States + + + {({ isSelected, isHovered }) => { + const states = []; + if (isSelected) states.push('on'); + else states.push('off'); + if (isHovered) states.push('hovered'); + return `Email (${states.join(', ')})`; + }} + + + {({ isSelected, isHovered }) => { + const states = []; + if (isSelected) states.push('on'); + else states.push('off'); + if (isHovered) states.push('hovered'); + return `Push (${states.join(', ')})`; + }} + + + -export const VerticalGroup = meta.story({ - render: () => ( - - Low - Medium - High - - ), + + Example 3: Conditional Icons + + + {({ isSelected }) => ( + <> + {isSelected ? : } + {isSelected ? 'Starred' : 'Star'} + + )} + + + + + + Example 4: Status Indicators + + + {({ isSelected }) => ( + + + Active + + )} + + + {({ isSelected }) => ( + + + Inactive + + )} + + + + + ); + }, }); diff --git a/packages/ui/src/components/ToggleButton/ToggleButton.tsx b/packages/ui/src/components/ToggleButton/ToggleButton.tsx index dd11554621..9c96304a80 100644 --- a/packages/ui/src/components/ToggleButton/ToggleButton.tsx +++ b/packages/ui/src/components/ToggleButton/ToggleButton.tsx @@ -47,14 +47,22 @@ export const ToggleButton = forwardRef( {...(typeof surface === 'string' ? { 'data-on-surface': surface } : {})} {...rest} > - - {iconStart} - {children} - {iconEnd} - + {renderProps => { + // If children is a function, call it with render props; otherwise use children as-is + const renderedChildren = + typeof children === 'function' ? children(renderProps) : children; + + return ( + + {iconStart} + {renderedChildren} + {iconEnd} + + ); + }} ); }, diff --git a/packages/ui/src/components/ToggleButton/types.ts b/packages/ui/src/components/ToggleButton/types.ts index aba31e4f25..de70bd3c29 100644 --- a/packages/ui/src/components/ToggleButton/types.ts +++ b/packages/ui/src/components/ToggleButton/types.ts @@ -15,7 +15,7 @@ */ import type { Breakpoint } from '../..'; -import type { ReactElement, ReactNode } from 'react'; +import type { ReactElement } from 'react'; import type { ToggleButtonProps as AriaToggleButtonProps } from 'react-aria-components'; import type { Responsive, Surface } from '../../types'; @@ -28,7 +28,6 @@ export interface ToggleButtonProps extends AriaToggleButtonProps { size?: 'small' | 'medium' | Partial>; iconStart?: ReactElement; iconEnd?: ReactElement; - children?: ReactNode; /** Surface the toggle button is placed on. Defaults to context surface if available */ onSurface?: Responsive; } diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css index 36309ad9dd..90bedd1756 100644 --- a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css @@ -24,6 +24,7 @@ border-radius: var(--bui-radius-2); overflow: hidden; box-shadow: inset 0 0 0 1px var(--bui-border); + width: fit-content; } .bui-ToggleButtonGroup[data-orientation='vertical'] { diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx index 6934b42eb6..43449f8472 100644 --- a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -15,28 +15,13 @@ */ import clsx from 'clsx'; -import { forwardRef, Ref, createContext, useContext } from 'react'; +import { forwardRef, Ref } from 'react'; import { ToggleButtonGroup as AriaToggleButtonGroup } from 'react-aria-components'; import type { ToggleButtonGroupProps } from './types'; import { useStyles } from '../../hooks/useStyles'; import { ToggleButtonGroupDefinition } from './definition'; import styles from './ToggleButtonGroup.module.css'; -/** @internal */ -export interface ToggleButtonGroupContextValue {} - -const ToggleButtonGroupContext = createContext< - ToggleButtonGroupContextValue | undefined ->(undefined); - -/** - * Hook to access the ToggleButtonGroup context. - * @internal - */ -export const useToggleButtonGroupContext = () => { - return useContext(ToggleButtonGroupContext); -}; - /** @public */ export const ToggleButtonGroup = forwardRef( (props: ToggleButtonGroupProps, ref: Ref) => { @@ -49,19 +34,15 @@ export const ToggleButtonGroup = forwardRef( const { className, children, ...rest } = cleanedProps; - const contextValue: ToggleButtonGroupContextValue = {}; - return ( - - - {children} - - + + {children} + ); }, );