diff --git a/.changeset/stupid-carrots-jam.md b/.changeset/stupid-carrots-jam.md
new file mode 100644
index 00000000000..0df7f9b5237
--- /dev/null
+++ b/.changeset/stupid-carrots-jam.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+Adds a toggle switch component
diff --git a/docs/content/ToggleSwitch.mdx b/docs/content/ToggleSwitch.mdx
new file mode 100644
index 00000000000..ae2764af93e
--- /dev/null
+++ b/docs/content/ToggleSwitch.mdx
@@ -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
+
+
+ Notifications
+
+
+
+```
+
+### Uncontrolled with default value
+
+```jsx live
+
+
+ Notifications
+
+
+
+```
+
+### 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 (
+ <>
+
+
+ Notifications
+
+
+
+
The switch is {isOn ? 'on' : 'off'}
+ >
+ )
+}
+
+render(Controlled)
+```
+
+### Small
+
+```jsx live
+
+
+ Notifications
+
+
+
+```
+
+### 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 (
+ <>
+
+
+ Notifications
+
+
+
+ The switch is {isOn ? 'on' : 'off'}
+ >
+ )
+}
+
+render(LoadingToggle)
+```
+
+### Disabled
+
+```jsx live
+
+
+ Notifications
+
+
+
+```
+
+### With associated caption text
+
+```jsx live
+
+
+
+ Notifications
+
+
+ Notifications will be delivered via email and the GitHub notification center
+
+
+
+
+```
+
+### Left-aligned with label
+
+```jsx live
+<>
+
+ Notifications
+
+
+>
+```
+
+## Props
+
+
+
+
+
+
+
+
+
+
+
+
+ Whether the "on" and "off" labels should appear before or after the switch.
+
+ This should only be changed when the switch's alignment needs to be adjusted. {' '}
+ For example: It needs to be left-aligned because the label appears above it and the caption appears below it.
+
+ >
+ }
+ />
+
+
+## Status
+
+
diff --git a/docs/src/@primer/gatsby-theme-doctocat/nav.yml b/docs/src/@primer/gatsby-theme-doctocat/nav.yml
index 0637cea346a..1f096be7ecc 100644
--- a/docs/src/@primer/gatsby-theme-doctocat/nav.yml
+++ b/docs/src/@primer/gatsby-theme-doctocat/nav.yml
@@ -123,6 +123,8 @@
url: /StyledOcticon
- title: SubNav
url: /SubNav
+ - title: ToggleSwitch
+ url: /ToggleSwitch
- title: TabNav
url: /TabNav
- title: Textarea
diff --git a/package-lock.json b/package-lock.json
index 516123d6476..149f5b67255 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,17 @@
{
"name": "@primer/react",
- "version": "35.0.0",
+ "version": "35.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@primer/react",
- "version": "35.0.0",
+ "version": "35.1.0",
"license": "MIT",
"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",
@@ -5659,9 +5659,9 @@
}
},
"node_modules/@primer/primitives": {
- "version": "7.5.1",
- "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.5.1.tgz",
- "integrity": "sha512-1pFKR+FcYRPXJ+zK/qtidrCJB7WmTaAX4sG7zE5LvGWjS5latue4pzZrK0FxxGGBdAU3HpoabANsGjv7T7sRRg=="
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.6.0.tgz",
+ "integrity": "sha512-cu28QLjectVf2rT4P7m6zS9v4g4yHtErRuPfsgEWEJhbXVIII6vDBm6elJzYixGpTNxpVtSUNezxUXv16l1ejQ=="
},
"node_modules/@radix-ui/react-polymorphic": {
"version": "0.0.14",
@@ -39292,9 +39292,9 @@
"requires": {}
},
"@primer/primitives": {
- "version": "7.5.1",
- "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.5.1.tgz",
- "integrity": "sha512-1pFKR+FcYRPXJ+zK/qtidrCJB7WmTaAX4sG7zE5LvGWjS5latue4pzZrK0FxxGGBdAU3HpoabANsGjv7T7sRRg=="
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.6.0.tgz",
+ "integrity": "sha512-cu28QLjectVf2rT4P7m6zS9v4g4yHtErRuPfsgEWEJhbXVIII6vDBm6elJzYixGpTNxpVtSUNezxUXv16l1ejQ=="
},
"@radix-ui/react-polymorphic": {
"version": "0.0.14",
diff --git a/package.json b/package.json
index f80909698e1..a131dec5295 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/Autocomplete/AutocompleteMenu.tsx b/src/Autocomplete/AutocompleteMenu.tsx
index 22e99c61652..efe4bff8d25 100644
--- a/src/Autocomplete/AutocompleteMenu.tsx
+++ b/src/Autocomplete/AutocompleteMenu.tsx
@@ -23,7 +23,7 @@ function getDefaultItemFilter(filterValue: strin
}
}
-function getDefaultOnSelectionChange(
+function getdefaultCheckedSelectionChange(
setInputValueFn: (value: string) => void
): OnSelectedChange {
return function (itemOrItems) {
@@ -160,7 +160,9 @@ function AutocompleteMenu(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[]
diff --git a/src/ToggleSwitch.tsx b/src/ToggleSwitch.tsx
new file mode 100644
index 00000000000..14e07f1e006
--- /dev/null
+++ b/src/ToggleSwitch.tsx
@@ -0,0 +1,329 @@
+import React, {MouseEventHandler, useCallback, useEffect} from 'react'
+import styled, {css} from 'styled-components'
+import {variant} from 'styled-system'
+import {Box, Spinner, Text} from '.'
+import {get} from './constants'
+import {useProvidedStateOrCreate} from './hooks'
+import sx, {BetterSystemStyleObject, SxProp} from './sx'
+import VisuallyHidden from './_VisuallyHidden'
+
+const TRANSITION_DURATION = '80ms'
+const EASE_OUT_QUAD_CURVE = 'cubic-bezier(0.5, 1, 0.89, 1)'
+
+type SwitchProps = {
+ /** The id of the DOM node that describes the switch */
+ ['aria-describedby']?: string
+ /** The id of the DOM node that labels the switch */
+ ['aria-labelledby']: string
+ /** Uncontrolled - whether the switch is turned on */
+ defaultChecked?: boolean
+ /** Whether the switch is ready for user input */
+ disabled?: boolean
+ /** Whether the switch's value is being calculated */
+ loading?: boolean
+ /** Whether the switch is turned on */
+ checked?: boolean
+ /** The callback that is called when the switch is toggled on or off */
+ onChange?: (on: boolean) => void
+ /** The callback that is called when the switch is clicked */
+ onClick?: MouseEventHandler
+ /** Size of the switch */
+ size?: 'small' | 'medium'
+ /** Whether the "on" and "off" labels should appear before or after the switch.
+ * **This should only be changed when the switch's alignment needs to be adjusted.** For example: It needs to be left-aligned because the label appears above it and the caption appears below it.
+ */
+ statusLabelPosition?: 'start' | 'end'
+} & SxProp
+
+const sizeVariants = variant({
+ prop: 'size',
+ variants: {
+ small: {
+ height: '24px',
+ width: '48px'
+ }
+ }
+})
+
+type SwitchButtonProps = {
+ disabled?: boolean
+ checked?: boolean
+ size?: SwitchProps['size']
+} & SxProp
+
+type InnerIconProps = {size?: SwitchProps['size']}
+
+const CircleIcon: React.FC = ({size}) => (
+
+
+
+)
+const LineIcon: React.FC = ({size}) => (
+
+
+
+)
+
+const SwitchButton = styled.button`
+ vertical-align: middle;
+ cursor: pointer;
+ user-select: none;
+ appearance: none;
+ text-decoration: none;
+ padding: 0;
+ transition-property: background-color, border-color;
+ transition-duration: ${TRANSITION_DURATION};
+ transition-timing-function: ${EASE_OUT_QUAD_CURVE};
+ border-radius: ${get('radii.2')};
+ border-style: solid;
+ border-width: 1px;
+ display: block;
+ height: 32px;
+ width: 64px;
+ outline-offset: 2px;
+ position: relative;
+
+ @media (pointer: coarse) {
+ &:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ transform: translateY(-50%);
+ top: 50%;
+ min-height: 44px;
+ }
+ }
+
+ @media (prefers-reduced-motion) {
+ transition: none;
+
+ * {
+ transition: none;
+ }
+ }
+
+ &:after {
+ content: '';
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: calc(${get('radii.2')} - 1px); /* -1px to account for 1px border around the control */
+ }
+
+ ${props => {
+ if (props.disabled) {
+ return css`
+ background-color: ${get('colors.canvas.subtle')};
+ border-color: ${get('colors.border.subtle')};
+ cursor: not-allowed;
+ transition-property: none;
+ `
+ }
+
+ if (props.checked) {
+ return css`
+ background-color: ${get('colors.switchTrack.checked.bg')};
+ border-color: ${get('colors.switchTrack.checked.border')};
+
+ &:hover,
+ &:focus:focus-visible {
+ background-color: ${get('colors.switchTrack.checked.hoverBg')};
+ }
+
+ &:active,
+ &:active:focus-visible {
+ background-color: ${get('colors.switchTrack.checked.activeBg')};
+ }
+ `
+ } else {
+ return css`
+ background-color: ${get('colors.switchTrack.bg')};
+ border-color: ${get('colors.switchTrack.border')};
+
+ &:hover,
+ &:focus:focus-visible {
+ .Toggle-knob {
+ background-color: ${get('colors.btn.hoverBg')};
+ }
+ }
+
+ &:active,
+ &:active:focus-visible {
+ .Toggle-knob {
+ background-color: ${get('colors.btn.activeBg')};
+ }
+ }
+ `
+ }
+ }}
+
+ ${sx}
+ ${sizeVariants}
+`
+
+const ToggleKnob = styled.div<{checked?: boolean; disabled?: boolean}>`
+ background-color: ${get('colors.btn.bg')};
+ border-width: 1px;
+ border-style: solid;
+ border-color: ${props => (props.disabled ? get('colors.border.default') : get('colors.switchTrack.border'))};
+ border-radius: calc(${get('radii.2')} - 1px); /* -1px to account for 1px border around the control */
+ box-shadow: ${props =>
+ props.disabled ? 'none' : `${props.theme?.shadows?.shadow.medium}, ${props.theme?.shadows?.btn.insetShadow}`};
+ width: 50%;
+ position: absolute;
+ top: -1px;
+ bottom: -1px;
+ transition-property: transform;
+ transition-duration: ${TRANSITION_DURATION};
+ transition-timing-function: ${EASE_OUT_QUAD_CURVE};
+ transform: ${props => `translateX(${props.checked ? 'calc(100% + 1px)' : '-1px'})`};
+ z-index: 1;
+
+ @media (prefers-reduced-motion) {
+ transition: none;
+ }
+
+ ${props => {
+ if (props.checked) {
+ return css`
+ background-color: ${props.disabled
+ ? get('colors.switchKnob.checked.disabledBg')
+ : get('colors.switchKnob.checked.bg')};
+ border-color: ${props.disabled
+ ? get('colors.switchKnob.checked.disabledBg')
+ : get('colors.switchKnob.checked.bg')};
+ box-shadow: ${get('shadows.shadow.small')};
+ `
+ }
+ }}
+`
+
+const hiddenTextStyles: BetterSystemStyleObject = {
+ visibility: 'hidden',
+ height: 0
+}
+
+const Switch: React.FC = ({
+ 'aria-labelledby': ariaLabelledby,
+ 'aria-describedby': ariaDescribedby,
+ defaultChecked,
+ disabled,
+ loading,
+ checked,
+ onChange,
+ onClick,
+ size,
+ statusLabelPosition,
+ sx: sxProp
+}) => {
+ const isControlled = typeof checked !== 'undefined'
+ const [isOn, setIsOn] = useProvidedStateOrCreate(checked, onChange, Boolean(defaultChecked))
+ const acceptsInteraction = !disabled && !loading
+ const handleToggleClick: MouseEventHandler = useCallback(
+ e => {
+ if (!isControlled) {
+ setIsOn(!isOn)
+ }
+ onClick && onClick(e)
+ },
+ [onClick, isControlled, isOn, setIsOn]
+ )
+
+ useEffect(() => {
+ if (onChange && isControlled) {
+ onChange(Boolean(checked))
+ }
+ }, [onChange, checked, isControlled])
+
+ return (
+
+ {loading ? : null}
+
+
+ On
+
+
+ Off
+
+
+
+ {isOn ? 'On' : 'Off'}
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+Switch.defaultProps = {
+ statusLabelPosition: 'start',
+ size: 'medium'
+}
+
+export default Switch
diff --git a/src/__tests__/ToggleSwitch.test.tsx b/src/__tests__/ToggleSwitch.test.tsx
new file mode 100644
index 00000000000..65816bae599
--- /dev/null
+++ b/src/__tests__/ToggleSwitch.test.tsx
@@ -0,0 +1,128 @@
+import React from 'react'
+import '@testing-library/jest-dom/extend-expect'
+import {render} from '@testing-library/react'
+import {ToggleSwitch} from '..'
+import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import userEvent from '@testing-library/user-event'
+
+const SWITCH_LABEL_TEXT = 'Switch label'
+
+behavesAsComponent({
+ Component: ToggleSwitch,
+ options: {skipAs: true}
+})
+checkExports('ToggleSwitch', {
+ default: ToggleSwitch
+})
+it('renders a switch that is turned off', () => {
+ const {getByLabelText} = render(
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+})
+it('renders a switch that is turned on', () => {
+ const {getByLabelText} = render(
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'true')
+})
+it('renders a switch that is disabled', () => {
+ const {getByLabelText} = render(
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+
+ expect(toggleSwitch).toHaveAttribute('aria-disabled', 'true')
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+ userEvent.click(toggleSwitch)
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+})
+it("renders a switch who's state is loading", () => {
+ const {getByLabelText, container} = render(
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+ const loadingSpinner = container.querySelector('svg')
+
+ expect(loadingSpinner).toBeDefined()
+ expect(toggleSwitch).toHaveAttribute('aria-disabled', 'true')
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+ userEvent.click(toggleSwitch)
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+})
+it('switches from off to on uncontrolled', () => {
+ const {getByLabelText} = render(
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+ userEvent.click(toggleSwitch)
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'true')
+})
+it('switches from off to on with a controlled prop', () => {
+ const ControlledSwitchComponent = () => {
+ const [isOn, setIsOn] = React.useState(false)
+
+ const onClick = () => {
+ setIsOn(!isOn)
+ }
+
+ return (
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ }
+ const {getByLabelText} = render( )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'false')
+ userEvent.click(toggleSwitch)
+ expect(toggleSwitch).toHaveAttribute('aria-checked', 'true')
+})
+it('calls onChange when the switch is toggled', () => {
+ const handleChange = jest.fn()
+ const ControlledSwitchComponent = ({handleSwitchChange}: {handleSwitchChange: (on: boolean) => void}) => {
+ const [isOn, setIsOn] = React.useState(false)
+
+ const onClick = () => {
+ setIsOn(!isOn)
+ }
+
+ return (
+ <>
+ {SWITCH_LABEL_TEXT}
+
+ >
+ )
+ }
+ const {getByLabelText} = render( )
+ const toggleSwitch = getByLabelText(SWITCH_LABEL_TEXT)
+
+ userEvent.click(toggleSwitch)
+ expect(handleChange).toHaveBeenCalledWith(true)
+})
+
+checkStoriesForAxeViolations('Switch/fixtures')
+checkStoriesForAxeViolations('Switch/examples')
diff --git a/src/__tests__/__snapshots__/ToggleSwitch.test.tsx.snap b/src/__tests__/__snapshots__/ToggleSwitch.test.tsx.snap
new file mode 100644
index 00000000000..ef3575ba286
--- /dev/null
+++ b/src/__tests__/__snapshots__/ToggleSwitch.test.tsx.snap
@@ -0,0 +1,305 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders consistently 1`] = `
+.c2 {
+ text-align: right;
+ visibility: hidden;
+ height: 0;
+}
+
+.c3 {
+ text-align: right;
+}
+
+.c6 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+
+.c7 {
+ color: #0969da;
+ line-height: 0;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ -webkit-flex-basis: 50%;
+ -ms-flex-preferred-size: 50%;
+ flex-basis: 50%;
+ -webkit-transform: translateX(-100%);
+ -ms-transform: translateX(-100%);
+ transform: translateX(-100%);
+ -webkit-transition-property: -webkit-transform;
+ -webkit-transition-property: transform;
+ transition-property: transform;
+ -webkit-transition-duration: 80ms;
+ transition-duration: 80ms;
+}
+
+.c8 {
+ color: #24292f;
+ line-height: 0;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ -webkit-flex-basis: 50%;
+ -ms-flex-preferred-size: 50%;
+ flex-basis: 50%;
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0);
+ -webkit-transition-property: -webkit-transform;
+ -webkit-transition-property: transform;
+ transition-property: transform;
+ -webkit-transition-duration: 80ms;
+ transition-duration: 80ms;
+}
+
+.c0 {
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+}
+
+.c5 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ -webkit-clip: rect(0,0,0,0);
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.c1 {
+ font-size: 14px;
+ color: #24292f;
+ margin-left: 8px;
+ margin-right: 8px;
+ position: relative;
+}
+
+.c4 {
+ vertical-align: middle;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ padding: 0;
+ -webkit-transition-property: background-color,border-color;
+ transition-property: background-color,border-color;
+ -webkit-transition-duration: 80ms;
+ transition-duration: 80ms;
+ -webkit-transition-timing-function: cubic-bezier(0.5,1,0.89,1);
+ transition-timing-function: cubic-bezier(0.5,1,0.89,1);
+ border-radius: 6px;
+ border-style: solid;
+ border-width: 1px;
+ display: block;
+ height: 32px;
+ width: 64px;
+ outline-offset: 2px;
+ position: relative;
+ background-color: #eaeef2;
+ border-color: #afb8c1;
+}
+
+.c4:after {
+ content: '';
+ box-sizing: border-box;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: calc(6px - 1px);
+}
+
+.c4:hover .Toggle-knob,
+.c4:focus:focus-visible .Toggle-knob {
+ background-color: #f3f4f6;
+}
+
+.c4:active .Toggle-knob,
+.c4:active:focus-visible .Toggle-knob {
+ background-color: hsla(220,14%,93%,1);
+}
+
+.c9 {
+ background-color: #f6f8fa;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #afb8c1;
+ border-radius: calc(6px - 1px);
+ box-shadow: 0 3px 6px rgba(140,149,159,0.15),inset 0 1px 0 rgba(255,255,255,0.25);
+ width: 50%;
+ position: absolute;
+ top: -1px;
+ bottom: -1px;
+ -webkit-transition-property: -webkit-transform;
+ -webkit-transition-property: transform;
+ transition-property: transform;
+ -webkit-transition-duration: 80ms;
+ transition-duration: 80ms;
+ -webkit-transition-timing-function: cubic-bezier(0.5,1,0.89,1);
+ transition-timing-function: cubic-bezier(0.5,1,0.89,1);
+ -webkit-transform: translateX(-1px);
+ -ms-transform: translateX(-1px);
+ transform: translateX(-1px);
+ z-index: 1;
+}
+
+@media (pointer:coarse) {
+ .c4:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ right: 0;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ top: 50%;
+ min-height: 44px;
+ }
+}
+
+@media (prefers-reduced-motion) {
+ .c4 {
+ -webkit-transition: none;
+ transition: none;
+ }
+
+ .c4 * {
+ -webkit-transition: none;
+ transition: none;
+ }
+}
+
+@media (prefers-reduced-motion) {
+ .c9 {
+ -webkit-transition: none;
+ transition: none;
+ }
+}
+
+
+
+
+ On
+
+
+ Off
+
+
+
+
+ Off
+
+
+
+
+
+`;
diff --git a/src/__tests__/__snapshots__/themePreval.test.ts.snap b/src/__tests__/__snapshots__/themePreval.test.ts.snap
index fb64ee82db7..40363654676 100644
--- a/src/__tests__/__snapshots__/themePreval.test.ts.snap
+++ b/src/__tests__/__snapshots__/themePreval.test.ts.snap
@@ -3,7 +3,7 @@
exports[`snapshot theme-preval.js 1`] = `
"// this file was prevaled
// This file needs to be a JavaScript file using CommonJS to be compatible with preval
-// Cache bust: 2022-03-14 12:00:00 GMT (This file is cached by our deployment tooling, update this timestamp to rebuild this file)
+// Cache bust: 2022-03-24 12:00:00 GMT (This file is cached by our deployment tooling, update this timestamp to rebuild this file)
module.exports = {
\\"theme\\": {
\\"animation\\": {
@@ -353,6 +353,22 @@ module.exports = {
\\"hoverText\\": \\"#cf222e\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#eaeef2\\",
+ \\"border\\": \\"#afb8c1\\",
+ \\"checked\\": {
+ \\"bg\\": \\"#ddf4ff\\",
+ \\"hoverBg\\": \\"#b6e3ff\\",
+ \\"activeBg\\": \\"#80ccff\\",
+ \\"border\\": \\"#54aeff\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#0969da\\",
+ \\"disabledBg\\": \\"#6e7781\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#24292f\\",
\\"muted\\": \\"#57606a\\",
@@ -818,6 +834,22 @@ module.exports = {
\\"hoverText\\": \\"#ffffff\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#ffffff\\",
+ \\"border\\": \\"#20252C\\",
+ \\"checked\\": {
+ \\"bg\\": \\"#dff7ff\\",
+ \\"hoverBg\\": \\"#9cd7ff\\",
+ \\"activeBg\\": \\"#67b3fd\\",
+ \\"border\\": \\"#0349b4\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#0349b4\\",
+ \\"disabledBg\\": \\"#66707B\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#0E1116\\",
\\"muted\\": \\"#0E1116\\",
@@ -1283,6 +1315,22 @@ module.exports = {
\\"hoverText\\": \\"#b35900\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#eaeef2\\",
+ \\"border\\": \\"#afb8c1\\",
+ \\"checked\\": {
+ \\"bg\\": \\"#ddf4ff\\",
+ \\"hoverBg\\": \\"#b6e3ff\\",
+ \\"activeBg\\": \\"#80ccff\\",
+ \\"border\\": \\"#54aeff\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#0969da\\",
+ \\"disabledBg\\": \\"#6e7781\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#24292f\\",
\\"muted\\": \\"#57606a\\",
@@ -1508,7 +1556,7 @@ module.exports = {
\\"bg\\": \\"#0d1117\\",
\\"guttersBg\\": \\"#0d1117\\",
\\"guttermarkerText\\": \\"#0d1117\\",
- \\"guttermarkerSubtleText\\": \\"#484f58\\",
+ \\"guttermarkerSubtleText\\": \\"#6e7681\\",
\\"linenumberText\\": \\"#8b949e\\",
\\"cursor\\": \\"#c9d1d9\\",
\\"selectionBg\\": \\"rgba(56,139,253,0.4)\\",
@@ -1535,7 +1583,7 @@ module.exports = {
\\"btnHoverIcon\\": \\"#c9d1d9\\",
\\"btnHoverBg\\": \\"rgba(110,118,129,0.1)\\",
\\"inputText\\": \\"#8b949e\\",
- \\"inputPlaceholderText\\": \\"#484f58\\",
+ \\"inputPlaceholderText\\": \\"#6e7681\\",
\\"inputFocusText\\": \\"#c9d1d9\\",
\\"inputBg\\": \\"#161b22\\",
\\"donutError\\": \\"#f85149\\",
@@ -1556,8 +1604,8 @@ module.exports = {
\\"headerBorder\\": \\"#21262d\\",
\\"headerIcon\\": \\"#8b949e\\",
\\"lineText\\": \\"#8b949e\\",
- \\"lineNumText\\": \\"#484f58\\",
- \\"lineTimestampText\\": \\"#484f58\\",
+ \\"lineNumText\\": \\"#6e7681\\",
+ \\"lineTimestampText\\": \\"#6e7681\\",
\\"lineHoverBg\\": \\"rgba(110,118,129,0.1)\\",
\\"lineSelectedBg\\": \\"rgba(56,139,253,0.15)\\",
\\"lineSelectedNumText\\": \\"#58a6ff\\",
@@ -1570,10 +1618,10 @@ module.exports = {
\\"stepErrorText\\": \\"#f85149\\",
\\"stepWarningText\\": \\"#d29922\\",
\\"loglineText\\": \\"#8b949e\\",
- \\"loglineNumText\\": \\"#484f58\\",
+ \\"loglineNumText\\": \\"#6e7681\\",
\\"loglineDebugText\\": \\"#a371f7\\",
\\"loglineErrorText\\": \\"#8b949e\\",
- \\"loglineErrorNumText\\": \\"#484f58\\",
+ \\"loglineErrorNumText\\": \\"#6e7681\\",
\\"loglineErrorBg\\": \\"rgba(248,81,73,0.15)\\",
\\"loglineWarningText\\": \\"#8b949e\\",
\\"loglineWarningNumText\\": \\"#d29922\\",
@@ -1730,7 +1778,7 @@ module.exports = {
}
},
\\"underlinenav\\": {
- \\"icon\\": \\"#484f58\\",
+ \\"icon\\": \\"#6e7681\\",
\\"borderHover\\": \\"rgba(110,118,129,0.4)\\"
},
\\"actionListItem\\": {
@@ -1748,10 +1796,26 @@ module.exports = {
\\"hoverText\\": \\"#ff7b72\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#010409\\",
+ \\"border\\": \\"#6e7681\\",
+ \\"checked\\": {
+ \\"bg\\": \\"rgba(31,111,235,0.35)\\",
+ \\"hoverBg\\": \\"rgba(31,111,235,0.5)\\",
+ \\"activeBg\\": \\"rgba(31,111,235,0.65)\\",
+ \\"border\\": \\"#58a6ff\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#1f6feb\\",
+ \\"disabledBg\\": \\"#484f58\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#c9d1d9\\",
\\"muted\\": \\"#8b949e\\",
- \\"subtle\\": \\"#484f58\\",
+ \\"subtle\\": \\"#6e7681\\",
\\"onEmphasis\\": \\"#ffffff\\"
},
\\"canvas\\": {
@@ -1976,7 +2040,7 @@ module.exports = {
\\"bg\\": \\"#22272e\\",
\\"guttersBg\\": \\"#22272e\\",
\\"guttermarkerText\\": \\"#22272e\\",
- \\"guttermarkerSubtleText\\": \\"#545d68\\",
+ \\"guttermarkerSubtleText\\": \\"#636e7b\\",
\\"linenumberText\\": \\"#768390\\",
\\"cursor\\": \\"#adbac7\\",
\\"selectionBg\\": \\"rgba(65,132,228,0.4)\\",
@@ -2003,7 +2067,7 @@ module.exports = {
\\"btnHoverIcon\\": \\"#adbac7\\",
\\"btnHoverBg\\": \\"rgba(99,110,123,0.1)\\",
\\"inputText\\": \\"#768390\\",
- \\"inputPlaceholderText\\": \\"#545d68\\",
+ \\"inputPlaceholderText\\": \\"#636e7b\\",
\\"inputFocusText\\": \\"#adbac7\\",
\\"inputBg\\": \\"#2d333b\\",
\\"donutError\\": \\"#e5534b\\",
@@ -2024,8 +2088,8 @@ module.exports = {
\\"headerBorder\\": \\"#373e47\\",
\\"headerIcon\\": \\"#768390\\",
\\"lineText\\": \\"#768390\\",
- \\"lineNumText\\": \\"#545d68\\",
- \\"lineTimestampText\\": \\"#545d68\\",
+ \\"lineNumText\\": \\"#636e7b\\",
+ \\"lineTimestampText\\": \\"#636e7b\\",
\\"lineHoverBg\\": \\"rgba(99,110,123,0.1)\\",
\\"lineSelectedBg\\": \\"rgba(65,132,228,0.15)\\",
\\"lineSelectedNumText\\": \\"#539bf5\\",
@@ -2038,10 +2102,10 @@ module.exports = {
\\"stepErrorText\\": \\"#e5534b\\",
\\"stepWarningText\\": \\"#c69026\\",
\\"loglineText\\": \\"#768390\\",
- \\"loglineNumText\\": \\"#545d68\\",
+ \\"loglineNumText\\": \\"#636e7b\\",
\\"loglineDebugText\\": \\"#986ee2\\",
\\"loglineErrorText\\": \\"#768390\\",
- \\"loglineErrorNumText\\": \\"#545d68\\",
+ \\"loglineErrorNumText\\": \\"#636e7b\\",
\\"loglineErrorBg\\": \\"rgba(229,83,75,0.15)\\",
\\"loglineWarningText\\": \\"#768390\\",
\\"loglineWarningNumText\\": \\"#c69026\\",
@@ -2198,7 +2262,7 @@ module.exports = {
}
},
\\"underlinenav\\": {
- \\"icon\\": \\"#545d68\\",
+ \\"icon\\": \\"#636e7b\\",
\\"borderHover\\": \\"rgba(99,110,123,0.4)\\"
},
\\"actionListItem\\": {
@@ -2216,10 +2280,26 @@ module.exports = {
\\"hoverText\\": \\"#f47067\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#1c2128\\",
+ \\"border\\": \\"#636e7b\\",
+ \\"checked\\": {
+ \\"bg\\": \\"rgba(49,109,202,0.35)\\",
+ \\"hoverBg\\": \\"rgba(49,109,202,0.5)\\",
+ \\"activeBg\\": \\"rgba(49,109,202,0.65)\\",
+ \\"border\\": \\"#539bf5\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#316dca\\",
+ \\"disabledBg\\": \\"#545d68\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#adbac7\\",
\\"muted\\": \\"#768390\\",
- \\"subtle\\": \\"#545d68\\",
+ \\"subtle\\": \\"#636e7b\\",
\\"onEmphasis\\": \\"#cdd9e5\\"
},
\\"canvas\\": {
@@ -2444,7 +2524,7 @@ module.exports = {
\\"bg\\": \\"#0a0c10\\",
\\"guttersBg\\": \\"#0a0c10\\",
\\"guttermarkerText\\": \\"#0a0c10\\",
- \\"guttermarkerSubtleText\\": \\"#7a828e\\",
+ \\"guttermarkerSubtleText\\": \\"#9ea7b3\\",
\\"linenumberText\\": \\"#f0f3f6\\",
\\"cursor\\": \\"#f0f3f6\\",
\\"selectionBg\\": \\"rgba(64,158,255,0.4)\\",
@@ -2471,7 +2551,7 @@ module.exports = {
\\"btnHoverIcon\\": \\"#f0f3f6\\",
\\"btnHoverBg\\": \\"rgba(158,167,179,0.1)\\",
\\"inputText\\": \\"#f0f3f6\\",
- \\"inputPlaceholderText\\": \\"#7a828e\\",
+ \\"inputPlaceholderText\\": \\"#9ea7b3\\",
\\"inputFocusText\\": \\"#f0f3f6\\",
\\"inputBg\\": \\"#272b33\\",
\\"donutError\\": \\"#ff6a69\\",
@@ -2492,8 +2572,8 @@ module.exports = {
\\"headerBorder\\": \\"#7a828e\\",
\\"headerIcon\\": \\"#f0f3f6\\",
\\"lineText\\": \\"#f0f3f6\\",
- \\"lineNumText\\": \\"#7a828e\\",
- \\"lineTimestampText\\": \\"#7a828e\\",
+ \\"lineNumText\\": \\"#9ea7b3\\",
+ \\"lineTimestampText\\": \\"#9ea7b3\\",
\\"lineHoverBg\\": \\"rgba(158,167,179,0.1)\\",
\\"lineSelectedBg\\": \\"rgba(64,158,255,0.15)\\",
\\"lineSelectedNumText\\": \\"#71b7ff\\",
@@ -2506,10 +2586,10 @@ module.exports = {
\\"stepErrorText\\": \\"#ff6a69\\",
\\"stepWarningText\\": \\"#f0b72f\\",
\\"loglineText\\": \\"#f0f3f6\\",
- \\"loglineNumText\\": \\"#7a828e\\",
+ \\"loglineNumText\\": \\"#9ea7b3\\",
\\"loglineDebugText\\": \\"#b780ff\\",
\\"loglineErrorText\\": \\"#f0f3f6\\",
- \\"loglineErrorNumText\\": \\"#7a828e\\",
+ \\"loglineErrorNumText\\": \\"#9ea7b3\\",
\\"loglineErrorBg\\": \\"rgba(255,106,105,0.15)\\",
\\"loglineWarningText\\": \\"#f0f3f6\\",
\\"loglineWarningNumText\\": \\"#f0b72f\\",
@@ -2684,10 +2764,26 @@ module.exports = {
\\"hoverText\\": \\"#0a0c10\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#010409\\",
+ \\"border\\": \\"#7a828e\\",
+ \\"checked\\": {
+ \\"bg\\": \\"rgba(64,158,255,0.35)\\",
+ \\"hoverBg\\": \\"rgba(64,158,255,0.5)\\",
+ \\"activeBg\\": \\"rgba(64,158,255,0.65)\\",
+ \\"border\\": \\"#409eff\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#409eff\\",
+ \\"disabledBg\\": \\"#7a828e\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#f0f3f6\\",
\\"muted\\": \\"#f0f3f6\\",
- \\"subtle\\": \\"#7a828e\\",
+ \\"subtle\\": \\"#9ea7b3\\",
\\"onEmphasis\\": \\"#0a0c10\\"
},
\\"canvas\\": {
@@ -2912,7 +3008,7 @@ module.exports = {
\\"bg\\": \\"#0d1117\\",
\\"guttersBg\\": \\"#0d1117\\",
\\"guttermarkerText\\": \\"#0d1117\\",
- \\"guttermarkerSubtleText\\": \\"#484f58\\",
+ \\"guttermarkerSubtleText\\": \\"#6e7681\\",
\\"linenumberText\\": \\"#8b949e\\",
\\"cursor\\": \\"#c9d1d9\\",
\\"selectionBg\\": \\"rgba(56,139,253,0.4)\\",
@@ -2939,7 +3035,7 @@ module.exports = {
\\"btnHoverIcon\\": \\"#c9d1d9\\",
\\"btnHoverBg\\": \\"rgba(110,118,129,0.1)\\",
\\"inputText\\": \\"#8b949e\\",
- \\"inputPlaceholderText\\": \\"#484f58\\",
+ \\"inputPlaceholderText\\": \\"#6e7681\\",
\\"inputFocusText\\": \\"#c9d1d9\\",
\\"inputBg\\": \\"#161b22\\",
\\"donutError\\": \\"#d47616\\",
@@ -2960,8 +3056,8 @@ module.exports = {
\\"headerBorder\\": \\"#21262d\\",
\\"headerIcon\\": \\"#8b949e\\",
\\"lineText\\": \\"#8b949e\\",
- \\"lineNumText\\": \\"#484f58\\",
- \\"lineTimestampText\\": \\"#484f58\\",
+ \\"lineNumText\\": \\"#6e7681\\",
+ \\"lineTimestampText\\": \\"#6e7681\\",
\\"lineHoverBg\\": \\"rgba(110,118,129,0.1)\\",
\\"lineSelectedBg\\": \\"rgba(56,139,253,0.15)\\",
\\"lineSelectedNumText\\": \\"#58a6ff\\",
@@ -2974,10 +3070,10 @@ module.exports = {
\\"stepErrorText\\": \\"#d47616\\",
\\"stepWarningText\\": \\"#d29922\\",
\\"loglineText\\": \\"#8b949e\\",
- \\"loglineNumText\\": \\"#484f58\\",
+ \\"loglineNumText\\": \\"#6e7681\\",
\\"loglineDebugText\\": \\"#a371f7\\",
\\"loglineErrorText\\": \\"#8b949e\\",
- \\"loglineErrorNumText\\": \\"#484f58\\",
+ \\"loglineErrorNumText\\": \\"#6e7681\\",
\\"loglineErrorBg\\": \\"rgba(212,118,22,0.15)\\",
\\"loglineWarningText\\": \\"#8b949e\\",
\\"loglineWarningNumText\\": \\"#d29922\\",
@@ -3134,7 +3230,7 @@ module.exports = {
}
},
\\"underlinenav\\": {
- \\"icon\\": \\"#484f58\\",
+ \\"icon\\": \\"#6e7681\\",
\\"borderHover\\": \\"rgba(110,118,129,0.4)\\"
},
\\"actionListItem\\": {
@@ -3152,10 +3248,26 @@ module.exports = {
\\"hoverText\\": \\"#ec8e2c\\"
}
},
+ \\"switchTrack\\": {
+ \\"bg\\": \\"#010409\\",
+ \\"border\\": \\"#6e7681\\",
+ \\"checked\\": {
+ \\"bg\\": \\"rgba(31,111,235,0.35)\\",
+ \\"hoverBg\\": \\"rgba(31,111,235,0.5)\\",
+ \\"activeBg\\": \\"rgba(31,111,235,0.65)\\",
+ \\"border\\": \\"#58a6ff\\"
+ }
+ },
+ \\"switchKnob\\": {
+ \\"checked\\": {
+ \\"bg\\": \\"#1f6feb\\",
+ \\"disabledBg\\": \\"#484f58\\"
+ }
+ },
\\"fg\\": {
\\"default\\": \\"#c9d1d9\\",
\\"muted\\": \\"#8b949e\\",
- \\"subtle\\": \\"#484f58\\",
+ \\"subtle\\": \\"#6e7681\\",
\\"onEmphasis\\": \\"#ffffff\\"
},
\\"canvas\\": {
diff --git a/src/hooks/useOverlay.tsx b/src/hooks/useOverlay.tsx
index ef8ecf3010a..c4d5c367d39 100644
--- a/src/hooks/useOverlay.tsx
+++ b/src/hooks/useOverlay.tsx
@@ -31,10 +31,10 @@ export const useOverlay = ({
useOnOutsideClick({containerRef: overlayRef, ignoreClickRefs, onClickOutside})
// We only want one overlay to close at a time
- const preventedDefaultOnEscape: UseOverlaySettings['onEscape'] = event => {
+ const preventeddefaultCheckedEscape: UseOverlaySettings['onEscape'] = event => {
onEscape(event)
event.preventDefault()
}
- useOnEscapePress(preventedDefaultOnEscape)
+ useOnEscapePress(preventeddefaultCheckedEscape)
return {ref: overlayRef}
}
diff --git a/src/index.ts b/src/index.ts
index 43a8481f2d6..f4ce005e4f7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -124,6 +124,7 @@ export {default as StyledOcticon} from './StyledOcticon'
export type {StyledOcticonProps} from './StyledOcticon'
export {default as SubNav} from './SubNav'
export type {SubNavProps, SubNavLinkProps, SubNavLinksProps} from './SubNav'
+export {default as ToggleSwitch} from './ToggleSwitch'
export {default as TabNav} from './TabNav'
export type {TabNavProps, TabNavLinkProps} from './TabNav'
export {default as TextInput} from './TextInput'
diff --git a/src/stories/Switch/examples.stories.tsx b/src/stories/Switch/examples.stories.tsx
new file mode 100644
index 00000000000..c8d6003ee15
--- /dev/null
+++ b/src/stories/Switch/examples.stories.tsx
@@ -0,0 +1,113 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+
+import {BaseStyles, Box, ToggleSwitch, Text, ThemeProvider} from '../../'
+import {ComponentProps} from '../../utils/types'
+import {action} from '@storybook/addon-actions'
+
+type Args = ComponentProps
+
+const excludedControlKeys = [
+ 'aria-describedby',
+ 'aria-labelledby',
+ 'defaultChecked',
+ 'onChange',
+ 'onClick',
+ 'statusLabelPosition',
+ 'sx'
+]
+
+export default {
+ title: 'ToggleSwitch/examples',
+ component: ToggleSwitch,
+ argTypes: {
+ on: {
+ defaultValue: undefined,
+ control: {
+ type: 'boolean'
+ }
+ },
+ disabled: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ loading: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ size: {
+ defaultValue: 'medium',
+ control: {
+ type: 'radio',
+ options: ['small', 'medium']
+ }
+ }
+ },
+ parameters: {controls: {exclude: excludedControlKeys}},
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const Default = (args: Args) => (
+
+
+ Notifications
+
+
+
+)
+Default.storyName = 'Default (uncontrolled)'
+
+export const Controlled = (args: Args) => {
+ const [isOn, setIsOn] = React.useState(false)
+
+ const onClick = React.useCallback(() => {
+ setIsOn(!isOn)
+ }, [setIsOn, isOn])
+
+ const handleSwitchChange = (on: boolean) => {
+ action(`new switch "on" state: ${on}`)
+ }
+
+ return (
+ <>
+
+
+ Notifications
+
+
+
+ The switch is {isOn ? 'on' : 'off'}
+ >
+ )
+}
+Controlled.parameters = {controls: {exclude: [...excludedControlKeys, 'on']}}
+
+export const StatusLabelPositionedAtEnd = (args: Args) => (
+ <>
+
+ Notifications
+
+
+ >
+)
+StatusLabelPositionedAtEnd.storyName = 'statusLabelPosition="end"'
diff --git a/src/stories/Switch/fixtures.stories.tsx b/src/stories/Switch/fixtures.stories.tsx
new file mode 100644
index 00000000000..bf6b27049be
--- /dev/null
+++ b/src/stories/Switch/fixtures.stories.tsx
@@ -0,0 +1,78 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+
+import {BaseStyles, Box, ToggleSwitch, Text, ThemeProvider} from '../../'
+import {ComponentProps} from '../../utils/types'
+
+type Args = ComponentProps
+
+export default {
+ title: 'ToggleSwitch/fixtrues',
+ component: ToggleSwitch,
+ argTypes: {
+ on: {
+ defaultValue: undefined,
+ control: {
+ type: 'boolean'
+ }
+ },
+ disabled: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ loading: {
+ defaultValue: false,
+ control: {
+ type: 'boolean'
+ }
+ },
+ size: {
+ control: {
+ type: 'radio',
+ options: ['small', 'medium']
+ }
+ }
+ },
+ parameters: {
+ controls: {
+ exclude: ['aria-describedby', 'aria-labelledby', 'defaultChecked', 'onChange', 'onClick', 'statusLabelPosition']
+ }
+ },
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const Small = (args: Args) => (
+ <>
+
+ Notifications
+
+
+ >
+)
+
+export const WithCaption = (args: Args) => (
+
+
+
+ Notifications
+
+
+ Notifications will be delivered via email and the GitHub notification center
+
+
+
+
+)
+WithCaption.storyName = 'Associated with a caption'
diff --git a/src/theme-preval.js b/src/theme-preval.js
index 2f3ce2b7517..7f2222dd0d5 100644
--- a/src/theme-preval.js
+++ b/src/theme-preval.js
@@ -1,6 +1,6 @@
// @preval
// This file needs to be a JavaScript file using CommonJS to be compatible with preval
-// Cache bust: 2022-03-14 12:00:00 GMT (This file is cached by our deployment tooling, update this timestamp to rebuild this file)
+// Cache bust: 2022-03-24 12:00:00 GMT (This file is cached by our deployment tooling, update this timestamp to rebuild this file)
const {default: primitives} = require('@primer/primitives')
const {partitionColors, fontStack, omitScale} = require('./utils/theme')