-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(web-react): Introduce UncontrolledSplitButton component
- Solves #DS-1671
- Loading branch information
1 parent
fcac75a
commit 9539fbd
Showing
8 changed files
with
376 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
packages/web-react/src/components/SplitButton/UncontrolledSplitButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React, { useState } from 'react'; | ||
import { UncontrolledSplitButtonProps } from '../../types'; | ||
import { Button } from '../Button'; | ||
import { Dropdown, DropdownPopover, DropdownTrigger } from '../Dropdown'; | ||
import { Icon } from '../Icon'; | ||
import { VisuallyHidden } from '../VisuallyHidden'; | ||
import SplitButton from './SplitButton'; | ||
|
||
const defaultProps: Partial<UncontrolledSplitButtonProps> = { | ||
dropdownPlacement: 'bottom-end', | ||
dropdownIconName: 'chevron-down', | ||
}; | ||
|
||
const UncontrolledSplitButton = (props: UncontrolledSplitButtonProps) => { | ||
const propsWithDefaults = { ...defaultProps, ...props }; | ||
const { | ||
children, | ||
dropdownIconName, | ||
dropdownLabel, | ||
dropdownPlacement, | ||
id, | ||
isDisabled, | ||
buttonLabel, | ||
buttonOnClick, | ||
buttonIconName, | ||
...restProps | ||
} = propsWithDefaults; | ||
const [openDropdownStates, setOpenDropdownStates] = useState(false); | ||
|
||
return ( | ||
<SplitButton id={id} {...restProps}> | ||
<Button onClick={buttonOnClick} isDisabled={isDisabled}> | ||
{buttonIconName && <Icon name={buttonIconName} marginRight="space-400" />} | ||
{buttonLabel && buttonLabel} | ||
</Button> | ||
<Dropdown | ||
id={`${id}-dropdown`} | ||
isOpen={openDropdownStates} | ||
onToggle={() => setOpenDropdownStates(!openDropdownStates)} | ||
placement={dropdownPlacement} | ||
> | ||
<DropdownTrigger elementType={Button} isDisabled={isDisabled}> | ||
{!dropdownLabel && <VisuallyHidden>{dropdownIconName}</VisuallyHidden>} | ||
{dropdownLabel && dropdownLabel} | ||
<Icon name={dropdownIconName} marginLeft={dropdownLabel ? 'space-400' : undefined} /> | ||
</DropdownTrigger> | ||
<DropdownPopover>{children}</DropdownPopover> | ||
</Dropdown> | ||
</SplitButton> | ||
); | ||
}; | ||
|
||
export default UncontrolledSplitButton; |
92 changes: 92 additions & 0 deletions
92
packages/web-react/src/components/SplitButton/__tests__/UncontrolledSplitButton.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import '@testing-library/jest-dom'; | ||
import { render, screen } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { classNamePrefixProviderTest, restPropsTest, stylePropsTest } from '@local/tests'; | ||
import { ComponentButtonColors, Sizes } from '../../../constants'; | ||
import UncontrolledSplitButton from '../UncontrolledSplitButton'; | ||
|
||
describe('SplitButton', () => { | ||
const splitButtonColors = Object.values(ComponentButtonColors).filter( | ||
(color) => color !== ComponentButtonColors.PLAIN, | ||
); | ||
|
||
const onClick = jest.fn(); | ||
|
||
classNamePrefixProviderTest(UncontrolledSplitButton, 'SplitButton'); | ||
|
||
stylePropsTest(UncontrolledSplitButton); | ||
|
||
restPropsTest(UncontrolledSplitButton, 'div'); | ||
|
||
it('should have default classname', () => { | ||
render( | ||
<UncontrolledSplitButton | ||
id="uncontrolled-split-button-id" | ||
buttonLabel="Button" | ||
buttonOnClick={onClick} | ||
data-testid="test" | ||
> | ||
Content | ||
</UncontrolledSplitButton>, | ||
); | ||
|
||
expect(screen.getByTestId('test')).toHaveClass('SplitButton'); | ||
}); | ||
|
||
it('should render dropdown content', () => { | ||
render( | ||
<UncontrolledSplitButton id="uncontrolled-split-button-id" buttonLabel="Button" buttonOnClick={onClick}> | ||
Content | ||
</UncontrolledSplitButton>, | ||
); | ||
|
||
expect(screen.getByText('Content')).toHaveClass('DropdownPopover'); | ||
}); | ||
|
||
it.each(Object.values(Sizes))('should render size %s on buttons', (size) => { | ||
render( | ||
<UncontrolledSplitButton | ||
id="uncontrolled-split-button-id" | ||
buttonLabel="Button" | ||
buttonOnClick={onClick} | ||
size={size} | ||
> | ||
Content | ||
</UncontrolledSplitButton>, | ||
); | ||
|
||
expect(screen.getByText('Button')).toHaveClass(`Button--${size}`); | ||
}); | ||
|
||
it.each(splitButtonColors)('should render color %s on buttons', (color) => { | ||
render( | ||
<UncontrolledSplitButton | ||
id="uncontrolled-split-button-id" | ||
buttonLabel="Button" | ||
buttonOnClick={onClick} | ||
color={color} | ||
> | ||
Content | ||
</UncontrolledSplitButton>, | ||
); | ||
|
||
expect(screen.getByText('Button')).toHaveClass(`Button--${color}`); | ||
}); | ||
|
||
it('should render color and size on buttons', () => { | ||
render( | ||
<UncontrolledSplitButton | ||
id="uncontrolled-split-button-id" | ||
buttonLabel="Button" | ||
buttonOnClick={onClick} | ||
color="secondary" | ||
size="small" | ||
> | ||
Content | ||
</UncontrolledSplitButton>, | ||
); | ||
|
||
expect(screen.getByText('Button')).toHaveClass('Button--secondary'); | ||
expect(screen.getByText('Button')).toHaveClass('Button--small'); | ||
}); | ||
}); |
25 changes: 25 additions & 0 deletions
25
packages/web-react/src/components/SplitButton/demo/UncontrolledSplitButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from 'react'; | ||
import { Item } from '../../Item'; | ||
import UncontrolledSplitButton from '../UncontrolledSplitButton'; | ||
|
||
const UncontrolledSplitButtonDemo = () => { | ||
return ( | ||
<UncontrolledSplitButton | ||
buttonIconName="check-plain" | ||
buttonLabel="Button" | ||
buttonOnClick={() => alert('Button clicked')} | ||
color="secondary" | ||
dropdownIconName="more" | ||
dropdownLabel="More" | ||
dropdownPlacement="top-end" | ||
id="uncontrolled-split-button" | ||
isDisabled={false} | ||
size="large" | ||
> | ||
<Item label="Item 1" /> | ||
<Item label="Item 2" /> | ||
</UncontrolledSplitButton> | ||
); | ||
}; | ||
|
||
export default UncontrolledSplitButtonDemo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
'use client'; | ||
|
||
export { default as SplitButton } from './SplitButton'; | ||
export { default as UncontrolledSplitButton } from './UncontrolledSplitButton'; | ||
export * from './useSplitButtonStyleProps'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
packages/web-react/src/components/SplitButton/stories/UncontrolledSplitButton.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { Markdown } from '@storybook/blocks'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import React from 'react'; | ||
import { ComponentButtonColors, Placements, Sizes } from '../../../constants'; | ||
import ReadMe from '../README.md'; | ||
import { UncontrolledSplitButton } from '..'; | ||
import { DropdownContent } from './SplitButtonParts'; | ||
|
||
const meta: Meta<typeof UncontrolledSplitButton> = { | ||
title: 'Components/SplitButton', | ||
component: UncontrolledSplitButton, | ||
parameters: { | ||
docs: { | ||
page: () => <Markdown>{ReadMe}</Markdown>, | ||
}, | ||
controls: { exclude: ['ElementTag', 'UNSAFE_className', 'UNSAFE_style', 'transferClassName'] }, | ||
}, | ||
argTypes: { | ||
id: { | ||
control: 'text', | ||
description: 'The ID of the Uncontrolled Split Button.', | ||
table: { | ||
type: { summary: 'string' }, | ||
}, | ||
}, | ||
size: { | ||
control: 'select', | ||
options: [...Object.values(Sizes)], | ||
description: 'Size of the button.', | ||
table: { | ||
defaultValue: { summary: Sizes.MEDIUM }, | ||
type: { summary: 'ButtonSize' }, | ||
}, | ||
}, | ||
color: { | ||
control: 'select', | ||
options: [...Object.values(ComponentButtonColors).filter((color) => color !== ComponentButtonColors.PLAIN)], | ||
description: 'Color of the button.', | ||
table: { | ||
defaultValue: { summary: ComponentButtonColors.PRIMARY }, | ||
type: { summary: 'ButtonColor' }, | ||
}, | ||
}, | ||
buttonLabel: { | ||
control: 'text', | ||
description: 'The label for the button.', | ||
table: { | ||
type: { summary: 'string' }, | ||
}, | ||
}, | ||
buttonIconName: { | ||
control: 'text', | ||
description: 'The name of the icon to display on the button.', | ||
table: { | ||
type: { summary: 'string' }, | ||
}, | ||
}, | ||
dropdownLabel: { | ||
control: 'text', | ||
description: 'The label for the dropdown button.', | ||
table: { | ||
type: { summary: 'string' }, | ||
}, | ||
}, | ||
children: { | ||
control: 'text', | ||
description: 'The content of the dropdown.', | ||
table: { | ||
type: { summary: 'ReactNode' }, | ||
}, | ||
}, | ||
dropdownPlacement: { | ||
control: 'select', | ||
options: Object.values(Placements), | ||
table: { | ||
defaultValue: { summary: Placements.BOTTOM_END }, | ||
}, | ||
description: 'The placement of the dropdown.', | ||
}, | ||
isDisabled: { | ||
control: 'boolean', | ||
description: 'Whether the button is disabled.', | ||
table: { | ||
type: { summary: 'boolean' }, | ||
}, | ||
}, | ||
dropdownIconName: { | ||
control: 'text', | ||
description: 'The name of the icon to display on the dropdown button.', | ||
table: { | ||
type: { summary: 'string' }, | ||
}, | ||
}, | ||
buttonOnClick: { | ||
description: 'Function to call when the button is clicked.', | ||
table: { | ||
type: { summary: '() => void' }, | ||
}, | ||
}, | ||
}, | ||
args: { | ||
buttonIconName: undefined, | ||
buttonLabel: 'Button', | ||
buttonOnClick: () => {}, | ||
children: <DropdownContent />, | ||
color: ComponentButtonColors.PRIMARY, | ||
dropdownIconName: 'chevron-down', | ||
dropdownLabel: undefined, | ||
dropdownPlacement: Placements.BOTTOM_END, | ||
id: 'uncontrolled-split-button', | ||
isDisabled: false, | ||
size: Sizes.MEDIUM, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof UncontrolledSplitButton>; | ||
|
||
export const UncontrolledSplitButtonPlayground: Story = { | ||
name: 'UncontrolledSplitButton', | ||
render: (args) => { | ||
const { children, ...rest } = args; | ||
|
||
return <UncontrolledSplitButton {...rest}>{children}</UncontrolledSplitButton>; | ||
}, | ||
}; |
Oops, something went wrong.