svelte-toggle-switch 1.0.0 → 2.0.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.
@@ -1,259 +1,692 @@
1
- <script>
1
+ <script lang="ts">
2
+ // Core Props
3
+ export let value: boolean | string = false;
4
+ export let label: string = '';
5
+ export let design: 'inner' | 'slider' | 'modern' | 'ios' | 'material' | 'multi' = 'slider';
6
+
7
+ // Multi-option props (for multi design)
8
+ export let options: string[] = [];
9
+
10
+ // Styling Props
11
+ export let size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number = 'md';
12
+ export let color: string = '#007AFF';
13
+ export let offColor: string = '#E5E7EB';
14
+ export let colorScheme: 'blue' | 'green' | 'red' | 'purple' | 'orange' | 'pink' | 'custom' = 'blue';
15
+
16
+ // State Props
17
+ export let disabled: boolean = false;
18
+ export let loading: boolean = false;
19
+ export let readonly: boolean = false;
20
+
21
+ // Icon Props
22
+ export let showIcons: boolean = false;
23
+ export let onIcon: string = '✓';
24
+ export let offIcon: string = '✕';
25
+
26
+ // Animation Props
27
+ export let animationDuration: number = 300;
28
+ export let animationEasing: string = 'ease-in-out';
29
+
30
+ // Accessibility Props
31
+ export let ariaLabel: string = '';
32
+ export let ariaDescribedBy: string = '';
33
+ export let id: string = '';
34
+
35
+ // Advanced Props
36
+ export let labelPosition: 'left' | 'right' = 'right';
37
+ export let rounded: boolean = true;
38
+ export let shadow: boolean = false;
39
+ export let outline: boolean = false;
40
+
41
+ // Internal state
42
+ let checked: boolean = typeof value === 'boolean' ? value : value === 'on';
43
+ const uniqueID = id || `switch-${Math.floor(Math.random() * 1000000)}`;
44
+
45
+ // Color schemes
46
+ const colorSchemes = {
47
+ blue: '#007AFF',
48
+ green: '#10B981',
49
+ red: '#EF4444',
50
+ purple: '#8B5CF6',
51
+ orange: '#F97316',
52
+ pink: '#EC4899',
53
+ custom: color
54
+ };
55
+
56
+ const activeColor = colorScheme === 'custom' ? color : colorSchemes[colorScheme];
57
+
58
+ // Size variants (in rem)
59
+ const sizeMap = {
60
+ xs: 0.75,
61
+ sm: 0.875,
62
+ md: 1,
63
+ lg: 1.25,
64
+ xl: 1.5
65
+ };
66
+
67
+ const fontSize = typeof size === 'number' ? size : sizeMap[size];
68
+
69
+ // Sync checked state with value prop (one-way: value -> checked)
70
+ $: if (design !== 'multi') {
71
+ const newChecked = typeof value === 'boolean' ? value : value === 'on';
72
+ if (newChecked !== checked) {
73
+ checked = newChecked;
74
+ }
75
+ }
2
76
 
3
- export let label;
4
- export let design = 'inner label'
5
- export let options = [];
6
- export let fontSize = 16;
7
- export let value = 'on';
77
+ function handleClick(event: MouseEvent) {
78
+ if (disabled || loading || readonly) {
79
+ event.preventDefault();
80
+ return;
81
+ }
82
+
83
+ if (design !== 'multi') {
84
+ const newChecked = !checked;
85
+ checked = newChecked;
86
+ value = newChecked ? (typeof value === 'boolean' ? true : 'on') : (typeof value === 'boolean' ? false : 'off');
87
+ }
88
+ }
8
89
 
9
- let checked = true;
10
- const uniqueID = Math.floor(Math.random() * 100)
90
+ function handleKeyDown(event: KeyboardEvent) {
91
+ if (disabled || loading || readonly) return;
92
+
93
+ if (event.key === ' ' || event.key === 'Enter') {
94
+ event.preventDefault();
95
+ if (design !== 'multi') {
96
+ const newChecked = !checked;
97
+ checked = newChecked;
98
+ value = newChecked ? (typeof value === 'boolean' ? true : 'on') : (typeof value === 'boolean' ? false : 'off');
99
+ }
100
+ }
101
+ }
102
+ </script>
11
103
 
