Skip to content

Commit 343cc4a

Browse files
authored
FE-683: Add toggle to ds (#8871)
1 parent acf5df0 commit 343cc4a

10 files changed

Lines changed: 493 additions & 22 deletions

File tree

.changeset/tame-hairs-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashintel/ds-components": patch
3+
---
4+
5+
Add Toggle component

libs/@hashintel/ds-components/src/components/Button/button.stories.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import { Fragment, useState } from "react";
22

33
import { css } from "@hashintel/ds-helpers/css";
44

5-
import { formInputSizes } from "../../util/form-shared";
5+
import { formInputSizes, type Tone } from "../../util/form-shared";
66
import { Icon, iconNames } from "../Icon/icon";
77
import {
88
Button as ButtonComponent,
99
type ButtonElementProps,
1010
iconSizeMap,
11-
type Tone,
1211
type Variant,
1312
} from "./button";
1413

libs/@hashintel/ds-components/src/components/Button/button.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import { LoadingSpinner } from "../Loading/loading-spinner";
66
import { Tooltip } from "../Tooltip/tooltip";
77
import { styles } from "./button.recipe";
88

9-
import type { FormInputSize } from "../../util/form-shared";
9+
import type { FormInputSize, Tone } from "../../util/form-shared";
1010
import type { ExclusifyUnion, RequireAtLeastOne } from "type-fest";
1111

1212
export type Variant = "solid" | "subtle" | "ghost" | "link" | "linkSubtle";
13-
export type Tone = "neutral" | "brand" | "error"; // success, warning, etc
1413

1514
type SharedButtonProps<Element extends HTMLButtonElement | HTMLAnchorElement> =
1615
{

libs/@hashintel/ds-components/src/components/Menu/SelectableList/selectable-list-util.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useMemo } from "react";
22

3+
import { type Tone } from "../../../util/form-shared";
34
import { type IconName } from "../../Icon/icon";
45

56
import type { UseMenuContext } from "@ark-ui/react/menu";
@@ -13,7 +14,7 @@ type ItemBase = {
1314

1415
indent?: number;
1516
disabled?: boolean;
16-
tone?: "neutral" | "brand" | "error";
17+
tone?: Tone;
1718
selectedStyle?: "tick" | "checkbox" | "highlight";
1819

1920
/**

libs/@hashintel/ds-components/src/components/Switch/switch.stories.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { sva } from "@hashintel/ds-helpers/css";
2+
3+
export const styles = sva({
4+
slots: ["root", "control", "thumb", "label"],
5+
base: {
6+
root: {
7+
display: "inline-flex",
8+
width: "[fit-content]",
9+
alignItems: "center",
10+
gap: "[8px]",
11+
cursor: "pointer",
12+
userSelect: "none",
13+
"&[data-disabled]": {
14+
opacity: "0.7",
15+
cursor: "unset",
16+
},
17+
},
18+
control: {
19+
position: "relative",
20+
display: "inline-flex",
21+
alignItems: "center",
22+
flexShrink: "0",
23+
width: "var(--toggle-width)",
24+
height: "var(--toggle-height)",
25+
padding: "var(--toggle-padding)",
26+
borderRadius: "full",
27+
border: "[1px solid]",
28+
borderColor: "black.a15",
29+
boxShadow: "[inset 0 2px 4px rgba(0, 0, 0, 0.05)]",
30+
outlineColor: "black.a40",
31+
transition:
32+
"[background-color 0.15s ease, outline 0.15s ease, box-shadow 0.15s ease]",
33+
"&:has(~ input:focus-visible)": {
34+
outline: "[2px solid var(--colors-black-a40)]",
35+
outlineOffset: "[2px]",
36+
},
37+
// On hover, subtly slide the thumb part-way towards the opposite state as
38+
// an affordance hinting that the toggle is interactive.
39+
"&[data-state='unchecked']:hover:not([data-disabled]) [data-part='thumb']":
40+
{
41+
transform:
42+
"[translateX(calc(var(--toggle-travel) * var(--toggle-hover-nudge)))]",
43+
},
44+
"&[data-state='checked']:hover:not([data-disabled]) [data-part='thumb']":
45+
{
46+
transform:
47+
"[translateX(calc(var(--toggle-travel) * (1 - var(--toggle-hover-nudge))))]",
48+
},
49+
},
50+
thumb: {
51+
"--toggle-travel":
52+
"[calc(var(--toggle-width) - var(--toggle-thumb-size) - var(--toggle-padding) * 2 - 2px)]",
53+
"--toggle-hover-nudge": "[0.07]",
54+
width: "var(--toggle-thumb-size)",
55+
height: "var(--toggle-thumb-size)",
56+
borderRadius: "full",
57+
backgroundColor: "white",
58+
boxShadow:
59+
"[0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)]",
60+
transition: "[transform 0.18s ease]",
61+
"&[data-state='checked']": {
62+
transform: "[translateX(var(--toggle-travel))]",
63+
},
64+
"&[data-disabled]": {
65+
backgroundColor: "neutral.s30 !important",
66+
},
67+
"&[data-disabled][data-state='unchecked']": {
68+
backgroundColor: "neutral.s25 !important",
69+
boxShadow:
70+
"[0 1px 3px 0 rgba(0, 0, 0, 0.15), 0 1px 2px -1px rgba(0, 0, 0, 0.15)]",
71+
},
72+
},
73+
label: {
74+
fontWeight: "medium",
75+
lineHeight: "[1]",
76+
color: "fg.heading",
77+
78+
"&[data-disabled]": {
79+
color: "neutral.s75",
80+
},
81+
},
82+
},
83+
variants: {
84+
size: {
85+
xxs: {
86+
control: {
87+
"--toggle-width": "[24px]",
88+
"--toggle-height": "[14px]",
89+
"--toggle-padding": "[2px]",
90+
"--toggle-thumb-size": "[10px]",
91+
},
92+
label: { fontSize: "[12px]" },
93+
},
94+
xs: {
95+
control: {
96+
"--toggle-width": "[28px]",
97+
"--toggle-height": "[16px]",
98+
"--toggle-padding": "[2px]",
99+
"--toggle-thumb-size": "[12px]",
100+
},
101+
label: { fontSize: "[12px]" },
102+
},
103+
sm: {
104+
control: {
105+
"--toggle-width": "[34px]",
106+
"--toggle-height": "[20px]",
107+
"--toggle-padding": "[2px]",
108+
"--toggle-thumb-size": "[14px]",
109+
},
110+
label: { fontSize: "[13px]" },
111+
},
112+
md: {
113+
control: {
114+
"--toggle-width": "[42px]",
115+
"--toggle-height": "[24px]",
116+
"--toggle-padding": "[3px]",
117+
"--toggle-thumb-size": "[18px]",
118+
},
119+
label: { fontSize: "[15px]" },
120+
},
121+
lg: {
122+
control: {
123+
"--toggle-width": "[48px]",
124+
"--toggle-height": "[28px]",
125+
"--toggle-padding": "[4px]",
126+
"--toggle-thumb-size": "[20px]",
127+
},
128+
label: { fontSize: "[17px]" },
129+
},
130+
},
131+
tone: {
132+
neutral: {
133+
control: {
134+
"&[data-state='checked']": {
135+
backgroundColor: "neutral.s115",
136+
},
137+
"&[data-state='checked']:hover:not([data-disabled])": {
138+
backgroundColor: "neutral.s110",
139+
},
140+
"&[data-disabled][data-state='checked']": {
141+
backgroundColor: "neutral.s55 !important",
142+
},
143+
},
144+
},
145+
brand: {
146+
control: {
147+
"&[data-state='checked']": {
148+
backgroundColor: "blue.s85",
149+
},
150+
"&[data-state='checked']:hover:not([data-disabled])": {
151+
backgroundColor: "blue.s75",
152+
},
153+
"&[data-disabled][data-state='checked']": {
154+
backgroundColor: "blue.s55 !important",
155+
},
156+
},
157+
},
158+
success: {
159+
control: {
160+
"&[data-state='checked']": {
161+
backgroundColor: "green.s80",
162+
},
163+
"&[data-state='checked']:hover:not([data-disabled])": {
164+
backgroundColor: "green.s70",
165+
},
166+
"&[data-disabled][data-state='checked']": {
167+
backgroundColor: "green.s55 !important",
168+
},
169+
},
170+
},
171+
},
172+
offTone: {
173+
neutral: {
174+
control: {
175+
"&[data-state='unchecked']": {
176+
backgroundColor: "neutral.s30",
177+
},
178+
"&[data-state='unchecked']:hover:not([data-disabled])": {
179+
backgroundColor: "neutral.s40",
180+
},
181+
"&[data-disabled][data-state='unchecked']": {
182+
backgroundColor: "neutral.s30 !important",
183+
},
184+
},
185+
},
186+
error: {
187+
control: {
188+
"&[data-state='unchecked']": {
189+
backgroundColor: "red.s40",
190+
},
191+
"&[data-state='unchecked']:hover:not([data-disabled])": {
192+
backgroundColor: "red.s50",
193+
},
194+
"&[data-disabled][data-state='unchecked']": {
195+
backgroundColor: "red.s30 !important",
196+
},
197+
},
198+
},
199+
},
200+
invalid: {
201+
true: {
202+
control: {
203+
borderColor: "red.s70",
204+
"&::after": {
205+
content: '""',
206+
position: "absolute",
207+
inset: "0",
208+
borderRadius: "[inherit]",
209+
border: "[1px solid]",
210+
borderColor: "red.s70",
211+
pointerEvents: "none",
212+
},
213+
},
214+
},
215+
false: {},
216+
},
217+
},
218+
defaultVariants: {
219+
size: "md",
220+
tone: "neutral",
221+
offTone: "neutral",
222+
invalid: false,
223+
},
224+
});

0 commit comments

Comments
 (0)