@realmorg/ui 0.0.0-stage → 2.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +44 -2
  3. package/components/accordion.html +139 -0
  4. package/components/alert-dialog.html +236 -0
  5. package/components/alert.html +83 -0
  6. package/components/aspect-ratio.html +29 -0
  7. package/components/attachment.html +98 -0
  8. package/components/avatar.html +149 -0
  9. package/components/badge.html +62 -0
  10. package/components/breadcrumb.html +159 -0
  11. package/components/bubble.html +77 -0
  12. package/components/button-group.html +98 -0
  13. package/components/button.html +194 -0
  14. package/components/calendar.html +176 -0
  15. package/components/card.html +143 -0
  16. package/components/carousel.html +208 -0
  17. package/components/chart.html +168 -0
  18. package/components/checkbox.html +99 -0
  19. package/components/code-block.html +64 -0
  20. package/components/collapsible.html +90 -0
  21. package/components/combobox.html +237 -0
  22. package/components/command.html +201 -0
  23. package/components/context-menu.html +81 -0
  24. package/components/dialog.html +327 -0
  25. package/components/direction.html +23 -0
  26. package/components/drawer.html +267 -0
  27. package/components/dropdown-menu.html +421 -0
  28. package/components/empty.html +132 -0
  29. package/components/field.html +143 -0
  30. package/components/hover-card.html +111 -0
  31. package/components/input-group.html +91 -0
  32. package/components/input-otp.html +151 -0
  33. package/components/input.html +94 -0
  34. package/components/item.html +208 -0
  35. package/components/kbd.html +45 -0
  36. package/components/label.html +46 -0
  37. package/components/marker.html +41 -0
  38. package/components/menubar.html +137 -0
  39. package/components/message-scroller.html +68 -0
  40. package/components/message.html +95 -0
  41. package/components/navigation-menu.html +233 -0
  42. package/components/pagination.html +160 -0
  43. package/components/popover.html +164 -0
  44. package/components/progress.html +45 -0
  45. package/components/radio-group.html +120 -0
  46. package/components/resizable.html +170 -0
  47. package/components/scroll-area.html +56 -0
  48. package/components/select.html +103 -0
  49. package/components/separator.html +31 -0
  50. package/components/sheet.html +173 -0
  51. package/components/sidebar.html +393 -0
  52. package/components/skeleton.html +36 -0
  53. package/components/slider.html +108 -0
  54. package/components/spinner.html +53 -0
  55. package/components/switch.html +95 -0
  56. package/components/table.html +81 -0
  57. package/components/tabs.html +182 -0
  58. package/components/textarea.html +80 -0
  59. package/components/toaster.html +134 -0
  60. package/components/toggle-group.html +71 -0
  61. package/components/toggle.html +87 -0
  62. package/components/tooltip.html +114 -0
  63. package/dist/theme.css +158 -0
  64. package/dist/ui.html +7923 -0
  65. package/package.json +40 -3
  66. package/theme.css +158 -0
