Skip to content

Add ToggleSwitch component #1933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
81d5b63
adds Switch component and stories
mperrotti Mar 7, 2022
f02595e
adds tests and more ARIA markup
mperrotti Mar 8, 2022
872bdb4
adds component docs and fixes transition
mperrotti Mar 8, 2022
0316ed3
adds changeset
mperrotti Mar 8, 2022
27dccc7
Merge branch 'main' into mp/switch-component
mperrotti Mar 8, 2022
ca8950c
update snapshots
mperrotti Mar 8, 2022
108b40f
Merge branch 'mp/switch-component' of github.com:primer/react into mp…
mperrotti Mar 8, 2022
40ff806
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 8, 2022
cebfa1b
addresses component design feedback
mperrotti Mar 9, 2022
176b5b8
improves docs and storybook examples
mperrotti Mar 10, 2022
d653469
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 10, 2022
30612f9
updates snaps
mperrotti Mar 10, 2022
b475d8a
Merge branch 'main' into mp/switch-component
mperrotti Mar 11, 2022
1d52067
Update src/Switch.tsx
mperrotti Mar 11, 2022
37ed9ae
Update docs/content/Switch.mdx
mperrotti Mar 11, 2022
1971e6f
Update src/Switch.tsx
mperrotti Mar 11, 2022
3b8d444
Update src/Switch.tsx
mperrotti Mar 11, 2022
55817f2
addresses PR feedback
mperrotti Mar 15, 2022
22c19ae
Merge branch 'mp/switch-component' of github.com:primer/react into mp…
mperrotti Mar 15, 2022
f43c423
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 15, 2022
3ce5352
increases contrast of control to meet WCAG guidelines
mperrotti Mar 16, 2022
301e05a
CSS cleanup and revert to light off button
mperrotti Mar 17, 2022
8c32664
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 17, 2022
650a89f
updates snapshots
mperrotti Mar 17, 2022
1bd2e3f
addresses PR feedback
mperrotti Mar 17, 2022
5516bee
rename switch docs
mperrotti Mar 17, 2022
16a3c9c
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 18, 2022
f79f4a3
use component primitives for toggle switch colors
mperrotti Mar 21, 2022
b9558bb
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 21, 2022
bbadab8
fixes documentation mistakes
mperrotti Mar 21, 2022
ce275cd
updates snapshots
mperrotti Mar 21, 2022
d3022e4
Update docs/content/ToggleSwitch.mdx
mperrotti Mar 21, 2022
97cea00
Update docs/content/ToggleSwitch.mdx
mperrotti Mar 21, 2022
09b735e
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 21, 2022
7afe8e3
updates themePreval snapshot
mperrotti Mar 21, 2022
2871849
Merge branch 'main' of github.com:primer/react into mp/switch-component
mperrotti Mar 22, 2022
5aee49d
upgrades primitives, updates snapshots
mperrotti Mar 23, 2022
fa9ff75
Update snapshot
simurai Mar 24, 2022
bb78093
Merge branch 'main' into mp/switch-component
mperrotti Mar 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stupid-carrots-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Adds a toggle switch component
220 changes: 220 additions & 0 deletions docs/content/ToggleSwitch.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
---
componentId: toggle_switch
title: ToggleSwitch
description: Toggles a setting on or off, and immediately saves the change
status: Alpha
source: https://github.com/primer/react/blob/main/src/ToggleSwitch.tsx
storybook: '/react/storybook?path=/story/toggleswitch-examples--default'
---

## Examples

### Basic

```jsx live
<Box display="flex" maxWidth="300px">
<Box flexGrow={1} fontSize={2} fontWeight="bold" id="switchLabel">
Notifications
</Box>
<ToggleSwitch aria-labelledby="switchLabel" />
</Box>
```

### Uncontrolled with default value

```jsx live
<Box display="flex" maxWidth="300px">
<Box flexGrow={1} fontSize={2} fontWeight="bold" id="switchLabel">
Notifications
</Box>
<ToggleSwitch defaultChecked aria-labelledby="switchLabel" />
</Box>
```

### Controlled

```javascript noinline live
const Controlled = () => {
const [isOn, setIsOn] = React.useState(false)

const onClick = () => {
setIsOn(!isOn)
}

const handleSwitchChange = on => {
console.log(`new switch "on" state: ${on}`)
}

return (
<>
<Box display="flex" maxWidth="300px">
<Box flexGrow={1} fontSize={2} fontWeight="bold" id="switchLabel">
Notifications
</Box>
<ToggleSwitch onClick={onClick} onChange={handleSwitchChange} checked={isOn} aria-labelledby="switchLabel" />
</Box>
<p>The switch is {isOn ? 'on' : 'off'}</p>
</>
)
}

render(Controlled)
```

### Small

```jsx live
<Box display="flex" maxWidth="300px">
<Box flexGrow={1} fontSize={1} fontWeight="bold" id="switchLabel">
Notifications
</Box>
<ToggleSwitch aria-labelledby="switchLabel" size="small" />
</Box>
```

### Delayed toggle with loading state

