@studiocms/ui 0.0.1 → 0.3.0
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.
- package/README.md +28 -544
- package/package.json +11 -6
- package/src/components/Button.astro +303 -269
- package/src/components/Card.astro +37 -13
- package/src/components/Center.astro +2 -2
- package/src/components/Checkbox.astro +99 -29
- package/src/components/Divider.astro +15 -8
- package/src/components/Dropdown/Dropdown.astro +102 -41
- package/src/components/Dropdown/dropdown.ts +111 -23
- package/src/components/Footer.astro +137 -0
- package/src/components/Input.astro +42 -14
- package/src/components/Modal/Modal.astro +84 -30
- package/src/components/Modal/modal.ts +43 -9
- package/src/components/RadioGroup.astro +153 -29
- package/src/components/Row.astro +16 -7
- package/src/components/SearchSelect.astro +278 -222
- package/src/components/Select.astro +260 -127
- package/src/components/Sidebar/Double.astro +12 -12
- package/src/components/Sidebar/Single.astro +6 -6
- package/src/components/Sidebar/helpers.ts +53 -7
- package/src/components/Tabs/TabItem.astro +47 -0
- package/src/components/Tabs/Tabs.astro +376 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Textarea.astro +56 -15
- package/src/components/ThemeToggle.astro +14 -8
- package/src/components/Toast/Toaster.astro +171 -31
- package/src/components/Toggle.astro +89 -21
- package/src/components/User.astro +34 -15
- package/src/components/index.ts +24 -22
- package/src/components.ts +2 -0
- package/src/css/colors.css +65 -65
- package/src/css/resets.css +0 -1
- package/src/integration.ts +18 -0
- package/src/layouts/RootLayout.astro +1 -2
- package/src/types/index.ts +1 -1
- package/src/utils/ThemeHelper.ts +135 -117
- package/src/utils/create-resolver.ts +30 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
3
|
-
import Button from "./Button.astro";
|
|
2
|
+
import Button, { type Props as ButtonProps } from './Button.astro';
|
|
4
3
|
|
|
5
|
-
interface Props extends
|
|
4
|
+
interface Props extends ButtonProps {}
|
|
6
5
|
|
|
7
6
|
const props = Astro.props;
|
|
8
7
|
---
|
|
9
8
|
|
|
10
|
-
<Button id='
|
|
9
|
+
<Button id='sui-theme-toggle' {...props}>
|
|
11
10
|
<div id="dark-content">
|
|
12
11
|
<slot name="dark" />
|
|
13
12
|
</div>
|
|
@@ -19,22 +18,29 @@ const props = Astro.props;
|
|
|
19
18
|
<script>
|
|
20
19
|
import { ThemeHelper } from '../utils/ThemeHelper';
|
|
21
20
|
|
|
22
|
-
const themeToggle = document.getElementById('
|
|
21
|
+
const themeToggle = document.getElementById('sui-theme-toggle');
|
|
23
22
|
const themeHelper = new ThemeHelper();
|
|
24
23
|
|
|
25
24
|
themeHelper.registerToggle(themeToggle);
|
|
26
25
|
</script>
|
|
27
26
|
|
|
28
27
|
<style is:global>
|
|
29
|
-
#
|
|
28
|
+
#sui-theme-toggle, #sui-theme-toggle * {
|
|
29
|
+
color: hsl(var(--text-normal));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#sui-theme-toggle #dark-content, #sui-theme-toggle #light-content {
|
|
30
33
|
display: none;
|
|
34
|
+
width: fit-content;
|
|
35
|
+
height: fit-content;
|
|
36
|
+
max-height: 100%;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
[data-theme="dark"] #
|
|
39
|
+
[data-theme="dark"] #sui-theme-toggle #dark-content {
|
|
34
40
|
display: block;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
[data-theme="light"] #
|
|
43
|
+
[data-theme="light"] #sui-theme-toggle #light-content {
|
|
38
44
|
display: block;
|
|
39
45
|
}
|
|
40
46
|
</style>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
+
/**
|
|
3
|
+
* Props for the Toast component.
|
|
4
|
+
*/
|
|
2
5
|
interface Props {
|
|
6
|
+
/**
|
|
7
|
+
* The position of the toaster. All toasts will originate from this position.
|
|
8
|
+
*/
|
|
3
9
|
position?:
|
|
4
10
|
| 'top-left'
|
|
5
11
|
| 'top-right'
|
|
@@ -7,11 +13,23 @@ interface Props {
|
|
|
7
13
|
| 'bottom-left'
|
|
8
14
|
| 'bottom-right'
|
|
9
15
|
| 'bottom-center';
|
|
16
|
+
/**
|
|
17
|
+
* The duration of the toast in milliseconds. Defaults to 4000 (4 seconds).
|
|
18
|
+
*/
|
|
10
19
|
duration?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the toast has a close button. Defaults to false.
|
|
22
|
+
*/
|
|
11
23
|
closeButton?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The offset of the toaster from the edge of the screen in pixels. Defaults to 32.
|
|
26
|
+
*/
|
|
12
27
|
offset?: number;
|
|
28
|
+
/**
|
|
29
|
+
* The gap between toasts in pixels. Defaults to 8.
|
|
30
|
+
*/
|
|
13
31
|
gap?: number;
|
|
14
|
-
}
|
|
32
|
+
}
|
|
15
33
|
|
|
16
34
|
const {
|
|
17
35
|
position = 'top-center',
|
|
@@ -22,14 +40,14 @@ const {
|
|
|
22
40
|
} = Astro.props;
|
|
23
41
|
---
|
|
24
42
|
<div
|
|
25
|
-
id="toaster"
|
|
43
|
+
id="sui-toaster"
|
|
26
44
|
class:list={[
|
|
27
45
|
closeButton && "closeable",
|
|
28
46
|
position,
|
|
29
47
|
]},
|
|
30
48
|
>
|
|
31
49
|
<div
|
|
32
|
-
id="toast-drawer"
|
|
50
|
+
id="sui-toast-drawer"
|
|
33
51
|
data-offset={offset}
|
|
34
52
|
data-gap={gap}
|
|
35
53
|
data-duration={duration}
|
|
@@ -53,6 +71,65 @@ const {
|
|
|
53
71
|
|
|
54
72
|
let activeToasts: string[] = [];
|
|
55
73
|
|
|
74
|
+
let lastActiveElement: HTMLElement | null = null;
|
|
75
|
+
|
|
76
|
+
const revertFocusBackToLastActiveElement = () => {
|
|
77
|
+
if (lastActiveElement) {
|
|
78
|
+
lastActiveElement.focus();
|
|
79
|
+
lastActiveElement = null;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Callback wrapper that allows for pausing, continuing and clearing a timer. Based on https://stackoverflow.com/a/20745721.
|
|
85
|
+
* @param callback The callback to be called.
|
|
86
|
+
* @param delay The delay in milliseconds.
|
|
87
|
+
*/
|
|
88
|
+
class Timer {
|
|
89
|
+
private id: NodeJS.Timeout | null;
|
|
90
|
+
private started: Date | null;
|
|
91
|
+
private remaining: number;
|
|
92
|
+
private running: boolean;
|
|
93
|
+
private callback: () => any;
|
|
94
|
+
|
|
95
|
+
constructor(callback: () => any, delay: number) {
|
|
96
|
+
this.id = null;
|
|
97
|
+
this.started = null;
|
|
98
|
+
this.remaining = delay;
|
|
99
|
+
this.running = false;
|
|
100
|
+
this.callback = callback;
|
|
101
|
+
|
|
102
|
+
this.start();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
start = () => {
|
|
106
|
+
this.running = true;
|
|
107
|
+
this.started = new Date();
|
|
108
|
+
this.id = setTimeout(this.callback, this.remaining);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
pause = () => {
|
|
112
|
+
if (!this.id || !this.started || !this.running) return;
|
|
113
|
+
|
|
114
|
+
this.running = false;
|
|
115
|
+
clearTimeout(this.id);
|
|
116
|
+
this.remaining -= new Date().getTime() - this.started.getTime();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
getTimeLeft = () => {
|
|
120
|
+
if (this.running) {
|
|
121
|
+
this.pause();
|
|
122
|
+
this.start();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.remaining;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
getStateRunning = () => {
|
|
129
|
+
return this.running;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
56
133
|
function removeToast(toastID: string) {
|
|
57
134
|
const toastEl = document.getElementById(toastID);
|
|
58
135
|
|
|
@@ -66,22 +143,26 @@ const {
|
|
|
66
143
|
}
|
|
67
144
|
|
|
68
145
|
function createToast(props: ToastProps) {
|
|
69
|
-
const toastParent = document.getElementById('toast-drawer')! as HTMLDivElement;
|
|
146
|
+
const toastParent = document.getElementById('sui-toast-drawer')! as HTMLDivElement;
|
|
70
147
|
|
|
71
148
|
const toastContainer = document.createElement('div');
|
|
72
149
|
const toastID = generateID('toast');
|
|
150
|
+
toastContainer.tabIndex = 0;
|
|
151
|
+
toastContainer.ariaLive = 'polite';
|
|
152
|
+
toastContainer.role = 'alert';
|
|
73
153
|
toastContainer.id = toastID;
|
|
74
|
-
toastContainer.
|
|
154
|
+
toastContainer.ariaLabel = `${props.title} (F8)`;
|
|
155
|
+
toastContainer.classList.add('sui-toast-container', props.type, `${props.closeButton || props.persistent && "closeable"}`, `${props.persistent && 'persistent'}`);
|
|
75
156
|
|
|
76
157
|
const toastHeader = document.createElement('div');
|
|
77
|
-
toastHeader.classList.add('toast-header');
|
|
158
|
+
toastHeader.classList.add('sui-toast-header');
|
|
78
159
|
|
|
79
160
|
const toastHeaderLeftSide = document.createElement('div');
|
|
80
|
-
toastHeaderLeftSide.classList.add('toast-header-left-side')
|
|
161
|
+
toastHeaderLeftSide.classList.add('sui-toast-header-left-side')
|
|
81
162
|
|
|
82
163
|
const toastTitle = document.createElement('span');
|
|
83
164
|
toastTitle.textContent = props.title;
|
|
84
|
-
toastTitle.classList.add('toast-title');
|
|
165
|
+
toastTitle.classList.add('sui-toast-title');
|
|
85
166
|
|
|
86
167
|
let iconString: ValidIconString;
|
|
87
168
|
|
|
@@ -106,6 +187,8 @@ const {
|
|
|
106
187
|
closeIconContainer.classList.add('close-icon-container');
|
|
107
188
|
closeIconContainer.addEventListener('click', () => removeToast(toastID));
|
|
108
189
|
closeIconContainer.innerHTML = getIconString('x-mark', 'close-icon', 24, 24);
|
|
190
|
+
closeIconContainer.tabIndex = 0;
|
|
191
|
+
closeIconContainer.ariaLabel = 'Close toast';
|
|
109
192
|
|
|
110
193
|
toastHeader.appendChild(closeIconContainer);
|
|
111
194
|
}
|
|
@@ -115,14 +198,14 @@ const {
|
|
|
115
198
|
if (props.description) {
|
|
116
199
|
const toastDesc = document.createElement('span');
|
|
117
200
|
toastDesc.innerHTML = props.description;
|
|
118
|
-
toastDesc.classList.add('toast-desc');
|
|
201
|
+
toastDesc.classList.add('sui-toast-desc');
|
|
119
202
|
|
|
120
203
|
toastContainer.appendChild(toastDesc);
|
|
121
204
|
}
|
|
122
205
|
|
|
123
206
|
if (!props.persistent) {
|
|
124
207
|
const toastProgressBar = document.createElement('div');
|
|
125
|
-
toastProgressBar.classList.add('toast-progress-bar');
|
|
208
|
+
toastProgressBar.classList.add('sui-toast-progress-bar');
|
|
126
209
|
toastProgressBar.style.animationDuration = props.duration ? `${props.duration}ms` : `${toastParent.dataset.duration || 4000}ms`;
|
|
127
210
|
|
|
128
211
|
toastContainer.appendChild(toastProgressBar);
|
|
@@ -133,11 +216,43 @@ const {
|
|
|
133
216
|
activeToasts.push(toastID);
|
|
134
217
|
|
|
135
218
|
if (!props.persistent) {
|
|
136
|
-
|
|
219
|
+
let timer = new Timer(
|
|
137
220
|
() => removeToast(toastID),
|
|
138
221
|
props.duration || (toastParent.dataset.duration ? parseInt(toastParent.dataset.duration) : 4000)
|
|
139
222
|
);
|
|
223
|
+
|
|
224
|
+
const timerPauseWrapper = () => {
|
|
225
|
+
toastContainer.classList.add('paused');
|
|
226
|
+
timer.pause();
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const timerStartWrapper = () => {
|
|
230
|
+
toastContainer.classList.remove('paused');
|
|
231
|
+
timer.start();
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
toastContainer.addEventListener('mouseenter', timerPauseWrapper);
|
|
235
|
+
toastContainer.addEventListener('focusin', timerPauseWrapper);
|
|
236
|
+
|
|
237
|
+
toastContainer.addEventListener('mouseleave', timerStartWrapper);
|
|
238
|
+
toastContainer.addEventListener('focusout', () => {
|
|
239
|
+
let focusedOrHasFocused = toastContainer.matches(':focus-within');
|
|
240
|
+
|
|
241
|
+
if (!focusedOrHasFocused) {
|
|
242
|
+
revertFocusBackToLastActiveElement();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
timerStartWrapper();
|
|
246
|
+
});
|
|
140
247
|
}
|
|
248
|
+
|
|
249
|
+
toastContainer.addEventListener('keydown', (e) => {
|
|
250
|
+
if (e.key === 'Escape') {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
removeToast(toastID);
|
|
253
|
+
revertFocusBackToLastActiveElement();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
141
256
|
}
|
|
142
257
|
|
|
143
258
|
document.addEventListener('createtoast', (e) => {
|
|
@@ -147,9 +262,24 @@ const {
|
|
|
147
262
|
|
|
148
263
|
createToast(event.detail);
|
|
149
264
|
});
|
|
265
|
+
|
|
266
|
+
window.addEventListener('keydown', (e) => {
|
|
267
|
+
if (e.key === 'F8') {
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
|
|
270
|
+
const oldestToast = activeToasts[0];
|
|
271
|
+
|
|
272
|
+
if (oldestToast) {
|
|
273
|
+
lastActiveElement = document.activeElement as HTMLElement;
|
|
274
|
+
|
|
275
|
+
const toastEl = document.getElementById(oldestToast);
|
|
276
|
+
if (toastEl) toastEl?.focus();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
150
280
|
</script>
|
|
151
281
|
<style>
|
|
152
|
-
#toaster {
|
|
282
|
+
#sui-toaster {
|
|
153
283
|
width: 100vw;
|
|
154
284
|
height: 100vh;
|
|
155
285
|
position: fixed;
|
|
@@ -157,9 +287,10 @@ const {
|
|
|
157
287
|
left: 0;
|
|
158
288
|
z-index: 100;
|
|
159
289
|
pointer-events: none;
|
|
290
|
+
color: hsl(var(--text-normal));
|
|
160
291
|
}
|
|
161
292
|
|
|
162
|
-
#toast-drawer {
|
|
293
|
+
#sui-toast-drawer {
|
|
163
294
|
max-width: 420px;
|
|
164
295
|
width: 100%;
|
|
165
296
|
height: fit-content;
|
|
@@ -168,14 +299,14 @@ const {
|
|
|
168
299
|
flex-direction: column;
|
|
169
300
|
}
|
|
170
301
|
|
|
171
|
-
#toaster.top-left #toast-drawer,
|
|
172
|
-
#toaster.bottom-left #toast-drawer {
|
|
302
|
+
#sui-toaster.top-left #sui-toast-drawer,
|
|
303
|
+
#sui-toaster.bottom-left #sui-toast-drawer {
|
|
173
304
|
left: 50%;
|
|
174
305
|
transform: translateX(-50%);
|
|
175
306
|
}
|
|
176
307
|
</style>
|
|
177
308
|
<style is:global>
|
|
178
|
-
.toast-container {
|
|
309
|
+
.sui-toast-container {
|
|
179
310
|
pointer-events: all;
|
|
180
311
|
padding: 1rem;
|
|
181
312
|
border-radius: .5rem;
|
|
@@ -192,14 +323,14 @@ const {
|
|
|
192
323
|
z-index: 90;
|
|
193
324
|
}
|
|
194
325
|
|
|
195
|
-
.toast-header {
|
|
326
|
+
.sui-toast-header {
|
|
196
327
|
display: flex;
|
|
197
328
|
flex-direction: row;
|
|
198
329
|
align-items: center;
|
|
199
330
|
justify-content: space-between;
|
|
200
331
|
}
|
|
201
332
|
|
|
202
|
-
.toast-header-left-side {
|
|
333
|
+
.sui-toast-header-left-side {
|
|
203
334
|
display: flex;
|
|
204
335
|
flex-direction: row;
|
|
205
336
|
gap: .5rem;
|
|
@@ -208,23 +339,23 @@ const {
|
|
|
208
339
|
font-size: 1.125em;
|
|
209
340
|
}
|
|
210
341
|
|
|
211
|
-
.toast-header-left-side svg {
|
|
342
|
+
.sui-toast-header-left-side svg {
|
|
212
343
|
color: hsl(var(--primary-base));
|
|
213
344
|
}
|
|
214
345
|
|
|
215
|
-
.toast-container.success .toast-header-left-side svg {
|
|
346
|
+
.sui-toast-container.success .sui-toast-header-left-side svg {
|
|
216
347
|
color: hsl(var(--success-base));
|
|
217
348
|
}
|
|
218
349
|
|
|
219
|
-
.toast-container.warning .toast-header-left-side svg {
|
|
350
|
+
.sui-toast-container.warning .sui-toast-header-left-side svg {
|
|
220
351
|
color: hsl(var(--warning-base));
|
|
221
352
|
}
|
|
222
353
|
|
|
223
|
-
.toast-container.danger .toast-header-left-side svg {
|
|
354
|
+
.sui-toast-container.danger .sui-toast-header-left-side svg {
|
|
224
355
|
color: hsl(var(--danger-base));
|
|
225
356
|
}
|
|
226
357
|
|
|
227
|
-
.toast-progress-bar {
|
|
358
|
+
.sui-toast-progress-bar {
|
|
228
359
|
position: absolute;
|
|
229
360
|
height: 4px;
|
|
230
361
|
width: 100%;
|
|
@@ -234,15 +365,19 @@ const {
|
|
|
234
365
|
animation: toast-progress forwards linear;
|
|
235
366
|
}
|
|
236
367
|
|
|
237
|
-
.toast-container.
|
|
368
|
+
.sui-toast-container.paused .sui-toast-progress-bar {
|
|
369
|
+
animation-play-state: paused;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.sui-toast-container.success .sui-toast-progress-bar {
|
|
238
373
|
background-color: hsl(var(--success-base));
|
|
239
374
|
}
|
|
240
375
|
|
|
241
|
-
.toast-container.warning .toast-progress-bar {
|
|
376
|
+
.sui-toast-container.warning .sui-toast-progress-bar {
|
|
242
377
|
background-color: hsl(var(--warning-base));
|
|
243
378
|
}
|
|
244
379
|
|
|
245
|
-
.toast-container.danger .toast-progress-bar {
|
|
380
|
+
.sui-toast-container.danger .sui-toast-progress-bar {
|
|
246
381
|
background-color: hsl(var(--danger-base));
|
|
247
382
|
}
|
|
248
383
|
|
|
@@ -261,23 +396,28 @@ const {
|
|
|
261
396
|
background-color: hsl(var(--default-base));
|
|
262
397
|
}
|
|
263
398
|
|
|
264
|
-
.
|
|
399
|
+
.close-icon-container:focus-visible {
|
|
400
|
+
outline: 2px solid hsl(var(--text-normal));
|
|
401
|
+
outline-offset: 2px;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.sui-toast-container.closing {
|
|
265
405
|
animation: toast-closing .25s ease forwards;
|
|
266
406
|
}
|
|
267
407
|
|
|
268
|
-
.toast-container.persistent {
|
|
408
|
+
.sui-toast-container.persistent {
|
|
269
409
|
border: 1px solid hsl(var(--primary-base));
|
|
270
410
|
}
|
|
271
411
|
|
|
272
|
-
.toast-container.persistent.success {
|
|
412
|
+
.sui-toast-container.persistent.success {
|
|
273
413
|
border: 1px solid hsl(var(--success-base));
|
|
274
414
|
}
|
|
275
415
|
|
|
276
|
-
.toast-container.persistent.warning {
|
|
416
|
+
.sui-toast-container.persistent.warning {
|
|
277
417
|
border: 1px solid hsl(var(--warning-base));
|
|
278
418
|
}
|
|
279
419
|
|
|
280
|
-
.toast-container.persistent.danger {
|
|
420
|
+
.sui-toast-container.persistent.danger {
|
|
281
421
|
border: 1px solid hsl(var(--danger-base));
|
|
282
422
|
}
|
|
283
423
|
|
|
@@ -2,15 +2,39 @@
|
|
|
2
2
|
import type { StudioCMSColorway } from '../utils/colors';
|
|
3
3
|
import { generateID } from '../utils/generateID';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* The props for the toggle component
|
|
7
|
+
*/
|
|
5
8
|
interface Props {
|
|
9
|
+
/**
|
|
10
|
+
* The label of the toggle.
|
|
11
|
+
*/
|
|
6
12
|
label: string;
|
|
13
|
+
/**
|
|
14
|
+
* The size of the toggle. Defaults to `md`.
|
|
15
|
+
*/
|
|
7
16
|
size?: 'sm' | 'md' | 'lg';
|
|
17
|
+
/**
|
|
18
|
+
* The color of the toggle. Defaults to `default`.
|
|
19
|
+
*/
|
|
8
20
|
color?: StudioCMSColorway;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the toggle is checked by default. Defaults to `false`.
|
|
23
|
+
*/
|
|
9
24
|
defaultChecked?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the toggle is disabled. Defaults to `false`.
|
|
27
|
+
*/
|
|
10
28
|
disabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The name of the toggle.
|
|
31
|
+
*/
|
|
11
32
|
name?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the toggle is required. Defaults to `false`.
|
|
35
|
+
*/
|
|
12
36
|
isRequired?: boolean;
|
|
13
|
-
}
|
|
37
|
+
}
|
|
14
38
|
|
|
15
39
|
const {
|
|
16
40
|
size = 'md',
|
|
@@ -23,7 +47,7 @@ const {
|
|
|
23
47
|
} = Astro.props;
|
|
24
48
|
---
|
|
25
49
|
<label
|
|
26
|
-
class="toggle-label"
|
|
50
|
+
class="sui-toggle-label"
|
|
27
51
|
for={name}
|
|
28
52
|
class:list={[
|
|
29
53
|
disabled && "disabled",
|
|
@@ -31,8 +55,14 @@ const {
|
|
|
31
55
|
size,
|
|
32
56
|
]}
|
|
33
57
|
>
|
|
34
|
-
<div class="toggle-container">
|
|
35
|
-
<div
|
|
58
|
+
<div class="sui-toggle-container">
|
|
59
|
+
<div
|
|
60
|
+
class="sui-toggle-switch"
|
|
61
|
+
tabindex="0"
|
|
62
|
+
role="checkbox"
|
|
63
|
+
aria-checked={defaultChecked}
|
|
64
|
+
aria-label={label}
|
|
65
|
+
/>
|
|
36
66
|
<input
|
|
37
67
|
type="checkbox"
|
|
38
68
|
name={name}
|
|
@@ -40,15 +70,48 @@ const {
|
|
|
40
70
|
checked={defaultChecked}
|
|
41
71
|
disabled={disabled}
|
|
42
72
|
required={isRequired}
|
|
43
|
-
class="checkbox"
|
|
73
|
+
class="sui-toggle-checkbox"
|
|
74
|
+
hidden
|
|
44
75
|
/>
|
|
45
76
|
</div>
|
|
46
|
-
<span>
|
|
77
|
+
<span id={`label-${name}`}>
|
|
47
78
|
{label} <span class="req-star">{isRequired && "*"}</span>
|
|
48
79
|
</span>
|
|
49
80
|
</label>
|
|
81
|
+
<script>
|
|
82
|
+
const elements = document.querySelectorAll<HTMLDivElement>('.sui-toggle-container');
|
|
83
|
+
const toggles = document.querySelectorAll<HTMLInputElement>('.sui-toggle-checkbox');
|
|
84
|
+
|
|
85
|
+
for (const element of elements) {
|
|
86
|
+
if (element.dataset.initialized) continue;
|
|
87
|
+
|
|
88
|
+
element.dataset.initialized = 'true';
|
|
89
|
+
|
|
90
|
+
element.addEventListener('keydown', (e) => {
|
|
91
|
+
if (e.key !== 'Enter' && e.key !== ' ') return;
|
|
92
|
+
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
|
|
95
|
+
const checkbox = element.querySelector<HTMLInputElement>('.sui-toggle-checkbox');
|
|
96
|
+
|
|
97
|
+
if (!checkbox) return;
|
|
98
|
+
|
|
99
|
+
checkbox.click();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const box of toggles) {
|
|
104
|
+
if (box.dataset.initialized) continue;
|
|
105
|
+
|
|
106
|
+
box.dataset.initialized = 'true';
|
|
107
|
+
|
|
108
|
+
box.addEventListener('change', (e) => {
|
|
109
|
+
(box.previousSibling as HTMLDivElement).ariaChecked = (e.target as HTMLInputElement).checked ? 'true' : 'false';
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
50
113
|
<style>
|
|
51
|
-
.toggle-label {
|
|
114
|
+
.sui-toggle-label {
|
|
52
115
|
display: flex;
|
|
53
116
|
flex-direction: row;
|
|
54
117
|
align-items: center;
|
|
@@ -57,17 +120,17 @@ const {
|
|
|
57
120
|
margin: .25rem 0;
|
|
58
121
|
}
|
|
59
122
|
|
|
60
|
-
.toggle-label.disabled {
|
|
123
|
+
.sui-toggle-label.disabled {
|
|
61
124
|
opacity: 0.5;
|
|
62
125
|
pointer-events: none;
|
|
63
126
|
color: hsl(var(--text-muted));
|
|
64
127
|
}
|
|
65
128
|
|
|
66
|
-
.toggle-label:active .toggle-switch {
|
|
129
|
+
.sui-toggle-label:active .sui-toggle-switch {
|
|
67
130
|
transform: scale(0.85);
|
|
68
131
|
}
|
|
69
132
|
|
|
70
|
-
.toggle-container {
|
|
133
|
+
.sui-toggle-container {
|
|
71
134
|
--toggle-height: 12px;
|
|
72
135
|
--toggle-width: 40px;
|
|
73
136
|
display: flex;
|
|
@@ -80,7 +143,7 @@ const {
|
|
|
80
143
|
border-radius: var(--toggle-height);
|
|
81
144
|
}
|
|
82
145
|
|
|
83
|
-
.toggle-switch {
|
|
146
|
+
.sui-toggle-switch {
|
|
84
147
|
--switch: calc(var(--toggle-height) * 1.75);
|
|
85
148
|
height: var(--switch);
|
|
86
149
|
width: var(--switch);
|
|
@@ -92,42 +155,47 @@ const {
|
|
|
92
155
|
will-change: transform;
|
|
93
156
|
}
|
|
94
157
|
|
|
95
|
-
.toggle-
|
|
158
|
+
.sui-toggle-switch:focus-visible {
|
|
159
|
+
outline: 2px solid hsl(var(--text-normal));
|
|
160
|
+
outline-offset: 2px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.sui-toggle-container:has(.sui-toggle-checkbox:checked) .sui-toggle-switch {
|
|
96
164
|
left: calc(100% - var(--switch));
|
|
97
165
|
background-color: hsl(var(--text-normal));
|
|
98
166
|
}
|
|
99
167
|
|
|
100
|
-
.toggle-label.sm .toggle-container {
|
|
168
|
+
.sui-toggle-label.sm .sui-toggle-container {
|
|
101
169
|
--toggle-height: 10px;
|
|
102
170
|
--toggle-width: 32px;
|
|
103
171
|
}
|
|
104
172
|
|
|
105
|
-
.toggle-label.sm .toggle-switch {
|
|
173
|
+
.sui-toggle-label.sm .sui-toggle-switch {
|
|
106
174
|
--switch: calc(var(--toggle-height) * 1.65);
|
|
107
175
|
}
|
|
108
176
|
|
|
109
|
-
.toggle-label.lg .toggle-container {
|
|
177
|
+
.sui-toggle-label.lg .sui-toggle-container {
|
|
110
178
|
--toggle-height: 16px;
|
|
111
179
|
--toggle-width: 48px;
|
|
112
180
|
}
|
|
113
181
|
|
|
114
|
-
.toggle-label.lg .toggle-switch {
|
|
182
|
+
.sui-toggle-label.lg .sui-toggle-switch {
|
|
115
183
|
--switch: calc(var(--toggle-height) * 1.65);
|
|
116
184
|
}
|
|
117
185
|
|
|
118
|
-
.toggle-label.primary .toggle-container:has(.checkbox:checked) {
|
|
186
|
+
.sui-toggle-label.primary .sui-toggle-container:has(.sui-toggle-checkbox:checked) {
|
|
119
187
|
background-color: hsl(var(--primary-base));
|
|
120
188
|
}
|
|
121
189
|
|
|
122
|
-
.toggle-label.success .toggle-container:has(.checkbox:checked) {
|
|
190
|
+
.sui-toggle-label.success .sui-toggle-container:has(.sui-toggle-checkbox:checked) {
|
|
123
191
|
background-color: hsl(var(--success-base));
|
|
124
192
|
}
|
|
125
193
|
|
|
126
|
-
.toggle-label.warning .toggle-container:has(.checkbox:checked) {
|
|
194
|
+
.sui-toggle-label.warning .sui-toggle-container:has(.sui-toggle-checkbox:checked) {
|
|
127
195
|
background-color: hsl(var(--warning-base));
|
|
128
196
|
}
|
|
129
197
|
|
|
130
|
-
.toggle-label.danger .toggle-container:has(.checkbox:checked) {
|
|
198
|
+
.sui-toggle-label.danger .sui-toggle-container:has(.sui-toggle-checkbox:checked) {
|
|
131
199
|
background-color: hsl(var(--danger-base));
|
|
132
200
|
}
|
|
133
201
|
|
|
@@ -136,7 +204,7 @@ const {
|
|
|
136
204
|
font-weight: 700;
|
|
137
205
|
}
|
|
138
206
|
|
|
139
|
-
.checkbox {
|
|
207
|
+
.sui-toggle-checkbox {
|
|
140
208
|
width: 0;
|
|
141
209
|
height: 0;
|
|
142
210
|
visibility: hidden;
|