@@ -0,0 +1,393 @@
1
+ <import-element from="./button.html"></import-element>
2
+
3
+ <custom-element name="ui-sidebar-layout">
4
+ <script>
5
+ const props = defineProps({
6
+ collapsed: prop.boolean().reflect(),
7
+ persist: prop.string(),
8
+ });
9
+
10
+ const media = matchMedia("(max-width: 767px)");
11
+ const layout = useState({ mobile: media.matches, open: false });
12
+
13
+ function toggle() {
14
+ if (layout.mobile) layout.open = !layout.open;
15
+ else props.collapsed = !props.collapsed;
16
+ }
17
+
18
+ function close() {
19
+ layout.open = false;
20
+ }
21
+
22
+ function shortcut(event) {
23
+ if (event.key.toLowerCase() !== "b" || !(event.metaKey || event.ctrlKey)) return;
24
+ event.preventDefault();
25
+ toggle();
26
+ }
27
+
28
+ if (props.persist) props.collapsed = localStorage.getItem(props.persist) === "true";
29
+
30
+ defineMethods({ toggle });
31
+
32
+ listen("sidebartoggle", toggle);
33
+ listen("keydown", shortcut, document);
34
+ listen("change", () => (layout.mobile = media.matches), media);
35
+
36
+ observe(() => {
37
+ if (props.persist) localStorage.setItem(props.persist, String(props.collapsed));
38
+ });
39
+ </script>
40
+
41
+ <style>
42
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
43
+ touch-action does not reach shadow content in iOS Safari. */
44
+ :host,
45
+ * {
46
+ touch-action: manipulation;
47
+ }
48
+ :host {
49
+ --ui-sidebar-width: calc(var(--spacing) * 64);
50
+ --ui-sidebar-label: block;
51
+ display: flex;
52
+ min-height: 100svh;
53
+ background: var(--sidebar);
54
+ }
55
+ :host(:state(attr-collapsed)) {
56
+ --ui-sidebar-width: calc(var(--spacing) * 12);
57
+ --ui-sidebar-label: none;
58
+ }
59
+ .sidebar {
60
+ position: sticky;
61
+ top: 0;
62
+ width: var(--ui-sidebar-width);
63
+ height: 100svh;
64
+ flex-shrink: 0;
65
+ transition: width 0.2s ease;
66
+ }
67
+ .backdrop {
68
+ display: none;
69
+ }
70
+ :host(:state(state-layout-mobile)) {
71
+ --ui-sidebar-width: calc(var(--spacing) * 72);
72
+ --ui-sidebar-label: block;
73
+ --ui-sidebar-inset-margin: 0;
74
+ --ui-sidebar-inset-radius: 0;
75
+ }
76
+ :host(:state(state-layout-mobile)) .sidebar {
77
+ position: fixed;
78
+ inset: 0 auto 0 0;
79
+ z-index: 50;
80
+ translate: -100% 0;
81
+ transition: translate 0.2s ease;
82
+ }
83
+ :host(:state(state-layout-open)) .sidebar {
84
+ translate: 0 0;
85
+ }
86
+ :host(:state(state-layout-open)) .backdrop {
87
+ position: fixed;
88
+ inset: 0;
89
+ z-index: 40;
90
+ display: block;
91
+ background: rgb(0 0 0 / 0.5);
92
+ }
93
+ </style>
94
+
95
+ <aside class="sidebar" part="sidebar"><slot name="sidebar"></slot></aside>
96
+ <div class="backdrop" part="backdrop" onclick="close()"></div>
97
+ <slot></slot>
98
+ </custom-element>
99
+
100
+ <custom-element name="ui-sidebar" slot="sidebar">
101
+ <style>
102
+ :host,
103
+ * {
104
+ touch-action: manipulation;
105
+ }
106
+ :host {
107
+ display: flex;
108
+ flex-direction: column;
109
+ height: 100%;
110
+ box-sizing: border-box;
111
+ background: var(--sidebar);
112
+ color: var(--sidebar-foreground);
113
+ overflow: hidden;
114
+ }
115
+ </style>
116
+
117
+ <slot name="header"></slot>
118
+ <slot></slot>
119
+ <slot name="footer"></slot>
120
+ </custom-element>
121
+
122
+ <custom-element name="ui-sidebar-header" slot="header">
123
+ <style>
124
+ :host,
125
+ * {
126
+ touch-action: manipulation;
127
+ }
128
+ :host {
129
+ display: flex;
130
+ flex-direction: column;
131
+ gap: calc(var(--spacing) * 2);
132
+ padding: calc(var(--spacing) * 2);
133
+ }
134
+ </style>
135
+
136
+ <slot></slot>
137
+ </custom-element>
138
+
139
+ <custom-element name="ui-sidebar-footer" slot="footer">
140
+ <style>
141
+ :host,
142
+ * {
143
+ touch-action: manipulation;
144
+ }
145
+ :host {
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: calc(var(--spacing) * 2);
149
+ padding: calc(var(--spacing) * 2);
150
+ }
151
+ </style>
152
+
153
+ <slot></slot>
154
+ </custom-element>
155
+
156
+ <custom-element name="ui-sidebar-content">
157
+ <style>
158
+ :host,
159
+ * {
160
+ touch-action: manipulation;
161
+ }
162
+ :host {
163
+ display: block;
164
+ flex: 1;
165
+ min-height: 0;
166
+ overflow: hidden auto;
167
+ }
168
+ nav {
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: calc(var(--spacing) * 2);
172
+ }
173
+ </style>
174
+
175
+ <nav part="content"><slot></slot></nav>
176
+ </custom-element>
177
+
178
+ <custom-element name="ui-sidebar-group" role="group">
179
+ <script>
180
+ const labels = useChildren("ui-sidebar-group-label");
181
+
182
+ observe(() => {
183
+ internals.ariaLabelledByElements = [...labels];
184
+ });
185
+ </script>
186
+
187
+ <style>
188
+ :host,
189
+ * {
190
+ touch-action: manipulation;
191
+ }
192
+ :host {
193
+ display: flex;
194
+ flex-direction: column;
195
+ padding: calc(var(--spacing) * 2);
196
+ }
197
+ </style>
198
+
199
+ <slot></slot>
200
+ </custom-element>
201
+
202
+ <custom-element name="ui-sidebar-group-label">
203
+ <style>
204
+ :host,
205
+ * {
206
+ touch-action: manipulation;
207
+ }
208
+ :host {
209
+ display: var(--ui-sidebar-label, block);
210
+ height: calc(var(--spacing) * 8);
211
+ padding: 0 calc(var(--spacing) * 2);
212
+ font-size: var(--text-xs);
213
+ font-weight: var(--font-weight-medium);
214
+ line-height: calc(var(--spacing) * 8);
215
+ white-space: nowrap;
216
+ color: color-mix(in oklab, var(--sidebar-foreground) 70%, transparent);
217
+ }
218
+ </style>
219
+
220
+ <slot></slot>
221
+ </custom-element>
222
+
223
+ <custom-element name="ui-sidebar-menu">
224
+ <style>
225
+ :host,
226
+ * {
227
+ touch-action: manipulation;
228
+ }
229
+ :host {
230
+ display: block;
231
+ }
232
+ ul {
233
+ display: flex;
234
+ flex-direction: column;
235
+ gap: calc(var(--spacing) * 1);
236
+ margin: 0;
237
+ padding: 0;
238
+ list-style: none;
239
+ }
240
+ </style>
241
+
242
+ <ul part="menu" focusgroup="menu block"><slot></slot></ul>
243
+ </custom-element>
244
+
245
+ <custom-element name="ui-sidebar-menu-item" role="listitem">
246
+ <style>
247
+ :host,
248
+ * {
249
+ touch-action: manipulation;
250
+ }
251
+ :host {
252
+ position: relative;
253
+ display: block;
254
+ }
255
+ </style>
256
+
257
+ <slot></slot>
258
+ </custom-element>
259
+
260
+ <custom-element name="ui-sidebar-menu-button">
261
+ <script>
262
+ const props = defineProps({
263
+ href: prop.string(),
264
+ current: prop.boolean(),
265
+ label: prop.string(),
266
+ });
267
+
268
+ const item = useState({
269
+ get current() {
270
+ return props.current ? "page" : null;
271
+ },
272
+ get role() {
273
+ return props.href ? null : "button";
274
+ },
275
+ });
276
+
277
+ const ACTIVATE = { Enter: true, " ": true };
278
+
279
+ function pressed(event) {
280
+ if (!dispatch("select")) event.preventDefault();
281
+ }
282
+
283
+ function key(event) {
284
+ if (props.href || !ACTIVATE[event.key]) return;
285
+ event.preventDefault();
286
+ event.currentTarget.click();
287
+ }
288
+ </script>
289
+
290
+ <style>
291
+ :host,
292
+ * {
293
+ touch-action: manipulation;
294
+ }
295
+ :host {
296
+ display: block;
297
+ }
298
+ a {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: calc(var(--spacing) * 2);
302
+ height: calc(var(--spacing) * 8);
303
+ padding: 0 calc(var(--spacing) * 2);
304
+ border-radius: var(--radius-md);
305
+ overflow: hidden;
306
+ color: inherit;
307
+ font-size: var(--text-sm);
308
+ text-decoration: none;
309
+ white-space: nowrap;
310
+ outline: none;
311
+ cursor: pointer;
312
+ }
313
+ a:hover,
314
+ a:focus-visible,
315
+ :host(:state(attr-current)) a {
316
+ background: var(--sidebar-accent);
317
+ }
318
+ :host(:state(attr-current)) a {
319
+ font-weight: var(--font-weight-medium);
320
+ }
321
+ a:focus-visible {
322
+ box-shadow: 0 0 0 2px var(--ring);
323
+ }
324
+ ::slotted(svg) {
325
+ width: calc(var(--spacing) * 4);
326
+ height: calc(var(--spacing) * 4);
327
+ flex-shrink: 0;
328
+ }
329
+ </style>
330
+
331
+ <a part="button" _href="@href" _title="@label" _aria-current="#item.current" _role="#item.role" tabindex="0" onclick="pressed(event)" onkeydown="key(event)"><slot></slot></a>
332
+ </custom-element>
333
+
334
+ <custom-element name="ui-sidebar-menu-badge">
335
+ <style>
336
+ :host,
337
+ * {
338
+ touch-action: manipulation;
339
+ }
340
+ :host {
341
+ display: var(--ui-sidebar-label, block);
342
+ position: absolute;
343
+ top: 0;
344
+ right: calc(var(--spacing) * 1);
345
+ padding: 0 calc(var(--spacing) * 1);
346
+ line-height: calc(var(--spacing) * 8);
347
+ font-size: var(--text-xs);
348
+ font-weight: var(--font-weight-medium);
349
+ color: color-mix(in oklab, var(--sidebar-foreground) 70%, transparent);
350
+ pointer-events: none;
351
+ }
352
+ </style>
353
+
354
+ <slot></slot>
355
+ </custom-element>
356
+
357
+ <custom-element name="ui-sidebar-trigger">
358
+ <script>
359
+ function toggle() {
360
+ dispatch("sidebartoggle");
361
+ }
362
+ </script>
363
+
364
+ <ui-button variant="ghost" size="icon-sm" aria-label="Toggle sidebar" onclick="toggle()">
365
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" /><path d="M9 3v18" /></svg>
366
+ </ui-button>
367
+ </custom-element>
368
+
369
+ <custom-element name="ui-sidebar-inset">
370
+ <style>
371
+ :host,
372
+ * {
373
+ touch-action: manipulation;
374
+ }
375
+ :host {
376
+ display: flex;
377
+ flex: 1;
378
+ min-width: 0;
379
+ }
380
+ main {
381
+ display: flex;
382
+ flex: 1;
383
+ flex-direction: column;
384
+ min-width: 0;
385
+ margin: var(--ui-sidebar-inset-margin, calc(var(--spacing) * 2) calc(var(--spacing) * 2) calc(var(--spacing) * 2) 0);
386
+ border-radius: var(--ui-sidebar-inset-radius, var(--radius-xl));
387
+ background: var(--background);
388
+ box-shadow: var(--shadow-xs);
389
+ }
390
+ </style>
391
+
392
+ <main part="main"><slot></slot></main>
393
+ </custom-element>
@@ -0,0 +1,36 @@
1
+ <custom-element name="ui-skeleton">
2
+ <style>
3
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
4
+ touch-action does not reach shadow content in iOS Safari. */
5
+ :host,
6
+ * {
7
+ touch-action: manipulation;
8
+ }
9
+ :host {
10
+ display: block;
11
+ border-radius: var(--radius-md);
12
+ background: var(--accent);
13
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
14
+ }
15
+ :host(:state(attr-width)) {
16
+ width: calc(var(--spacing) * var(--attr-width));
17
+ }
18
+ :host(:state(attr-height)) {
19
+ height: calc(var(--spacing) * var(--attr-height));
20
+ }
21
+ :host(:state(attr-shape-is-circle)) {
22
+ flex-shrink: 0;
23
+ border-radius: var(--radius-full);
24
+ }
25
+ @keyframes pulse {
26
+ 50% {
27
+ opacity: 0.5;
28
+ }
29
+ }
30
+ @media (prefers-reduced-motion: reduce) {
31
+ :host {
32
+ animation: none;
33
+ }
34
+ }
35
+ </style>
36
+ </custom-element>
@@ -0,0 +1,108 @@
1
+ <custom-element name="ui-slider" form-associated delegates-focus>
2
+ <script>
3
+ const props = defineProps({
4
+ value: prop.number().default(0),
5
+ min: prop.number().default(0),
6
+ max: prop.number().default(100),
7
+ step: prop.number().default(1),
8
+ disabled: prop.boolean(),
9
+ label: prop.string().attribute("aria-label"),
10
+ });
11
+
12
+ const range = useState({
13
+ get percent() {
14
+ return `${((props.value - props.min) / (props.max - props.min || 1)) * 100}%`;
15
+ },
16
+ });
17
+
18
+ // Writing the value Safari just set, during a finger drag, makes the thumb jump.
19
+ observe(() => {
20
+ const value = String(props.value);
21
+ if (refs.control && refs.control.value !== value) refs.control.value = value;
22
+ });
23
+ </script>
24
+
25
+ <style>
26
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
27
+ touch-action does not reach shadow content in iOS Safari. */
28
+ :host,
29
+ * {
30
+ touch-action: manipulation;
31
+ }
32
+ :host {
33
+ display: block;
34
+ min-width: calc(var(--spacing) * 32);
35
+ }
36
+ input {
37
+ --fill: var(--primary);
38
+ appearance: none;
39
+ width: 100%;
40
+ height: calc(var(--spacing) * 4);
41
+ margin: 0;
42
+ background: transparent;
43
+ cursor: pointer;
44
+ outline: none;
45
+ /* The slider owns the whole gesture, so the browser never turns a drag into a page pan. */
46
+ touch-action: none;
47
+ }
48
+ input::-webkit-slider-runnable-track {
49
+ height: calc(var(--spacing) * 1.5);
50
+ border-radius: var(--radius-full);
51
+ background: linear-gradient(to right, var(--fill) var(--state-range-percent), var(--muted) var(--state-range-percent));
52
+ }
53
+ input::-moz-range-track {
54
+ height: calc(var(--spacing) * 1.5);
55
+ border-radius: var(--radius-full);
56
+ background: linear-gradient(to right, var(--fill) var(--state-range-percent), var(--muted) var(--state-range-percent));
57
+ }
58
+ input::-webkit-slider-thumb {
59
+ appearance: none;
60
+ width: calc(var(--spacing) * 4);
61
+ height: calc(var(--spacing) * 4);
62
+ margin-top: calc(var(--spacing) * -1.25);
63
+ border: 1px solid var(--primary);
64
+ border-radius: var(--radius-full);
65
+ background: var(--background);
66
+ box-shadow: var(--shadow-sm);
67
+ }
68
+ input::-moz-range-thumb {
69
+ width: calc(var(--spacing) * 4);
70
+ height: calc(var(--spacing) * 4);
71
+ border: 1px solid var(--primary);
72
+ border-radius: var(--radius-full);
73
+ background: var(--background);
74
+ }
75
+ input:focus-visible::-webkit-slider-thumb {
76
+ box-shadow: 0 0 0 4px color-mix(in oklab, var(--ring) 50%, transparent);
77
+ }
78
+ @media (pointer: coarse) {
79
+ input {
80
+ height: calc(var(--spacing) * 6);
81
+ }
82
+ input::-webkit-slider-thumb {
83
+ width: calc(var(--spacing) * 6);
84
+ height: calc(var(--spacing) * 6);
85
+ margin-top: calc(var(--spacing) * -2.25);
86
+ }
87
+ input::-moz-range-thumb {
88
+ width: calc(var(--spacing) * 6);
89
+ height: calc(var(--spacing) * 6);
90
+ }
91
+ }
92
+ input:disabled {
93
+ cursor: not-allowed;
94
+ opacity: 0.5;
95
+ }
96
+ </style>
97
+
98
+ <input
99
+ ref="control"
100
+ type="range"
101
+ part="control"
102
+ _min="@min"
103
+ _max="@max"
104
+ _step="@step"
105
+ _disabled="@disabled"
106
+ _aria-label="@label"
107
+ >
108
+ </custom-element>
@@ -0,0 +1,53 @@
1
+ <custom-element name="ui-spinner" role="status">
2
+ <script>
3
+ const props = defineProps({
4
+ size: prop.number().min(8).default(16),
5
+ label: prop.string().default("Loading"),
6
+ variant: prop.enum(["default", "primary", "muted", "destructive"]).default("default"),
7
+ });
8
+
9
+ observe(() => {
10
+ internals.ariaLabel = props.label;
11
+ });
12
+ </script>
13
+
14
+ <style>
15
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
16
+ touch-action does not reach shadow content in iOS Safari. */
17
+ :host,
18
+ * {
19
+ touch-action: manipulation;
20
+ }
21
+ :host {
22
+ display: inline-flex;
23
+ width: calc(var(--attr-size) * 1px);
24
+ height: calc(var(--attr-size) * 1px);
25
+ }
26
+ :host(:state(attr-variant-is-primary)) {
27
+ color: var(--primary);
28
+ }
29
+ :host(:state(attr-variant-is-muted)) {
30
+ color: var(--muted-foreground);
31
+ }
32
+ :host(:state(attr-variant-is-destructive)) {
33
+ color: var(--destructive);
34
+ }
35
+ svg {
36
+ width: 100%;
37
+ height: 100%;
38
+ animation: spin 0.8s linear infinite;
39
+ }
40
+ @keyframes spin {
41
+ to {
42
+ rotate: 360deg;
43
+ }
44
+ }
45
+ @media (prefers-reduced-motion: reduce) {
46
+ svg {
47
+ animation-duration: 2.4s;
48
+ }
49
+ }
50
+ </style>
51
+
52
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
53
+ </custom-element>
@@ -0,0 +1,95 @@
1
+ <custom-element name="ui-switch" form-associated delegates-focus>
2
+ <script>
3
+ defineProps({
4
+ checked: prop.boolean(),
5
+ value: prop.string(),
6
+ size: prop.enum(["default", "sm"]).default("default"),
7
+ disabled: prop.boolean(),
8
+ label: prop.string().attribute("aria-label"),
9
+ });
10
+ </script>
11
+
12
+ <style>
13
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
14
+ touch-action does not reach shadow content in iOS Safari. */
15
+ :host,
16
+ * {
17
+ touch-action: manipulation;
18
+ }
19
+ :host(:state(attr-checked)) label {
20
+ color: var(--foreground);
21
+ }
22
+ :host {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ gap: calc(var(--spacing) * 2);
26
+ vertical-align: middle;
27
+ }
28
+ input {
29
+ appearance: none;
30
+ box-sizing: border-box;
31
+ position: relative;
32
+ width: calc(var(--spacing) * 8);
33
+ height: calc(var(--spacing) * 4.6);
34
+ margin: 0;
35
+ border: 1px solid transparent;
36
+ border-radius: var(--radius-full);
37
+ background: var(--input);
38
+ box-shadow: var(--shadow-xs);
39
+ cursor: pointer;
40
+ outline: none;
41
+ transition: background-color var(--default-transition-duration), box-shadow var(--default-transition-duration);
42
+ }
43
+ input::before {
44
+ content: "";
45
+ position: absolute;
46
+ top: 50%;
47
+ left: 0;
48
+ width: calc(var(--spacing) * 4);
49
+ height: calc(var(--spacing) * 4);
50
+ border-radius: var(--radius-full);
51
+ background: var(--background);
52
+ translate: 0 -50%;
53
+ transition: translate var(--default-transition-duration);
54
+ }
55
+ input:checked {
56
+ background: var(--primary);
57
+ }
58
+ input:checked::before {
59
+ translate: calc(100% - 2px) -50%;
60
+ }
61
+ :host(:state(attr-size-is-sm)) input {
62
+ width: calc(var(--spacing) * 6);
63
+ height: calc(var(--spacing) * 3.5);
64
+ }
65
+ :host(:state(attr-size-is-sm)) input::before {
66
+ width: calc(var(--spacing) * 3);
67
+ height: calc(var(--spacing) * 3);
68
+ }
69
+ input:focus-visible {
70
+ border-color: var(--ring);
71
+ box-shadow: var(--focus-ring);
72
+ }
73
+ input:disabled {
74
+ cursor: not-allowed;
75
+ opacity: 0.5;
76
+ }
77
+ label {
78
+ font-size: var(--text-sm);
79
+ font-weight: var(--font-weight-medium);
80
+ }
81
+ </style>
82
+
83
+ <input
84
+ id="control"
85
+ ref="control"
86
+ part="control"
87
+ type="checkbox"
88
+ _aria-label="@label"
89
+ role="switch"
90
+ _checked="@checked"
91
+ _aria-checked="@checked"
92
+ _disabled="@disabled"
93
+ >
94
+ <label for="control" part="label"><slot></slot></label>
95
+ </custom-element>