diff --git a/packages/ui/src/ButtonGroup/index.stories.tsx b/packages/ui/src/ButtonGroup/index.stories.tsx index a3d9e55011..5ea487ae66 100644 --- a/packages/ui/src/ButtonGroup/index.stories.tsx +++ b/packages/ui/src/ButtonGroup/index.stories.tsx @@ -18,6 +18,7 @@ export const Basic: Story = { args: { actionType: 'default', size: 'sparse', + iconOnly: false, buttons: [ { label: 'Delegate', diff --git a/packages/ui/src/ButtonGroup/index.tsx b/packages/ui/src/ButtonGroup/index.tsx index ebd1538886..7fd51daf4b 100644 --- a/packages/ui/src/ButtonGroup/index.tsx +++ b/packages/ui/src/ButtonGroup/index.tsx @@ -16,18 +16,17 @@ const Root = styled.div<{ $size: Size }>` `} `; -const ButtonWrapper = styled.div<{ $size: Size }>` - flex-grow: ${props => (props.$size === 'sparse' ? 1 : 0)}; - flex-shrink: ${props => (props.$size === 'sparse' ? 1 : 0)}; +const ButtonWrapper = styled.div<{ $size: Size; $iconOnly?: boolean }>` + flex-grow: ${props => (props.$size === 'sparse' && !props.$iconOnly ? 1 : 0)}; + flex-shrink: ${props => (props.$size === 'sparse' && !props.$iconOnly ? 1 : 0)}; `; -interface ButtonDescription { +type ButtonDescription = { label: string; - icon?: LucideIcon; onClick?: MouseEventHandler; -} +} & (IconOnly extends true ? { icon: LucideIcon } : { icon?: LucideIcon }); -export interface ButtonGroupProps { +export interface ButtonGroupProps { /** * An array of objects, each describing a button to render. The first will be * rendered with the `primary` variant, the rest with the `secondary` variant. @@ -35,9 +34,9 @@ export interface ButtonGroupProps { * Minimum length: 1. Maximum length: 3. */ buttons: - | [ButtonDescription] - | [ButtonDescription, ButtonDescription] - | [ButtonDescription, ButtonDescription, ButtonDescription]; + | [ButtonDescription] + | [ButtonDescription, ButtonDescription] + | [ButtonDescription, ButtonDescription, ButtonDescription]; /** * The action type of the button group. Will be used for all buttons in the * group. @@ -45,8 +44,18 @@ export interface ButtonGroupProps { actionType?: ActionType; /** Will be used for all buttons in the group. */ size?: Size; + /** + * When `true`, will render just icon buttons. The label for each button will + * be used as the `aria-label`. + * + * Will be used for all buttons in the group. + */ + iconOnly?: IconOnly; } +const isIconOnly = (props: ButtonGroupProps): props is ButtonGroupProps => + !!props.iconOnly; + /** * Use a `` to render multiple buttons in a group with the same * `actionType` and `size`. @@ -54,23 +63,47 @@ export interface ButtonGroupProps { * When rendering multiple Penumbra UI buttons together, always use a `` rather than individual ` - - ))} - -); + ...props +}: ButtonGroupProps) => { + return ( + + {/* Annoying TypeScript workaround — we need to explicitly delineate the + `isIconOnly` and `!isIconOnly` cases, since TypeScript won't resolve the + compatibility of the icon-only and non-icon-only types otherwise. If + someone comes up with a better way to do this, feel free to revisit this. + */} + {isIconOnly(props) && + props.buttons.map((button, index) => ( + + + + ))} + + {!isIconOnly(props) && + props.buttons.map((button, index) => ( + + + + ))} + + ); +};