12
- function handleClick(event){
13
- const target = event.target
104
+ {#if design === 'inner'}
105
+ <div
106
+ class="switch-container"
107
+ class:disabled
108
+ class:readonly
109
+ style="font-size: {fontSize}rem;"
110
+ >
111
+ {#if label && labelPosition === 'left'}
112
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
113
+ {/if}
114
+ <button
115
+ type="button"
116
+ role="switch"
117
+ aria-checked={checked}
118
+ aria-label={ariaLabel || label}
119
+ aria-labelledby={label ? `${uniqueID}-label` : undefined}
120
+ aria-describedby={ariaDescribedBy || undefined}
121
+ {disabled}
122
+ class="switch switch--inner"
123
+ class:checked
124
+ class:loading
125
+ class:rounded
126
+ class:shadow
127
+ class:outline
128
+ on:click={handleClick}
129
+ on:keydown={handleKeyDown}
130
+ style="
131
+ --active-color: {activeColor};
132
+ --off-color: {offColor};
133
+ --animation-duration: {animationDuration}ms;
134
+ --animation-easing: {animationEasing};
135
+ "
136
+ >
137
+ {#if loading}
138
+ <span class="spinner"></span>
139
+ {:else}
140
+ <span class="switch-text">{checked ? 'ON' : 'OFF'}</span>
141
+ {/if}
142
+ </button>
143
+ {#if label && labelPosition === 'right'}
144
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
145
+ {/if}
146
+ </div>
147
+
148
+ {:else if design === 'slider' || design === 'ios'}
149
+ <div
150
+ class="switch-container"
151
+ class:disabled
152
+ class:readonly
153
+ style="font-size: {fontSize}rem;"
154
+ >
155
+ {#if label && labelPosition === 'left'}
156
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
157
+ {/if}
158
+ <button
159
+ type="button"
160
+ role="switch"
161
+ aria-checked={checked}
162
+ aria-label={ariaLabel || label}
163
+ aria-labelledby={label ? `${uniqueID}-label` : undefined}
164
+ aria-describedby={ariaDescribedBy || undefined}
165
+ {disabled}
166
+ class="switch switch--slider"
167
+ class:checked
168
+ class:loading
169
+ class:shadow
170
+ class:outline
171
+ on:click={handleClick}
172
+ on:keydown={handleKeyDown}
173
+ style="
174
+ --active-color: {activeColor};
175
+ --off-color: {offColor};
176
+ --animation-duration: {animationDuration}ms;
177
+ --animation-easing: {animationEasing};
178
+ "
179
+ >
180
+ <span class="switch-track">
181
+ <span class="switch-thumb">
182
+ {#if loading}
183
+ <span class="spinner-small"></span>
184
+ {:else if showIcons}
185
+ <span class="switch-icon">{checked ? onIcon : offIcon}</span>
186
+ {/if}
187
+ </span>
188
+ </span>
189
+ </button>
190
+ {#if label && labelPosition === 'right'}
191
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
192
+ {/if}
193
+ </div>
194
+
195
+ {:else if design === 'modern'}
196
+ <div
197
+ class="switch-container"
198
+ class:disabled
199
+ class:readonly
200
+ style="font-size: {fontSize}rem;"
201
+ >
202
+ {#if label && labelPosition === 'left'}
203
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
204
+ {/if}
205
+ <button
206
+ type="button"
207
+ role="switch"
208
+ aria-checked={checked}
209
+ aria-label={ariaLabel || label}
210
+ aria-labelledby={label ? `${uniqueID}-label` : undefined}
211
+ aria-describedby={ariaDescribedBy || undefined}
212
+ {disabled}
213
+ class="switch switch--modern"
214
+ class:checked
215
+ class:loading
216
+ class:shadow
217
+ class:outline
218
+ on:click={handleClick}
219
+ on:keydown={handleKeyDown}
220
+ style="
221
+ --active-color: {activeColor};
222
+ --off-color: {offColor};
223
+ --animation-duration: {animationDuration}ms;
224
+ --animation-easing: {animationEasing};
225
+ "
226
+ >
227
+ <span class="switch-track">
228
+ <span class="switch-thumb-modern">
229
+ {#if loading}
230
+ <span class="spinner-small"></span>
231
+ {:else if showIcons}
232
+ <span class="switch-icon-modern">{checked ? onIcon : offIcon}</span>
233
+ {/if}
234
+ </span>
235
+ {#if !loading && showIcons}
236
+ <span class="track-icons">
237
+ <span class="track-icon track-icon--on">{onIcon}</span>
238
+ <span class="track-icon track-icon--off">{offIcon}</span>
239
+ </span>
240
+ {/if}
241
+ </span>
242
+ </button>
243
+ {#if label && labelPosition === 'right'}
244
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
245
+ {/if}
246
+ </div>
247
+
248
+ {:else if design === 'material'}
249
+ <div
250
+ class="switch-container"
251
+ class:disabled
252
+ class:readonly
253
+ style="font-size: {fontSize}rem;"
254
+ >
255
+ {#if label && labelPosition === 'left'}
256
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
257
+ {/if}
258
+ <button
259
+ type="button"
260
+ role="switch"
261
+ aria-checked={checked}
262
+ aria-label={ariaLabel || label}
263
+ aria-labelledby={label ? `${uniqueID}-label` : undefined}
264
+ aria-describedby={ariaDescribedBy || undefined}
265
+ {disabled}
266
+ class="switch switch--material"
267
+ class:checked
268
+ class:loading
269
+ class:shadow
270
+ class:outline
271
+ on:click={handleClick}
272
+ on:keydown={handleKeyDown}
273
+ style="
274
+ --active-color: {activeColor};
275
+ --off-color: {offColor};
276
+ --animation-duration: {animationDuration}ms;
277
+ --animation-easing: {animationEasing};
278
+ "
279
+ >
280
+ <span class="switch-track-material">
281
+ <span class="switch-thumb-material">
282
+ {#if loading}
283
+ <span class="spinner-small"></span>
284
+ {/if}
285
+ </span>
286
+ </span>
287
+ </button>
288
+ {#if label && labelPosition === 'right'}
289
+ <span class="switch-label" id="{uniqueID}-label">{label}</span>
290
+ {/if}
291
+ </div>
292
+
293
+ {:else if design === 'multi'}
294
+ <div
295
+ class="switch-container switch-container--multi"
296
+ class:disabled
297
+ class:readonly
298
+ style="font-size: {fontSize}rem;"
299
+ >
300
+ <div
301
+ role="radiogroup"
302
+ class="switch-multi"
303
+ aria-labelledby="{uniqueID}-legend"
304
+ aria-describedby={ariaDescribedBy || undefined}
305
+ style="
306
+ --active-color: {activeColor};
307
+ --off-color: {offColor};
308
+ --animation-duration: {animationDuration}ms;
309
+ --animation-easing: {animationEasing};
310
+ "
311
+ >
312
+ {#if label}
313
+ <div class="switch-multi-legend" id="{uniqueID}-legend">{label}</div>
314
+ {/if}
315
+ <div class="switch-multi-options" class:shadow class:outline>
316
+ {#each options as option, index}
317
+ <input
318
+ type="radio"
319
+ id="{uniqueID}-{option}"
320
+ value={option}
321
+ bind:group={value}
322
+ {disabled}
323
+ class="switch-multi-input"
324
+ />
325
+ <label
326
+ for="{uniqueID}-{option}"
327
+ class="switch-multi-label"
328
+ class:first={index === 0}
329
+ class:last={index === options.length - 1}
330
+ >
331
+ {option}
332
+ </label>
333
+ {/each}
334
+ </div>
335
+ </div>
336
+ </div>
337
+ {/if}
14
338
 
15
- const state = target.getAttribute('aria-checked')
339
+ <style>
340
+ :root {
341
+ --active-color: #007AFF;
342
+ --off-color: #E5E7EB;
343
+ --animation-duration: 300ms;
344
+ --animation-easing: ease-in-out;
345
+ }
16
346
 
17
- checked = state === 'true' ? false : true
347
+ /* Container */
348
+ .switch-container {
349
+ display: inline-flex;
350
+ align-items: center;
351
+ gap: 0.75em;
352
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
353
+ }
18
354
 
19
- value = checked === true ? 'on' : 'off'
20
- }
21
-
22
- const slugify = (str = "") =>
23
- str.toLowerCase().replace(/ /g, "-").replace(/\./g, "");
355
+ .switch-container.disabled {
356
+ opacity: 0.5;
357
+ cursor: not-allowed;
358
+ }
24
359
 
25
- </script>
360
+ .switch-container.readonly {
361
+ cursor: default;
362
+ }
26
363
 
27
- {#if design == 'inner'}
28
- <div class="s s--inner">
29
- <span id={`switch-${uniqueID}`}>{label}</span>
30
- <button
31
- role="switch"
32
- aria-checked={checked}
33
- aria-labelledby={`switch-${uniqueID}`}
34
- on:click={handleClick}>
35
- <span>on</span>
36
- <span>off</span>
37
- </button>
38
- </div>
39
- {:else if design == 'slider'}
40
- <div class="s s--slider" style="font-size:{fontSize}px">
41
- <span id={`switch-${uniqueID}`}>{label}</span>
42
- <button
43
- role="switch"
44
- aria-checked={checked}
45
- aria-labelledby={`switch-${uniqueID}`}
46
- on:click={handleClick}>
47
- </button>
48
- </div>
49
- {:else}
50
- <div class="s s--multi">
51
- <div role='radiogroup'
52
- class="group-container"
53
- aria-labelledby={`label-${uniqueID}`}
54
- style="font-size:{fontSize}px"
55
- id={`group-${uniqueID}`}>
56
- <div class='legend' id={`label-${uniqueID}`}>{label}</div>
57
- {#each options as option}
58
- <input type="radio" id={`${option}-${uniqueID}`} value={option} bind:group={value}>
59
- <label for={`${option}-${uniqueID}`}>
60
- {option}
61
- </label>
62
- {/each}
63
- </div>
64
- </div>
364
+ .switch-label {
365
+ user-select: none;
366
+ font-size: 1em;
367
+ color: #374151;
368
+ }
65
369
 
66
- {/if}
370
+ /* Base Switch */
371
+ .switch {
372
+ position: relative;
373
+ border: none;
374
+ cursor: pointer;
375
+ transition: all var(--animation-duration) var(--animation-easing);
376
+ font-family: inherit;
377
+ }
67
378
 
68
- <style>
69
- :root {
70
- --accent-color: CornflowerBlue;
71
- --gray: #ccc;
72
- }
73
- /* Inner Design Option */
74
- .s--inner button {
75
- padding: 0.5em;
76
- background-color: #fff;
77
- border: 1px solid var(--gray);
78
- }
79
- [role='switch'][aria-checked='true'] :first-child,
80
- [role='switch'][aria-checked='false'] :last-child {
81
- display: none;
82
- color: #fff;
83
- }
84
-
85
- .s--inner button span {
86
- user-select: none;
87
- pointer-events:none;
88
- padding: 0.25em;
89
- }
90
-
91
- .s--inner button:focus {
92
- outline: var(--accent-color) solid 1px;
93
- }
94
-
95
- /* Slider Design Option */
96
-
97
- .s--slider {
98
- display: flex;
99
- align-items: center;
100
- }
101
-
102
- .s--slider button {
103
- width: 3em;
104
- height: 1.6em;
105
- position: relative;
106
- margin: 0 0 0 0.5em;
107
- background: var(--gray);
108
- border: none;
109
- }
110
-
111
- .s--slider button::before {
112
- content: '';
113
- position: absolute;
114
- width: 1.3em;
115
- height: 1.3em;
116
- background: #fff;
117
- top: 0.13em;
118
- right: 1.5em;
119
- transition: transform 0.3s;
120
- }
121
-
122
- .s--slider button[aria-checked='true']{
123
- background-color: var(--accent-color)
124
- }
125
-
126
- .s--slider button[aria-checked='true']::before{
127
- transform: translateX(1.3em);
128
- transition: transform 0.3s;
129
- }
130
-
131
- .s--slider button:focus {
132
- box-shadow: 0 0px 0px 1px var(--accent-color);
133
- }
134
-
135
- /* Multi Design Option */
136
-
137
- /* Based on suggestions from Sara Soueidan https://www.sarasoueidan.com/blog/toggle-switch-design/
138
- and this example from Scott O'hara https://codepen.io/scottohara/pen/zLZwNv */
139
-
140
- .s--multi .group-container {
141
- border: none;
142
- padding: 0;
143
- white-space: nowrap;
144
- }
145
-
146
- /* .s--multi legend {
147
- font-size: 2px;
148
- opacity: 0;
149
- position: absolute;
150
- } */
151
-
152
- .s--multi label {
153
- display: inline-block;
154
- line-height: 1.6;
155
- position: relative;
156
- z-index: 2;
157
- }
158
-
159
- .s--multi input {
160
- opacity: 0;
161
- position: absolute;
162
- }
163
-
164
- .s--multi label:first-of-type {
165
- padding-right: 5em;
166
- }
167
-
168
- .s--multi label:last-child {
169
- margin-left: -5em;
170
- padding-left: 5em;
171
- }
172
-
173
- .s--multi:focus-within label:first-of-type:after {
174
- box-shadow: 0 0px 8px var(--accent-color);
175
- border-radius: 1.5em;
176
- }
177
-
178
-
179
-
180
- /* making the switch UI. */
181
- .s--multi label:first-of-type:before,
182
- .s--multi label:first-of-type:after {
183
- content: "";
184
- height: 1.25em;
185
- overflow: hidden;
186
- pointer-events: none;
187
- position: absolute;
188
- vertical-align: middle;
189
- }
190
-
191
- .s--multi label:first-of-type:before {
192
- border-radius: 100%;
193
- z-index: 2;
194
- position: absolute;
195
- width: 1.2em;
196
- height: 1.2em;
197
- background: #fff;
198
- top: 0.2em;
199
- right: 1.2em;
200
- transition: transform 0.3s;
201
- }
202
-
203
- .s--multi label:first-of-type:after {
204
- background: var(--accent-color);
205
- border-radius: 1em;
206
- margin: 0 1em;
207
- transition: background .2s ease-in-out;
208
- width: 3em;
209
- height: 1.6em;
210
- }
211
-
212
- .s--multi input:first-of-type:checked ~ label:first-of-type:after {
213
- background: var(--gray);
214
- }
215
-
216
- .s--multi input:first-of-type:checked ~ label:first-of-type:before {
217
- transform: translateX(-1.4em);
218
- }
219
-
220
- .s--multi input:last-of-type:checked ~ label:last-of-type {
221
- z-index: 1;
222
- }
223
-
224
- .s--multi input:focus {
225
- box-shadow: 0 0px 8px var(--accent-color);
226
- border-radius: 1.5em;
227
- }
228
-
229
- /* gravy */
230
-
231
- /* Inner Design Option */
232
- [role='switch'][aria-checked='true'] :first-child,
233
- [role='switch'][aria-checked='false'] :last-child {
234
- border-radius: 0.25em;
235
- background: var(--accent-color);
236
- display: inline-block;
237
- }
238
-
239
- .s--inner button:focus {
240
- box-shadow: 0 0px 8px var(--accent-color);
241
- border-radius: 0.1em;
242
- }
243
-
244
- /* Slider Design Option */
245
- .s--slider button {
246
- border-radius: 1.5em;
247
- }
248
-
249
- .s--slider button::before {
250
- border-radius: 100%;
251
- }
252
-
253
- .s--slider button:focus {
254
- box-shadow: 0 0px 8px var(--accent-color);
255
- border-radius: 1.5em;
256
- }
257
-
258
-
259
- </style>
379
+ .switch:disabled {
380
+ cursor: not-allowed;
381
+ }
382
+
383
+ .switch:focus-visible {
384
+ outline: 2px solid var(--active-color);
385
+ outline-offset: 2px;
386
+ }
387
+
388
+ .switch.shadow {
389
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
390
+ }
391
+
392
+ .switch.outline {
393
+ border: 1px solid #D1D5DB;
394
+ }
395
+
396
+ /* Inner Design */
397
+ .switch--inner {
398
+ padding: 0.4em 0.8em;
399
+ background-color: var(--off-color);
400
+ color: #6B7280;
401
+ font-weight: 500;
402
+ font-size: 0.875em;
403
+ min-width: 4em;
404
+ border-radius: 0.25em;
405
+ }
406
+
407
+ .switch--inner.rounded {
408
+ border-radius: 0.5em;
409
+ }
410
+
411
+ .switch--inner.checked {
412
+ background-color: var(--active-color);
413
+ color: white;
414
+ }
415
+
416
+ .switch--inner .switch-text {
417
+ display: block;
418
+ user-select: none;
419
+ pointer-events: none;
420
+ }
421
+
422
+ /* Slider/iOS Design */
423
+ .switch--slider {
424
+ padding: 0;
425
+ background: transparent;
426
+ width: 3.5em;
427
+ height: 2em;
428
+ }
429
+
430
+ .switch-track {
431
+ position: relative;
432
+ display: block;
433
+ width: 100%;
434
+ height: 100%;
435
+ background-color: var(--off-color);
436
+ border-radius: 1em;
437
+ transition: background-color var(--animation-duration) var(--animation-easing);
438
+ }
439
+
440
+ .switch--slider.checked .switch-track {
441
+ background-color: var(--active-color);
442
+ }
443
+
444
+ .switch-thumb {
445
+ position: absolute;
446
+ top: 0.15em;
447
+ left: 0.15em;
448
+ width: 1.7em;
449
+ height: 1.7em;
450
+ background-color: white;
451
+ border-radius: 50%;
452
+ transition: transform var(--animation-duration) var(--animation-easing);
453
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
454
+ display: flex;
455
+ align-items: center;
456
+ justify-content: center;
457
+ }
458
+
459
+ .switch--slider.checked .switch-thumb {
460
+ transform: translateX(1.5em);
461
+ }
462
+
463
+ .switch-icon {
464
+ font-size: 0.75em;
465
+ user-select: none;
466
+ }
467
+
468
+ /* Modern Design */
469
+ .switch--modern {
470
+ padding: 0;
471
+ background: transparent;
472
+ width: 4em;
473
+ height: 2.2em;
474
+ }
475
+
476
+ .switch--modern .switch-track {
477
+ border-radius: 1.1em;
478
+ }
479
+
480
+ .switch-thumb-modern {
481
+ position: absolute;
482
+ top: 0.2em;
483
+ left: 0.2em;
484
+ width: 1.8em;
485
+ height: 1.8em;
486
+ background-color: white;
487
+ border-radius: 50%;
488
+ transition: transform var(--animation-duration) var(--animation-easing), background-color var(--animation-duration) var(--animation-easing);
489
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
490
+ display: flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ }
494
+
495
+ .switch--modern.checked .switch-thumb-modern {
496
+ transform: translateX(1.8em);
497
+ }
498
+
499
+ .track-icons {
500
+ position: absolute;
501
+ top: 0;
502
+ left: 0;
503
+ width: 100%;
504
+ height: 100%;
505
+ display: flex;
506
+ align-items: center;
507
+ justify-content: space-between;
508
+ padding: 0 0.5em;
509
+ pointer-events: none;
510
+ }
511
+
512
+ .track-icon {
513
+ font-size: 0.7em;
514
+ color: white;
515
+ opacity: 0;
516
+ transition: opacity var(--animation-duration) var(--animation-easing);
517
+ }
518
+
519
+ .switch--modern.checked .track-icon--on {
520
+ opacity: 1;
521
+ }
522
+
523
+ .switch--modern:not(.checked) .track-icon--off {
524
+ opacity: 0.7;
525
+ }
526
+
527
+ .switch-icon-modern {
528
+ font-size: 0.8em;
529
+ user-select: none;
530
+ color: var(--active-color);
531
+ }
532
+
533
+ /* Material Design */
534
+ .switch--material {
535
+ padding: 0;
536
+ background: transparent;
537
+ width: 3.5em;
538
+ height: 1.5em;
539
+ }
540
+
541
+ .switch-track-material {
542
+ position: relative;
543
+ display: block;
544
+ width: 100%;
545
+ height: 100%;
546
+ background-color: var(--off-color);
547
+ border-radius: 0.75em;
548
+ transition: background-color var(--animation-duration) var(--animation-easing);
549
+ }
550
+
551
+ .switch--material.checked .switch-track-material {
552
+ background-color: var(--active-color);
553
+ opacity: 0.5;
554
+ }
555
+
556
+ .switch-thumb-material {
557
+ position: absolute;
558
+ top: 50%;
559
+ left: 0;
560
+ transform: translateY(-50%);
561
+ width: 1.5em;
562
+ height: 1.5em;
563
+ background-color: #FAFAFA;
564
+ border-radius: 50%;
565
+ transition: transform var(--animation-duration) var(--animation-easing), background-color var(--animation-duration) var(--animation-easing);
566
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
567
+ display: flex;
568
+ align-items: center;
569
+ justify-content: center;
570
+ }
571
+
572
+ .switch--material.checked .switch-thumb-material {
573
+ transform: translateY(-50%) translateX(2em);
574
+ background-color: var(--active-color);
575
+ }
576
+
577
+ /* Multi Design */
578
+ .switch-container--multi {
579
+ display: block;
580
+ }
581
+
582
+ .switch-multi {
583
+ display: inline-block;
584
+ }
585
+
586
+ .switch-multi-legend {
587
+ font-size: 0.9em;
588
+ color: #374151;
589
+ margin-bottom: 0.5em;
590
+ font-weight: 500;
591
+ }
592
+
593
+ .switch-multi-options {
594
+ display: inline-flex;
595
+ background-color: var(--off-color);
596
+ border-radius: 0.5em;
597
+ padding: 0.25em;
598
+ position: relative;
599
+ }
600
+
601
+ .switch-multi-options.shadow {
602
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
603
+ }
604
+
605
+ .switch-multi-options.outline {
606
+ border: 1px solid #D1D5DB;
607
+ }
608
+
609
+ .switch-multi-input {
610
+ position: absolute;
611
+ opacity: 0;
612
+ pointer-events: none;
613
+ }
614
+
615
+ .switch-multi-label {
616
+ position: relative;
617
+ padding: 0.5em 1.2em;
618
+ border-radius: 0.375em;
619
+ cursor: pointer;
620
+ user-select: none;
621
+ transition: all var(--animation-duration) var(--animation-easing);
622
+ font-size: 0.875em;
623
+ font-weight: 500;
624
+ color: #6B7280;
625
+ z-index: 1;
626
+ }
627
+
628
+ .switch-multi-label.first {
629
+ margin-left: 0;
630
+ }
631
+
632
+ .switch-multi-label.last {
633
+ margin-right: 0;
634
+ }
635
+
636
+ .switch-multi-input:checked + .switch-multi-label {
637
+ background-color: var(--active-color);
638
+ color: white;
639
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
640
+ }
641
+
642
+ .switch-multi-input:focus-visible + .switch-multi-label {
643
+ outline: 2px solid var(--active-color);
644
+ outline-offset: 2px;
645
+ }
646
+
647
+ .switch-multi-input:disabled + .switch-multi-label {
648
+ cursor: not-allowed;
649
+ opacity: 0.5;
650
+ }
651
+
652
+ /* Loading Spinner */
653
+ .spinner,
654
+ .spinner-small {
655
+ display: inline-block;
656
+ border: 2px solid rgba(255, 255, 255, 0.3);
657
+ border-top-color: white;
658
+ border-radius: 50%;
659
+ animation: spin 0.6s linear infinite;
660
+ }
661
+
662
+ .spinner {
663
+ width: 1em;
664
+ height: 1em;
665
+ }
666
+
667
+ .spinner-small {
668
+ width: 0.8em;
669
+ height: 0.8em;
670
+ border-width: 1.5px;
671
+ }
672
+
673
+ @keyframes spin {
674
+ to {
675
+ transform: rotate(360deg);
676
+ }
677
+ }
678
+
679
+ /* Hover states */
680
+ .switch:not(:disabled):hover {
681
+ opacity: 0.9;
682
+ }
683
+
684
+ .switch-multi-label:hover {
685
+ background-color: rgba(0, 0, 0, 0.05);
686
+ }
687
+
688
+ .switch-multi-input:checked + .switch-multi-label:hover {
689
+ background-color: var(--active-color);
690
+ opacity: 0.9;
691
+ }
692
+ </style>