Skip to content

Commit 9d59bc2

Browse files
authored
feat: new SegmentedControl component (#531)
1 parent 8b82b16 commit 9d59bc2

14 files changed

+1480
-16
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
[data-kb-theme] .segmented-control {
2+
--font-size-md: 0.875rem;
3+
--font-size-sm: 0.75rem;
4+
--border-width: 1px;
5+
--border-radius: 0.5rem;
6+
}
7+
8+
[data-kb-theme="light"] .segmented-control {
9+
--border-color: hsl(240 6% 90%);
10+
--border-active-color: hsl(240 6% 75%);
11+
--label-color: hsl(240 6% 10%);
12+
--description-color: hsl(240 5% 26%);
13+
--error-message-color: hsl(0 72% 51%);
14+
--root-background-color: hsl(0, 0%, 98%);
15+
--indicator-background-color: hsl(0, 0%, 100%);
16+
--indicator-focus-shadow-color: hsl(200 98% 39%);
17+
}
18+
19+
[data-kb-theme="dark"] .segmented-control {
20+
--border-color: hsl(240 6% 26%);
21+
--border-active-color: hsl(240 6% 41%);
22+
--label-color: hsl(240 5% 84%);
23+
--description-color: hsl(240 5% 65%);
24+
--error-message-color: hsl(0 72% 51%);
25+
--root-background-color: hsl(240, 4%, 16%);
26+
--indicator-background-color: hsl(240, 6%, 10%);
27+
--indicator-focus-shadow-color: hsl(200 98% 39%);
28+
}
29+
30+
.segmented-control {
31+
--label-padding-x: 1rem;
32+
--label-padding-y: 0.563rem;
33+
--item-separator-translate: calc(-1 * var(--border-width) / 2);
34+
35+
display: flex;
36+
flex-direction: column;
37+
gap: 0.5rem;
38+
}
39+
40+
.segmented-control__label {
41+
color: var(--label-color);
42+
font-size: var(--font-size-md);
43+
font-weight: 500;
44+
user-select: none;
45+
line-height: 1;
46+
}
47+
48+
.segmented-control__description {
49+
color: var(--description-color);
50+
font-size: var(--font-size-sm);
51+
user-select: none;
52+
}
53+
54+
.segmented-control__error-message {
55+
color: var(--error-message-color);
56+
font-size: var(--font-size-sm);
57+
user-select: none;
58+
}
59+
60+
/* Wrapper */
61+
62+
.segmented-control__wrapper {
63+
all: unset;
64+
background-color: var(--root-background-color);
65+
border-radius: var(--border-radius);
66+
box-shadow: inset 0px 0px 0px var(--border-width) var(--border-color);
67+
margin: 0;
68+
padding: 0;
69+
position: relative;
70+
width: fit-content;
71+
}
72+
73+
/* Items */
74+
75+
.segmented-control__items {
76+
display: inline-flex;
77+
list-style: none;
78+
}
79+
80+
.segmented-control[aria-orientation="vertical"] .segmented-control__items {
81+
flex-direction: column;
82+
}
83+
84+
.segmented-control[aria-orientation="horizontal"] .segmented-control__items {
85+
flex-direction: row;
86+
}
87+
88+
/* Indicator */
89+
90+
.segmented-control__indicator {
91+
background: var(--indicator-background-color);
92+
border-radius: var(--border-radius);
93+
box-shadow:
94+
0px 1px 3px rgba(0 0 0 / 8%),
95+
0px 2px 8px rgba(0 0 0 / 12%),
96+
inset 0px 0px 0px var(--indicator-focus-shadow-width, 0px) var(--indicator-focus-shadow-color),
97+
inset 0px 0px 0px var(--border-width) var(--border-active-color);
98+
content: "";
99+
opacity: var(--pane-opacity, 1);
100+
position: absolute;
101+
transition:
102+
opacity 500ms ease-in-out,
103+
box-shadow 100ms ease-in-out,
104+
width 200ms ease,
105+
height 200ms ease,
106+
transform 200ms ease;
107+
}
108+
109+
.segmented-control__wrapper:has(.segmented-control__item-input:focus-visible)
110+
.segmented-control__indicator {
111+
--indicator-focus-shadow-width: 2px;
112+
}
113+
114+
.segmented-control__wrapper:not(:has(.segmented-control__item-input:checked))
115+
.segmented-control__indicator {
116+
--pane-opacity: 0;
117+
}
118+
119+
/* Item */
120+
121+
.segmented-control__item {
122+
position: relative;
123+
}
124+
125+
.segmented-control__item:not(:first-of-type)::before {
126+
background: var(--border-color);
127+
border-radius: var(--border-radius);
128+
content: "";
129+
inset: 0;
130+
position: absolute;
131+
transition: opacity 200ms ease;
132+
}
133+
134+
.segmented-control__item:has(.segmented-control__item-input:checked)::before,
135+
.segmented-control__item:has(.segmented-control__item-input:checked) + ::before {
136+
opacity: 0;
137+
}
138+
139+
.segmented-control[aria-orientation="vertical"]
140+
.segmented-control__item:not(:first-of-type)::before {
141+
height: var(--border-width);
142+
inset: 0 var(--label-padding-x);
143+
transform: translateY(var(--item-separator-translate));
144+
}
145+
146+
.segmented-control[aria-orientation="horizontal"]
147+
.segmented-control__item:not(:first-of-type)::before {
148+
inset: var(--label-padding-y) 0;
149+
transform: translateX(var(--item-separator-translate));
150+
width: var(--border-width);
151+
}
152+
153+
/* Item Label */
154+
155+
.segmented-control__item-label {
156+
color: var(--description-color);
157+
font-size: var(--font-size-md);
158+
border-radius: var(--border-radius);
159+
cursor: pointer;
160+
display: flex;
161+
flex-wrap: nowrap;
162+
font-weight: 500;
163+
gap: 0.25rem;
164+
line-height: 1;
165+
padding: var(--label-padding-y) var(--label-padding-x);
166+
place-content: center;
167+
position: relative;
168+
transition-duration: 200ms;
169+
transition-property: color, opacity;
170+
transition-timing-function: ease-in-out;
171+
user-select: none;
172+
}
173+
174+
.segmented-control__item-input {
175+
all: unset;
176+
}
177+
178+
.segmented-control__item-input:checked + .segmented-control__item-label {
179+
color: var(--label-color);
180+
}
181+
182+
.segmented-control__item-input:disabled + .segmented-control__item-label {
183+
cursor: not-allowed;
184+
opacity: 0.5;
185+
}
186+
187+
.segmented-control__item-input:not(:checked, :disabled) + .segmented-control__item-label {
188+
cursor: pointer;
189+
opacity: var(--item-label-opacity, 1);
190+
user-select: none;
191+
}
192+
193+
.segmented-control__item-input:not(:checked, :disabled) + .segmented-control__item-label:hover {
194+
--item-label-opacity: 0.75;
195+
}
196+
197+
.segmented-control__item-input:not(:checked, :disabled) + .segmented-control__item-label:active {
198+
--item-label-opacity: 0.5;
199+
}

0 commit comments

Comments
 (0)