```javascript noinline live
const LoadingToggle = () => {
const [loading, setLoading] = React.useState(false)
const [isOn, setIsOn] = React.useState(false)

async function switchSlowly(currentOn) {
await new Promise(resolve => setTimeout(resolve, 1500))
return await !currentOn
}

async function onClick() {
setLoading(!loading)
const newSwitchState = await switchSlowly(isOn)
setIsOn(newSwitchState)
}

const handleSwitchChange = React.useCallback(
on => {
setLoading(false)
},
[setLoading]
)

return (
<>
<Box display="flex" maxWidth="300px">
<Box flexGrow={1} fontSize={2} fontWeight="bold" id="switchLabel">
Notifications
</Box>
<ToggleSwitch
aria-labelledby="switchLabel"
loading={loading}
checked={isOn}
onClick={onClick}
onChange={handleSwitchChange}
/>
</Box>
<p>The switch is {isOn ? 'on' : 'off'}</p>
</>
)
}

render(LoadingToggle)
```

### Disabled

```jsx live
<Box display="flex" maxWidth="300px">
<Box flexGrow={1} fontSize={2} fontWeight="bold" id="switchLabel">
Notifications
</Box>
<ToggleSwitch aria-labelledby="switchLabel" disabled />
</Box>
```

### With associated caption text

```jsx live
<Box display="flex">
<Box flexGrow={1}>
<Text fontSize={2} fontWeight="bold" id="switchLabel" display="block">
Notifications
</Text>
<Text color="fg.subtle" fontSize={1} id="switchCaption" display="block">
Notifications will be delivered via email and the GitHub notification center
</Text>
</Box>
<ToggleSwitch aria-labelledby="switchLabel" aria-describedby="switchCaption" />
</Box>
```

### Left-aligned with label

```jsx live
<>
<Text fontSize={2} fontWeight="bold" id="switchLabel" display="block" mb={1}>
Notifications
</Text>
<ToggleSwitch statusLabelPosition="end" aria-labelledby="switchLabel" />
</>
```

## Props

<PropsTable>
<PropsTableRow name="aria-describedby" type="string" description="The id of the DOM node that describes the switch" />
<PropsTableRow
name="aria-labelledby"
type="string"
required
description="The id of the DOM node that labels the switch"
/>
<PropsTableRow name="defaultChecked" type="boolean" description="Uncontrolled - whether the switch is turned on" />
<PropsTableRow name="disabled" type="boolean" description="Whether the switch is ready for user input" />
<PropsTableRow name="loading" type="boolean" description="Whether the switch's value is being calculated" />
<PropsTableRow name="checked" type="boolean" description="Whether the switch is turned on" />
<PropsTableRow
name="onChange"
type="(on: boolean) => void"
description="The callback that is called when the switch is toggled on or off"
/>
<PropsTableRow
name="onClick"
type="(e: MouseEvent) => void"
description="The callback that is called when the switch is clicked"
/>
<PropsTableRow name="size" type="'small' | 'medium'" defaultValue="'medium'" description="Size of the switch" />
<PropsTableRow
name="statusLabelPosition"
type="'start' | 'end'"
defaultValue="'start'"
description={
<>
<div>Whether the "on" and "off" labels should appear before or after the switch.</div>
<div>
<Text fontWeight="bold">This should only be changed when the switch's alignment needs to be adjusted.</Text>{' '}
For example: It needs to be left-aligned because the label appears above it and the caption appears below it.
</div>
</>
}
/>
</PropsTable>

## Status

<ComponentChecklist
items={{
propsDocumented: true,
noUnnecessaryDeps: true,
adaptsToThemes: true,
adaptsToScreenSizes: true,
fullTestCoverage: true,
usedInProduction: false,
usageExamplesDocumented: true,
hasStorybookStories: true,
designReviewed: false,
a11yReviewed: false,
stableApi: false,
addressedApiFeedback: false,
hasDesignGuidelines: false,
hasFigmaComponent: false
}}
/>
2 changes: 2 additions & 0 deletions docs/src/@primer/gatsby-theme-doctocat/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@
url: /StyledOcticon
- title: SubNav
url: /SubNav
- title: ToggleSwitch
url: /ToggleSwitch
- title: TabNav
url: /TabNav
- title: Textarea
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"dependencies": {
"@primer/behaviors": "1.1.0",
"@primer/octicons-react": "16.1.1",
"@primer/primitives": "7.5.1",
"@primer/primitives": "7.6.0",
"@radix-ui/react-polymorphic": "0.0.14",
"@react-aria/ssr": "3.1.0",
"@styled-system/css": "5.1.5",
Expand Down
6 changes: 4 additions & 2 deletions src/Autocomplete/AutocompleteMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function getDefaultItemFilter<T extends AutocompleteMenuItem>(filterValue: strin
}
}

function getDefaultOnSelectionChange<T extends AutocompleteMenuItem>(
function getdefaultCheckedSelectionChange<T extends AutocompleteMenuItem>(
setInputValueFn: (value: string) => void
): OnSelectedChange<T> {
return function (itemOrItems) {
Expand Down Expand Up @@ -160,7 +160,9 @@ function AutocompleteMenu<T extends AutocompleteItemProps>(props: AutocompleteMe
const newSelectedItemIds = selectedItemIds.includes(item.id)
? otherSelectedItemIds
: [...otherSelectedItemIds, item.id]
const onSelectedChangeFn = onSelectedChange ? onSelectedChange : getDefaultOnSelectionChange(setInputValue)
const onSelectedChangeFn = onSelectedChange
? onSelectedChange
: getdefaultCheckedSelectionChange(setInputValue)

onSelectedChangeFn(
newSelectedItemIds.map(newSelectedItemId => getItemById(newSelectedItemId, items)) as T[]
Expand Down
Loading