-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2a616ad
commit abd3b02
Showing
8 changed files
with
378 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { Meta, Canvas, ArgTypes, Story } from '@storybook/blocks'; | ||
import Switch from './Switch'; | ||
import * as SwitchStories from './Switch.stories'; | ||
|
||
<Meta of={SwitchStories} /> | ||
|
||
<SectionHeader title={'Switch'} /> | ||
|
||
- [Overview](#overview) | ||
- [Props](#props) | ||
- [Usage](#usage) | ||
- [Variants](#variants) | ||
|
||
## Overview | ||
|
||
Enables the user to set the state of a single setting to 'on' or 'off'. | ||
|
||
## Props | ||
|
||
<ArgTypes of={Switch} /> | ||
|
||
## Usage | ||
|
||
<UsageGuidelines | ||
guidelines={[ | ||
'Use switches for binary choices only (e.g. “enable/disable”)', | ||
'When the item that is being toggled has a default state (e.g. deactivated by default)', | ||
]} | ||
policies={[ | ||
'If you have more than two choices, use a checkbox or a radio button instead', | ||
'If you can select 1+ items, use checkboxes', | ||
'If you can select exactly 1 item, use radio buttons', | ||
]} | ||
/> | ||
|
||
<SubsectionHeader title="Variants" /> | ||
|
||
### Simple Switch | ||
|
||
<Canvas of={SwitchStories.SimpleSwitch} /> | ||
|
||
### Switch label placement | ||
|
||
Label can be placed either `left` or `right` of the switch. | ||
|
||
<Tip> | ||
The labelConfig prop of each Switch includes an sx prop in order to add custom styles on the | ||
container that wraps the switch input and the label | ||
</Tip> | ||
|
||
<Canvas of={SwitchStories.SwitchLabelPlacement} /> | ||
|
||
### Switch label sizes | ||
|
||
Label can be either a string or a custom component. In the case of the string, there are 2 sizes (declared inside the labelConfig), `normal` and `large` | ||
|
||
<Canvas of={SwitchStories.SwitchLabelSizes} /> | ||
|
||
### Switch with helptext | ||
|
||
Switch can include a helptext under the label. | ||
|
||
<Canvas of={SwitchStories.SwitchWithHelptext} /> | ||
|
||
### Disabled Switch | ||
|
||
<Canvas of={SwitchStories.DisabledSwitch} /> | ||
|
||
### Playground | ||
|
||
<Canvas of={SwitchStories.Playground} /> |
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,108 @@ | ||
import { FIGMA_URL } from 'utils/common'; | ||
import Switch from './Switch'; | ||
import { useState } from 'react'; | ||
import Stack from 'components/storyUtils/Stack'; | ||
import { boolean, select, text } from '@storybook/addon-knobs'; | ||
|
||
export default { | ||
title: 'Updated Components/Controls/Switch', | ||
component: Switch, | ||
parameters: { | ||
design: [ | ||
{ | ||
type: 'figma', | ||
name: 'Switch', | ||
url: `${FIGMA_URL}?node-id=10283%3A104364`, | ||
}, | ||
], | ||
}, | ||
}; | ||
|
||
export const SimpleSwitch = { | ||
render: () => { | ||
const [selected, setSelected] = useState(false); | ||
return ( | ||
<Switch isSelected={selected} onChange={setSelected}> | ||
Option | ||
</Switch> | ||
); | ||
}, | ||
name: 'Simple Switch', | ||
}; | ||
|
||
export const SwitchLabelPlacement = { | ||
render: () => { | ||
return ( | ||
<> | ||
<Stack height={50}> | ||
<Switch>Option</Switch> | ||
</Stack> | ||
<Stack> | ||
<Switch labelConfig={{ placement: 'left' }}>Option</Switch> | ||
</Stack> | ||
</> | ||
); | ||
}, | ||
name: 'Switch label placement', | ||
}; | ||
|
||
export const SwitchLabelSizes = { | ||
render: () => { | ||
return ( | ||
<> | ||
<Stack height={50}> | ||
<Switch>Normal Option</Switch> | ||
</Stack> | ||
<Stack> | ||
<Switch labelConfig={{ size: 'large' }}>Large Option</Switch> | ||
</Stack> | ||
</> | ||
); | ||
}, | ||
name: 'Switch label sizes', | ||
}; | ||
|
||
export const SwitchWithHelptext = { | ||
render: () => { | ||
return <Switch labelConfig={{ helpText: 'This is the helptext of the option' }}>Option</Switch>; | ||
}, | ||
name: 'Switch with helptext', | ||
}; | ||
|
||
export const DisabledSwitch = { | ||
render: () => { | ||
return ( | ||
<> | ||
<Stack height={50}> | ||
<Switch isDisabled>Option</Switch> | ||
</Stack> | ||
<Stack> | ||
<Switch labelConfig={{ helpText: 'This option is disabled' }} isDisabled> | ||
Option | ||
</Switch> | ||
</Stack> | ||
</> | ||
); | ||
}, | ||
name: 'Disabled Switch', | ||
}; | ||
|
||
export const Playground = { | ||
render: () => { | ||
return ( | ||
<Stack> | ||
<Switch | ||
labelConfig={{ | ||
placement: select('Label placement', ['left', 'right'], 'right'), | ||
size: select('Label size', ['normal', 'large'], 'normal'), | ||
helpText: text('Help text', ''), | ||
}} | ||
isDisabled={boolean('isDisabled', false)} | ||
> | ||
Option | ||
</Switch> | ||
</Stack> | ||
); | ||
}, | ||
name: 'Playground', | ||
}; |
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,77 @@ | ||
import type { SerializedStyles } from '@emotion/react'; | ||
import { css } from '@emotion/react'; | ||
import type { Theme } from 'theme'; | ||
|
||
import { getControlsTokens } from 'components/Controls/Controls.tokens'; | ||
import type { LabelConfig } from 'components/Controls/Controls.types'; | ||
|
||
export const switchStyles = | ||
({ placement = 'right', sx }: Pick<LabelConfig, 'placement' | 'sx'>) => | ||
(theme: Theme): SerializedStyles => { | ||
const tokens = getControlsTokens(theme); | ||
|
||
return css` | ||
display: flex; | ||
flex-direction: ${placement === 'right' ? 'row' : 'row-reverse'}; | ||
align-items: center; | ||
gap: ${tokens('switch.padding')}; | ||
position: relative; | ||
cursor: pointer; | ||
.bar { | ||
width: ${tokens('switch.width.track')}; | ||
height: ${tokens('switch.height.track')}; | ||
background: ${tokens('switch.backgroundColor.track')}; | ||
position: absolute; | ||
border-radius: ${tokens('switch.borderRadius')}; | ||
} | ||
.indicator { | ||
width: ${tokens('switch.width.track')}; | ||
height: ${tokens('switch.size.thumb')}; | ||
box-sizing: border-box; | ||
position: relative; | ||
&:before { | ||
content: ''; | ||
box-sizing: border-box; | ||
display: block; | ||
width: ${tokens('switch.size.thumb')}; | ||
height: ${tokens('switch.size.thumb')}; | ||
background: ${tokens('switch.backgroundColor.thumb.default')}; | ||
border: ${tokens('switch.borderWidth')} solid | ||
${tokens('switch.borderColor.thumb.default')}; | ||
border-radius: 100%; | ||
transition: all 200ms; | ||
} | ||
} | ||
&[data-hovered], | ||
&[data-focus-visible='true'] { | ||
.indicator { | ||
&:before { | ||
transition: all 0.2s; | ||
box-shadow: 0px 0px 0px 8px ${theme.tokens.state.get('backgroundColor.hover')}; | ||
border-radius: 100%; | ||
} | ||
} | ||
} | ||
&[data-selected] { | ||
.indicator { | ||
&:before { | ||
transform: translateX(80%); | ||
background: ${tokens('switch.backgroundColor.thumb.active')}; | ||
border-color: ${tokens('switch.borderColor.thumb.active')}; | ||
} | ||
} | ||
} | ||
&[data-disabled] { | ||
opacity: ${theme.tokens.disabledState.get('default')}; | ||
cursor: not-allowed; | ||
} | ||
${sx}; | ||
`; | ||
}; |
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,54 @@ | ||
import userEvent from '@testing-library/user-event'; | ||
import React from 'react'; | ||
|
||
import { render, screen } from '../../../test'; | ||
import Switch from './Switch'; | ||
import { Mock } from 'vitest'; | ||
|
||
describe('Switch', () => { | ||
let mockOnClick: Mock<any, any>; | ||
|
||
beforeEach(() => { | ||
mockOnClick = vi.fn(); | ||
}); | ||
|
||
afterEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
|
||
it('it renders the Switch correctly', () => { | ||
const { container } = render(<Switch>Label</Switch>); | ||
|
||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it('should be able to change its check condition', async () => { | ||
render(<Switch />); | ||
|
||
const switchComponent = screen.getByTestId('undefined_undefined_switch'); | ||
|
||
expect(switchComponent.getAttribute('data-selected')).toEqual(null); | ||
|
||
await userEvent.click(switchComponent); | ||
|
||
expect(switchComponent.getAttribute('data-selected')).toEqual('true'); | ||
}); | ||
|
||
it('should invoke the onChange function', async () => { | ||
render(<Switch isSelected={false} onChange={mockOnClick} />); | ||
const switchComponent = screen.getByTestId('undefined_undefined_switch'); | ||
|
||
await userEvent.click(switchComponent); | ||
|
||
expect(mockOnClick).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should not invoke the onChange function if the switch is disabled', async () => { | ||
render(<Switch isSelected={false} onChange={mockOnClick} isDisabled />); | ||
const switchComponent = screen.getByTestId('undefined_undefined_switch'); | ||
|
||
await userEvent.click(switchComponent); | ||
|
||
expect(mockOnClick).toHaveBeenCalledTimes(0); | ||
}); | ||
}); |
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,63 @@ | ||
import * as React from 'react'; | ||
import type { SwitchAria } from 'react-aria'; | ||
import { Switch as ReactAriaSwitch } from 'react-aria-components'; | ||
import type { TestProps } from 'utils/types'; | ||
|
||
import { switchStyles } from './Switch.style'; | ||
import ControlLabel from 'components/Controls/ControlLabel'; | ||
import type { LabelConfig } from 'components/Controls/Controls.types'; | ||
|
||
export type SwitchProps = Partial<SwitchAria> & { | ||
/** Id property of the radio input */ | ||
id?: string; | ||
/** The value of the radio input */ | ||
value?: string; | ||
/** Callback for when the element's selection state changes. */ | ||
onChange?: (isSelected: boolean) => void; | ||
/** Label configuration; includes placement, size, helpText and sx */ | ||
labelConfig?: LabelConfig; | ||
children?: React.ReactNode; | ||
} & TestProps; | ||
|
||
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>((props, ref) => { | ||
const { | ||
id, | ||
value, | ||
isSelected, | ||
isDisabled, | ||
onChange, | ||
labelConfig = {}, | ||
dataTestPrefixId, | ||
children, | ||
} = props; | ||
const { placement = 'right', size = 'normal', helpText, sx } = labelConfig; | ||
|
||
return ( | ||
<ReactAriaSwitch | ||
id={id} | ||
value={value} | ||
isSelected={isSelected} | ||
isDisabled={isDisabled} | ||
onChange={onChange} | ||
css={switchStyles({ placement, sx })} | ||
data-testid={`${dataTestPrefixId}_${value}_switch`} | ||
ref={ref} | ||
> | ||
<div className="bar" /> | ||
<div className="indicator" /> | ||
{children && ( | ||
<ControlLabel | ||
size={size} | ||
helpText={helpText} | ||
dataTestPrefixId={`${dataTestPrefixId}_radio_${value?.split(' ').join('_')}`} | ||
> | ||
{children} | ||
</ControlLabel> | ||
)} | ||
</ReactAriaSwitch> | ||
); | ||
}); | ||
|
||
Switch.displayName = 'Switch'; | ||
|
||
export default Switch; |
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,2 @@ | ||
export { default } from './Switch'; | ||
export * from './Switch'; |
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,2 +1,3 @@ | ||
export { default as Radio } from './Radio'; | ||
export { default as RadioGroup } from './Radio/components/RadioGroup'; | ||
export { default as Switch } from './Switch'; |
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