Skip to content

Commit 12ff7ad

Browse files
feat: adds initial toggle component
1 parent d60572d commit 12ff7ad

File tree

7 files changed

+343
-0
lines changed

7 files changed

+343
-0
lines changed

package-lock.json

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@
168168
"@radix-ui/react-switch": "0.1.5",
169169
"@radix-ui/react-tabs": "0.1.5",
170170
"@radix-ui/react-toast": "0.1.1",
171+
"@radix-ui/react-toggle": "0.1.4",
172+
"@radix-ui/react-toggle-group": "0.1.5",
171173
"@radix-ui/react-tooltip": "0.1.7",
172174
"@radix-ui/react-visually-hidden": "^0.1.4",
173175
"@stitches/react": "^1.2.7"

src/components/Icons/Icons.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
mdiChevronRight,
77
mdiChevronUp,
88
mdiClose,
9+
mdiFormatItalic,
910
} from '@mdi/js'
1011
import React, { ComponentProps, FC } from 'react'
1112
import type { CSS } from '../../stitches.config'
@@ -49,3 +50,7 @@ export const LightMode: IconType = (props) => (
4950
export const DarkMode: IconType = (props) => (
5051
<Svg path={mdiBrightness3} {...props} />
5152
)
53+
54+
export const Italic: IconType = (props) => (
55+
<Svg path={mdiFormatItalic} {...props} />
56+
)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { ComponentStory, Meta, Story } from '@storybook/react'
2+
import React from 'react'
3+
import { Toggle } from '.'
4+
import { Column, Row } from '../'
5+
import { Italic } from '../Icons'
6+
7+
export default {
8+
title: 'Components/Toggle',
9+
component: Toggle,
10+
} as Meta
11+
12+
const Template: ComponentStory<typeof Toggle> = ({ ...args }) => {
13+
return (
14+
<Toggle aria-label="Toggle italic">
15+
<Italic />
16+
</Toggle>
17+
)
18+
}
19+
20+
export const Default = Template.bind({})
21+
22+
export const Variants: Story = () => (
23+
<Row css={{ gap: '$3' }}>
24+
<Toggle variant="primary">
25+
<Italic />
26+
</Toggle>
27+
<Toggle variant="secondary">
28+
<Italic />
29+
</Toggle>
30+
<Toggle variant="tertiary">
31+
<Italic />
32+
</Toggle>
33+
</Row>
34+
)
35+
36+
export const Disabled: Story = () => {
37+
return (
38+
<Toggle aria-label="Toggle italic" variant="primary" disabled>
39+
<Italic />
40+
</Toggle>
41+
)
42+
}
43+
44+
/**
45+
* This uses the force prop to simulate hover and focus states so they can be compared at the same time.
46+
* This prop is not intended for normal use and the toggles here will not interact normally.
47+
*/
48+
export const States: Story = () => (
49+
<Column css={{ gap: '$3' }}>
50+
<Row css={{ gap: '$3' }}>
51+
<Toggle variant="primary">
52+
<Italic />
53+
</Toggle>
54+
<Toggle variant="primary" force="hover">
55+
<Italic />
56+
</Toggle>
57+
<Toggle variant="primary" force="focus">
58+
<Italic />
59+
</Toggle>
60+
<Toggle variant="primary" pressed>
61+
<Italic />
62+
</Toggle>
63+
<Toggle variant="secondary" pressed force="hover">
64+
<Italic />
65+
</Toggle>
66+
<Toggle variant="secondary" pressed force="focus">
67+
<Italic />
68+
</Toggle>
69+
<Toggle variant="primary" disabled>
70+
<Italic />
71+
</Toggle>
72+
</Row>
73+
<Row css={{ gap: '$3' }}>
74+
<Toggle variant="secondary">
75+
<Italic />
76+
</Toggle>
77+
<Toggle variant="secondary" force="hover">
78+
<Italic />
79+
</Toggle>
80+
<Toggle variant="secondary" force="focus">
81+
<Italic />
82+
</Toggle>
83+
<Toggle variant="secondary" pressed>
84+
<Italic />
85+
</Toggle>
86+
<Toggle variant="secondary" pressed force="hover">
87+
<Italic />
88+
</Toggle>
89+
<Toggle variant="secondary" pressed force="focus">
90+
<Italic />
91+
</Toggle>
92+
<Toggle variant="secondary" disabled>
93+
<Italic />
94+
</Toggle>
95+
</Row>
96+
<Row css={{ gap: '$3' }}>
97+
<Toggle variant="tertiary">
98+
<Italic />
99+
</Toggle>
100+
<Toggle variant="tertiary" force="hover">
101+
<Italic />
102+
</Toggle>
103+
<Toggle variant="tertiary" force="focus">
104+
<Italic />
105+
</Toggle>
106+
<Toggle variant="tertiary" pressed>
107+
<Italic />
108+
</Toggle>
109+
<Toggle variant="tertiary" pressed force="hover">
110+
<Italic />
111+
</Toggle>
112+
<Toggle variant="tertiary" pressed force="focus">
113+
<Italic />
114+
</Toggle>
115+
<Toggle variant="tertiary" disabled>
116+
<Italic />
117+
</Toggle>
118+
</Row>
119+
</Column>
120+
)

src/components/Toggle/Toggle.tsx

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import * as TogglePrimitive from '@radix-ui/react-toggle'
2+
import { styled } from '@stitches/react'
3+
4+
const hover = {
5+
background:
6+
'$$hover radial-gradient(circle, transparent 1%, $$hover 1%) center/15000%',
7+
}
8+
9+
export const disabled = {
10+
pointerEvents: 'none',
11+
$$main: '$$lowlight',
12+
opacity: 0.4,
13+
}
14+
15+
export const noFocus = {
16+
$$focusColor: 'transparent',
17+
$$onFocusColor: 'transparent',
18+
}
19+
20+
export const selected = {
21+
backgroundColor: '$$onBackgroundColor',
22+
color: '$$onColor',
23+
$$focusColor: '$$onFocusColor',
24+
}
25+
26+
const focus = { borderColor: '$$focusColor', border: 'solid 2px' }
27+
28+
// TODO: pressed focus not working as intended
29+
const StyledToggle = styled(TogglePrimitive.Root, {
30+
$$main: '$colors$primary',
31+
$$mainHover: '$colors$primaryHighlight',
32+
$$contrast: '$colors$primaryContrast',
33+
$$active: '$colors$defaultActive',
34+
$$default: '$colors$default',
35+
$$defaultHover: '$colors$defaultHighlight',
36+
$$lowlight: '$colors$defaultLowlight',
37+
$$onBackgroundColor: '$color$defaultHighlight',
38+
$$onColor: '$$main',
39+
$$focusColor: '$$default',
40+
$$onFocusColor: '$$contrast',
41+
42+
// Reset
43+
alignItems: 'center',
44+
appearance: 'none',
45+
boxSizing: 'border-box',
46+
display: 'inline-flex',
47+
flexShrink: 0,
48+
justifyContent: 'center',
49+
lineHeight: '$none',
50+
margin: '0',
51+
outline: 'none',
52+
padding: '0',
53+
textDecoration: 'none',
54+
userSelect: 'none',
55+
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
56+
'&::before': {
57+
boxSizing: 'border-box',
58+
},
59+
'&::after': {
60+
boxSizing: 'border-box',
61+
},
62+
63+
// Defaults
64+
fontSize: '$0',
65+
borderRadius: '$default',
66+
cursor: 'pointer',
67+
backgroundColor: 'transparent',
68+
border: 'solid 2px',
69+
borderColor: 'transparent',
70+
71+
// Actions
72+
'@motion': {
73+
transition: 'background 0.5s',
74+
},
75+
backgroundPosition: 'center',
76+
77+
'&:hover': hover,
78+
'&:focus': focus,
79+
'&:disabled': disabled,
80+
'&:noFocus': noFocus,
81+
'&[data-state=on]': selected,
82+
83+
variants: {
84+
variant: {
85+
primary: {
86+
$$lowlight: '$colors$primaryLowlight',
87+
$$hover: '$$mainHover',
88+
backgroundColor: '$$main',
89+
color: '$$contrast',
90+
$$onBackgroundColor: '$$contrast',
91+
$$focusColor: 'transparent',
92+
$$onFocusColor: 'transparent',
93+
},
94+
secondary: {
95+
borderColor: '$$default',
96+
color: '$$default',
97+
$$hover: '$$defaultHover',
98+
$$onBackgroundColor: '$$contrast',
99+
$$focusColor: 'transparent',
100+
$$onFocusColor: 'transparent',
101+
},
102+
tertiary: {
103+
color: '$$default',
104+
$$hover: '$$defaultHover',
105+
$$onColor: '$$contrast',
106+
},
107+
},
108+
size: {
109+
small: {
110+
size: '$5',
111+
'& > svg': {
112+
size: '$4',
113+
},
114+
},
115+
default: {
116+
size: '$6',
117+
},
118+
large: {
119+
size: '$7',
120+
'& > svg': {
121+
size: '$6',
122+
},
123+
},
124+
},
125+
force: {
126+
hover,
127+
focus,
128+
},
129+
},
130+
131+
defaultVariants: {
132+
variant: 'secondary',
133+
size: 'default',
134+
},
135+
})
136+
137+
/**
138+
* Toggle Component
139+
*
140+
* Toggles are designed to wrap custom `Svg` icons much like an IconButton.
141+
*
142+
* Based on [Radix Toggle](https://www.radix-ui.com/docs/primitives/components/toggle).
143+
*/
144+
export const Toggle = StyledToggle
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react'
2+
import { renderDark, renderLight } from 'test-utils'
3+
import { Default } from './Toggle.stories'
4+
5+
it('renders light without error', () => {
6+
const { asFragment } = renderLight(<Default />)
7+
expect(asFragment()).toBeDefined()
8+
})
9+
10+
it('renders dark without error', () => {
11+
const { asFragment } = renderDark(<Default />)
12+
expect(asFragment()).toBeDefined()
13+
})

src/components/Toggle/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Toggle'

0 commit comments

Comments
 (0)