@joewinke/jatui 0.42.0 → 0.44.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.
Files changed (49) hide show
  1. package/CLAUDE.md +24 -3
  2. package/THIRD_PARTY_NOTICES.md +14 -0
  3. package/dist/actions/dismissOutside.d.ts +52 -0
  4. package/dist/actions/dismissOutside.js +80 -0
  5. package/dist/components/FileDropzone.svelte +165 -3
  6. package/dist/components/FileDropzone.svelte.d.ts +9 -0
  7. package/dist/components/ImageReveal.svelte +353 -0
  8. package/dist/components/ImageReveal.svelte.d.ts +31 -0
  9. package/dist/components/TextInput.svelte +71 -20
  10. package/dist/components/TextInput.svelte.d.ts +7 -0
  11. package/dist/components/ThumbnailGrid.svelte +106 -1
  12. package/dist/components/ThumbnailGrid.svelte.d.ts +7 -0
  13. package/dist/components/basic/Alert.svelte +113 -16
  14. package/dist/components/basic/Alert.svelte.d.ts +17 -1
  15. package/dist/components/basic/Checkbox.svelte +53 -1
  16. package/dist/components/basic/Checkbox.svelte.d.ts +6 -0
  17. package/dist/components/basic/RadioGroup.svelte +59 -3
  18. package/dist/components/basic/RadioGroup.svelte.d.ts +15 -0
  19. package/dist/components/basic/Slider.svelte +257 -9
  20. package/dist/components/basic/Slider.svelte.d.ts +14 -2
  21. package/dist/components/basic/sliderMath.d.ts +29 -0
  22. package/dist/components/basic/sliderMath.js +64 -0
  23. package/dist/components/field/FieldHint.svelte +29 -0
  24. package/dist/components/field/FieldHint.svelte.d.ts +17 -0
  25. package/dist/components/field/FieldLabel.svelte +100 -0
  26. package/dist/components/field/FieldLabel.svelte.d.ts +29 -0
  27. package/dist/components/form-cards/CheckGlyph.svelte +138 -0
  28. package/dist/components/form-cards/CheckGlyph.svelte.d.ts +29 -0
  29. package/dist/components/form-cards/CheckboxCard.svelte +120 -0
  30. package/dist/components/form-cards/CheckboxCard.svelte.d.ts +33 -0
  31. package/dist/components/form-cards/ChoiceCard.svelte +156 -0
  32. package/dist/components/form-cards/ChoiceCard.svelte.d.ts +42 -0
  33. package/dist/components/form-cards/SwitchCard.svelte +114 -0
  34. package/dist/components/form-cards/SwitchCard.svelte.d.ts +28 -0
  35. package/dist/components/imageReveal.d.ts +51 -0
  36. package/dist/components/imageReveal.js +83 -0
  37. package/dist/components/toast/ToastHost.svelte +56 -3
  38. package/dist/components/toast/ToastHost.svelte.d.ts +9 -1
  39. package/dist/components/toast/ToastItem.svelte +75 -23
  40. package/dist/components/toast/ToastItem.svelte.d.ts +2 -0
  41. package/dist/components/uploadProgress.d.ts +30 -0
  42. package/dist/components/uploadProgress.js +47 -0
  43. package/dist/index.d.ts +15 -2
  44. package/dist/index.js +13 -1
  45. package/dist/stores/toast.d.ts +35 -0
  46. package/dist/stores/toast.js +15 -1
  47. package/dist/styles/motion.css +72 -0
  48. package/dist/styles/rail.css +51 -0
  49. package/package.json +1 -1
@@ -0,0 +1,138 @@
1
+ <script lang="ts">
2
+ /**
3
+ * CheckGlyph — the drawn checkbox box shared by `Checkbox animated` and
4
+ * `CheckboxCard`, so the two can never drift apart visually.
5
+ *
6
+ * Purely presentational (`aria-hidden`): the host keeps a real, visually
7
+ * hidden `<input type="checkbox">` next to it for semantics, keys and AT.
8
+ *
9
+ * The tick draws on (stroke-dashoffset over `pathLength=1`) on the
10
+ * `--motion-fast` duration with the `--motion-glide` curve, so it scales
11
+ * with `data-motion` and collapses to an instant swap under reduced motion.
12
+ * `animate=false` (the default before the first user change) renders the
13
+ * tick already drawn — a pre-checked box never "draws itself" on load.
14
+ *
15
+ * Idea after BoardUI's checkbox glyph (MIT © 2026 Mertcan Dundar Esmergul);
16
+ * written fresh for Svelte, tokens and DaisyUI.
17
+ */
18
+ interface Props {
19
+ checked?: boolean;
20
+ indeterminate?: boolean;
21
+ disabled?: boolean;
22
+ /** Run the draw-on animation when the tick appears. */
23
+ animate?: boolean;
24
+ size?: 'xs' | 'sm' | 'md' | 'lg';
25
+ variant?: 'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
26
+ class?: string;
27
+ }
28
+
29
+ let {
30
+ checked = false,
31
+ indeterminate = false,
32
+ disabled = false,
33
+ animate = true,
34
+ size = 'md',
35
+ variant = 'primary',
36
+ class: className = ''
37
+ }: Props = $props();
38
+
39
+ const marked = $derived(checked || indeterminate);
40
+ const sizeClass = $derived(
41
+ { xs: 'jatui-glyph-xs', sm: 'jatui-glyph-sm', md: 'jatui-glyph-md', lg: 'jatui-glyph-lg' }[size]
42
+ );
43
+ const toneClass = $derived(
44
+ {
45
+ default: 'bg-base-content text-base-100 border-base-content',
46
+ primary: 'bg-primary text-primary-content border-primary',
47
+ secondary: 'bg-secondary text-secondary-content border-secondary',
48
+ accent: 'bg-accent text-accent-content border-accent',
49
+ success: 'bg-success text-success-content border-success',
50
+ warning: 'bg-warning text-warning-content border-warning',
51
+ error: 'bg-error text-error-content border-error',
52
+ info: 'bg-info text-info-content border-info'
53
+ }[variant]
54
+ );
55
+ </script>
56
+
57
+ <span
58
+ aria-hidden="true"
59
+ class="jatui-check-glyph {sizeClass} {marked
60
+ ? toneClass
61
+ : 'bg-base-100 border-base-content/30 text-transparent'} {className}"
62
+ class:jatui-glyph-disabled={disabled}
63
+ data-state={indeterminate ? 'indeterminate' : checked ? 'checked' : 'unchecked'}
64
+ >
65
+ <svg viewBox="0 0 16 16" fill="none">
66
+ {#if indeterminate}
67
+ <path d="M4.5 8h7" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
68
+ {:else if checked}
69
+ <path
70
+ class:jatui-check-draw={animate}
71
+ d="M4 8.2l2.6 2.6L12 5.4"
72
+ stroke="currentColor"
73
+ stroke-width="2"
74
+ stroke-linecap="round"
75
+ stroke-linejoin="round"
76
+ pathLength="1"
77
+ />
78
+ {/if}
79
+ </svg>
80
+ </span>
81
+
82
+ <style>
83
+ .jatui-check-glyph {
84
+ display: inline-flex;
85
+ flex-shrink: 0;
86
+ align-items: center;
87
+ justify-content: center;
88
+ border-width: var(--jatui-border-w, 1px);
89
+ border-style: solid;
90
+ border-radius: var(--jatui-radius-field, 0.25rem);
91
+ transition:
92
+ background-color var(--motion-fast, 150ms) var(--motion-glide, ease-out),
93
+ border-color var(--motion-fast, 150ms) var(--motion-glide, ease-out);
94
+ }
95
+ .jatui-check-glyph svg {
96
+ width: 100%;
97
+ height: 100%;
98
+ }
99
+ .jatui-glyph-xs {
100
+ width: 1rem;
101
+ height: 1rem;
102
+ }
103
+ .jatui-glyph-sm {
104
+ width: 1.125rem;
105
+ height: 1.125rem;
106
+ }
107
+ .jatui-glyph-md {
108
+ width: 1.25rem;
109
+ height: 1.25rem;
110
+ }
111
+ .jatui-glyph-lg {
112
+ width: 1.5rem;
113
+ height: 1.5rem;
114
+ }
115
+ .jatui-glyph-disabled {
116
+ opacity: 0.5;
117
+ }
118
+
119
+ .jatui-check-draw {
120
+ stroke-dasharray: 1;
121
+ stroke-dashoffset: 1;
122
+ animation: jatui-check-draw var(--motion-fast, 150ms) var(--motion-glide, ease-out) forwards;
123
+ }
124
+ @keyframes jatui-check-draw {
125
+ to {
126
+ stroke-dashoffset: 0;
127
+ }
128
+ }
129
+ @media (prefers-reduced-motion: reduce) {
130
+ .jatui-check-glyph {
131
+ transition: none;
132
+ }
133
+ .jatui-check-draw {
134
+ animation: none;
135
+ stroke-dashoffset: 0;
136
+ }
137
+ }
138
+ </style>
@@ -0,0 +1,29 @@
1
+ /**
2
+ * CheckGlyph — the drawn checkbox box shared by `Checkbox animated` and
3
+ * `CheckboxCard`, so the two can never drift apart visually.
4
+ *
5
+ * Purely presentational (`aria-hidden`): the host keeps a real, visually
6
+ * hidden `<input type="checkbox">` next to it for semantics, keys and AT.
7
+ *
8
+ * The tick draws on (stroke-dashoffset over `pathLength=1`) on the
9
+ * `--motion-fast` duration with the `--motion-glide` curve, so it scales
10
+ * with `data-motion` and collapses to an instant swap under reduced motion.
11
+ * `animate=false` (the default before the first user change) renders the
12
+ * tick already drawn — a pre-checked box never "draws itself" on load.
13
+ *
14
+ * Idea after BoardUI's checkbox glyph (MIT © 2026 Mertcan Dundar Esmergul);
15
+ * written fresh for Svelte, tokens and DaisyUI.
16
+ */
17
+ interface Props {
18
+ checked?: boolean;
19
+ indeterminate?: boolean;
20
+ disabled?: boolean;
21
+ /** Run the draw-on animation when the tick appears. */
22
+ animate?: boolean;
23
+ size?: 'xs' | 'sm' | 'md' | 'lg';
24
+ variant?: 'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
25
+ class?: string;
26
+ }
27
+ declare const CheckGlyph: import("svelte").Component<Props, {}, "">;
28
+ type CheckGlyph = ReturnType<typeof CheckGlyph>;
29
+ export default CheckGlyph;
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ /**
3
+ * CheckboxCard — a whole-card checkbox: title + description (+ icon) on a
4
+ * ChoiceCard, with the shared animated `CheckGlyph`. The native checkbox
5
+ * stays in the DOM (visually hidden) so forms, Space, and AT work as usual;
6
+ * the description is wired as its `aria-describedby`.
7
+ *
8
+ * Card variant after BoardUI's checkbox-card (MIT © 2026 Mertcan Dundar
9
+ * Esmergul); written fresh for Svelte, tokens and DaisyUI.
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ import ChoiceCard from './ChoiceCard.svelte';
13
+ import CheckGlyph from './CheckGlyph.svelte';
14
+
15
+ interface Props {
16
+ checked?: boolean;
17
+ title: string;
18
+ description?: string;
19
+ icon?: Snippet;
20
+ children?: Snippet;
21
+ indeterminate?: boolean;
22
+ disabled?: boolean;
23
+ required?: boolean;
24
+ name?: string;
25
+ value?: string;
26
+ id?: string;
27
+ size?: 'sm' | 'md';
28
+ variant?: 'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
29
+ controlPosition?: 'start' | 'end';
30
+ /** Draw the tick on when checked. Default true. */
31
+ animated?: boolean;
32
+ onchange?: (checked: boolean) => void;
33
+ class?: string;
34
+ }
35
+
36
+ let {
37
+ checked = $bindable(false),
38
+ title,
39
+ description,
40
+ icon,
41
+ children,
42
+ indeterminate = false,
43
+ disabled = false,
44
+ required = false,
45
+ name,
46
+ value,
47
+ id,
48
+ size = 'md',
49
+ variant = 'primary',
50
+ controlPosition = 'start',
51
+ animated = true,
52
+ onchange,
53
+ class: className = ''
54
+ }: Props = $props();
55
+
56
+ const uid = Math.random().toString(36).slice(2, 9);
57
+ const inputId = $derived(id ?? `checkbox-card-${uid}`);
58
+ const descId = $derived(description ? `${inputId}-desc` : undefined);
59
+
60
+ let inputEl: HTMLInputElement | undefined = $state();
61
+ let interacted = $state(false);
62
+
63
+ $effect(() => {
64
+ if (inputEl) inputEl.indeterminate = indeterminate;
65
+ });
66
+
67
+ function handleChange(e: Event) {
68
+ checked = (e.target as HTMLInputElement).checked;
69
+ interacted = true;
70
+ onchange?.(checked);
71
+ }
72
+ </script>
73
+
74
+ <ChoiceCard
75
+ {title}
76
+ {description}
77
+ {icon}
78
+ {children}
79
+ {size}
80
+ {disabled}
81
+ {controlPosition}
82
+ for={inputId}
83
+ descriptionId={descId}
84
+ class={className}
85
+ >
86
+ {#snippet control()}
87
+ <input
88
+ bind:this={inputEl}
89
+ type="checkbox"
90
+ class="jatui-card-native"
91
+ id={inputId}
92
+ {name}
93
+ {value}
94
+ {checked}
95
+ {disabled}
96
+ {required}
97
+ aria-describedby={descId}
98
+ onchange={handleChange}
99
+ />
100
+ <CheckGlyph
101
+ {checked}
102
+ {indeterminate}
103
+ {disabled}
104
+ {variant}
105
+ size={size === 'sm' ? 'sm' : 'md'}
106
+ animate={animated && interacted}
107
+ />
108
+ {/snippet}
109
+ </ChoiceCard>
110
+
111
+ <style>
112
+ .jatui-card-native {
113
+ position: absolute;
114
+ width: 1px;
115
+ height: 1px;
116
+ margin: 0;
117
+ opacity: 0;
118
+ pointer-events: none;
119
+ }
120
+ </style>
@@ -0,0 +1,33 @@
1
+ /**
2
+ * CheckboxCard — a whole-card checkbox: title + description (+ icon) on a
3
+ * ChoiceCard, with the shared animated `CheckGlyph`. The native checkbox
4
+ * stays in the DOM (visually hidden) so forms, Space, and AT work as usual;
5
+ * the description is wired as its `aria-describedby`.
6
+ *
7
+ * Card variant after BoardUI's checkbox-card (MIT © 2026 Mertcan Dundar
8
+ * Esmergul); written fresh for Svelte, tokens and DaisyUI.
9
+ */
10
+ import type { Snippet } from 'svelte';
11
+ interface Props {
12
+ checked?: boolean;
13
+ title: string;
14
+ description?: string;
15
+ icon?: Snippet;
16
+ children?: Snippet;
17
+ indeterminate?: boolean;
18
+ disabled?: boolean;
19
+ required?: boolean;
20
+ name?: string;
21
+ value?: string;
22
+ id?: string;
23
+ size?: 'sm' | 'md';
24
+ variant?: 'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
25
+ controlPosition?: 'start' | 'end';
26
+ /** Draw the tick on when checked. Default true. */
27
+ animated?: boolean;
28
+ onchange?: (checked: boolean) => void;
29
+ class?: string;
30
+ }
31
+ declare const CheckboxCard: import("svelte").Component<Props, {}, "checked">;
32
+ type CheckboxCard = ReturnType<typeof CheckboxCard>;
33
+ export default CheckboxCard;
@@ -0,0 +1,156 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ChoiceCard — the shared shell behind CheckboxCard, SwitchCard and
4
+ * RadioGroup `variant="card"`: a `<label>` card holding a real native
5
+ * control (passed in as the `control` snippet), a title, an optional
6
+ * description and an optional leading icon. Clicking anywhere on the card
7
+ * toggles the control because the whole card is its label.
8
+ *
9
+ * State styling is driven by CSS on the native control (`:has(:checked)`,
10
+ * `:has(:focus-visible)`, `:has(:disabled)`), so it can't disagree with the
11
+ * control itself. Flat at rest: a 1px border and a tint, no shadow.
12
+ *
13
+ * Use it directly for a custom card control; the three wrappers cover the
14
+ * common cases.
15
+ */
16
+ import type { Snippet } from 'svelte';
17
+
18
+ interface Props {
19
+ title: string;
20
+ description?: string;
21
+ /** The native control (checkbox / radio / switch input). Required. */
22
+ control: Snippet;
23
+ /** Optional leading icon / media. */
24
+ icon?: Snippet;
25
+ /** Extra content under the description (badges, a price, …). */
26
+ children?: Snippet;
27
+ /** Where the control sits. Default `end` (settings-row style). */
28
+ controlPosition?: 'start' | 'end';
29
+ size?: 'sm' | 'md';
30
+ disabled?: boolean;
31
+ /** id of the native control, so the card label points at it. */
32
+ for?: string;
33
+ /** id given to the description, for the control's aria-describedby. */
34
+ descriptionId?: string;
35
+ /** id given to the title text. */
36
+ titleId?: string;
37
+ /** Tint + primary border when the control is checked. Default true;
38
+ * SwitchCard turns it off (on/off is a setting, not a pick). */
39
+ selectedTint?: boolean;
40
+ class?: string;
41
+ }
42
+
43
+ let {
44
+ title,
45
+ description,
46
+ control,
47
+ icon,
48
+ children,
49
+ controlPosition = 'end',
50
+ size = 'md',
51
+ disabled = false,
52
+ for: htmlFor,
53
+ descriptionId,
54
+ titleId,
55
+ selectedTint = true,
56
+ class: className = ''
57
+ }: Props = $props();
58
+ </script>
59
+
60
+ <label
61
+ for={htmlFor}
62
+ class="jatui-choice-card jatui-choice-{size} bg-base-100 {className}"
63
+ class:jatui-choice-disabled={disabled}
64
+ class:jatui-choice-tint={selectedTint}
65
+ class:jatui-choice-control-start={controlPosition === 'start'}
66
+ >
67
+ {#if controlPosition === 'start'}
68
+ <span class="jatui-choice-control">{@render control()}</span>
69
+ {/if}
70
+ {#if icon}
71
+ <span class="jatui-choice-icon text-base-content/70">{@render icon()}</span>
72
+ {/if}
73
+ <span class="jatui-choice-body">
74
+ <span id={titleId} class="jatui-choice-title text-base-content">{title}</span>
75
+ {#if description}
76
+ <span id={descriptionId} class="jatui-choice-desc text-base-content/60">{description}</span>
77
+ {/if}
78
+ {#if children}{@render children()}{/if}
79
+ </span>
80
+ {#if controlPosition === 'end'}
81
+ <span class="jatui-choice-control">{@render control()}</span>
82
+ {/if}
83
+ </label>
84
+
85
+ <style>
86
+ .jatui-choice-card {
87
+ position: relative;
88
+ display: flex;
89
+ align-items: flex-start;
90
+ gap: var(--jatui-space-lg, 12px);
91
+ border: var(--jatui-border-w, 1px) solid color-mix(in oklch, var(--color-base-content) 15%, transparent);
92
+ border-radius: var(--jatui-radius-box, 0.5rem);
93
+ cursor: pointer;
94
+ transition:
95
+ border-color var(--motion-fast, 150ms) var(--motion-glide, ease-out),
96
+ background-color var(--motion-fast, 150ms) var(--motion-glide, ease-out);
97
+ }
98
+ .jatui-choice-md {
99
+ padding: var(--jatui-space-xl, 16px);
100
+ }
101
+ .jatui-choice-sm {
102
+ padding: var(--jatui-space-lg, 12px);
103
+ gap: var(--jatui-space-base, 8px);
104
+ }
105
+ .jatui-choice-card:hover {
106
+ border-color: color-mix(in oklch, var(--color-base-content) 30%, transparent);
107
+ }
108
+ .jatui-choice-tint:has(:global(input:checked)) {
109
+ border-color: var(--color-primary);
110
+ background-color: color-mix(in oklch, var(--color-primary) 6%, var(--color-base-100));
111
+ }
112
+ .jatui-choice-card:has(:global(input:focus-visible)) {
113
+ outline: 2px solid color-mix(in oklch, var(--color-primary) 65%, transparent);
114
+ outline-offset: 2px;
115
+ }
116
+ .jatui-choice-disabled,
117
+ .jatui-choice-card:has(:global(input:disabled)) {
118
+ cursor: not-allowed;
119
+ opacity: 0.55;
120
+ }
121
+ .jatui-choice-disabled:hover {
122
+ border-color: color-mix(in oklch, var(--color-base-content) 15%, transparent);
123
+ }
124
+ .jatui-choice-control,
125
+ .jatui-choice-icon {
126
+ display: inline-flex;
127
+ flex-shrink: 0;
128
+ align-items: center;
129
+ /* Line the control up with the first line of the title. */
130
+ min-height: 1.25rem;
131
+ }
132
+ .jatui-choice-body {
133
+ display: flex;
134
+ flex: 1 1 auto;
135
+ flex-direction: column;
136
+ gap: var(--jatui-space-xs, 2px);
137
+ min-width: 0;
138
+ }
139
+ .jatui-choice-title {
140
+ font-size: 0.875rem;
141
+ line-height: 1.25rem;
142
+ font-weight: 500;
143
+ }
144
+ .jatui-choice-sm .jatui-choice-title {
145
+ font-size: 0.8125rem;
146
+ }
147
+ .jatui-choice-desc {
148
+ font-size: 0.8125rem;
149
+ line-height: 1.125rem;
150
+ }
151
+ @media (prefers-reduced-motion: reduce) {
152
+ .jatui-choice-card {
153
+ transition: none;
154
+ }
155
+ }
156
+ </style>
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ChoiceCard — the shared shell behind CheckboxCard, SwitchCard and
3
+ * RadioGroup `variant="card"`: a `<label>` card holding a real native
4
+ * control (passed in as the `control` snippet), a title, an optional
5
+ * description and an optional leading icon. Clicking anywhere on the card
6
+ * toggles the control because the whole card is its label.
7
+ *
8
+ * State styling is driven by CSS on the native control (`:has(:checked)`,
9
+ * `:has(:focus-visible)`, `:has(:disabled)`), so it can't disagree with the
10
+ * control itself. Flat at rest: a 1px border and a tint, no shadow.
11
+ *
12
+ * Use it directly for a custom card control; the three wrappers cover the
13
+ * common cases.
14
+ */
15
+ import type { Snippet } from 'svelte';
16
+ interface Props {
17
+ title: string;
18
+ description?: string;
19
+ /** The native control (checkbox / radio / switch input). Required. */
20
+ control: Snippet;
21
+ /** Optional leading icon / media. */
22
+ icon?: Snippet;
23
+ /** Extra content under the description (badges, a price, …). */
24
+ children?: Snippet;
25
+ /** Where the control sits. Default `end` (settings-row style). */
26
+ controlPosition?: 'start' | 'end';
27
+ size?: 'sm' | 'md';
28
+ disabled?: boolean;
29
+ /** id of the native control, so the card label points at it. */
30
+ for?: string;
31
+ /** id given to the description, for the control's aria-describedby. */
32
+ descriptionId?: string;
33
+ /** id given to the title text. */
34
+ titleId?: string;
35
+ /** Tint + primary border when the control is checked. Default true;
36
+ * SwitchCard turns it off (on/off is a setting, not a pick). */
37
+ selectedTint?: boolean;
38
+ class?: string;
39
+ }
40
+ declare const ChoiceCard: import("svelte").Component<Props, {}, "">;
41
+ type ChoiceCard = ReturnType<typeof ChoiceCard>;
42
+ export default ChoiceCard;
@@ -0,0 +1,114 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SwitchCard — a settings row where the whole card toggles: title +
4
+ * description (+ icon) with a switch at the end. The switch is a native
5
+ * checkbox with `role="switch"` (DaisyUI `toggle` skin, same motion-token
6
+ * spring as `Switch`), and the description is its `aria-describedby`.
7
+ *
8
+ * Card variant after BoardUI's switch-card (MIT © 2026 Mertcan Dundar
9
+ * Esmergul); the gradient/inset skin is deliberately not ported.
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ import ChoiceCard from './ChoiceCard.svelte';
13
+
14
+ interface Props {
15
+ checked?: boolean;
16
+ title: string;
17
+ description?: string;
18
+ icon?: Snippet;
19
+ children?: Snippet;
20
+ disabled?: boolean;
21
+ name?: string;
22
+ id?: string;
23
+ size?: 'sm' | 'md';
24
+ variant?: 'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
25
+ controlPosition?: 'start' | 'end';
26
+ onchange?: (checked: boolean) => void;
27
+ class?: string;
28
+ }
29
+
30
+ let {
31
+ checked = $bindable(false),
32
+ title,
33
+ description,
34
+ icon,
35
+ children,
36
+ disabled = false,
37
+ name,
38
+ id,
39
+ size = 'md',
40
+ variant = 'primary',
41
+ controlPosition = 'end',
42
+ onchange,
43
+ class: className = ''
44
+ }: Props = $props();
45
+
46
+ const uid = Math.random().toString(36).slice(2, 9);
47
+ const inputId = $derived(id ?? `switch-card-${uid}`);
48
+ const descId = $derived(description ? `${inputId}-desc` : undefined);
49
+
50
+ const toggleClass = $derived(
51
+ [
52
+ 'toggle',
53
+ 'jatui-switch-card-toggle',
54
+ size === 'sm' ? 'toggle-sm' : '',
55
+ {
56
+ default: '',
57
+ primary: 'toggle-primary',
58
+ secondary: 'toggle-secondary',
59
+ accent: 'toggle-accent',
60
+ success: 'toggle-success',
61
+ warning: 'toggle-warning',
62
+ error: 'toggle-error',
63
+ info: 'toggle-info'
64
+ }[variant]
65
+ ]
66
+ .filter(Boolean)
67
+ .join(' ')
68
+ );
69
+
70
+ function handleChange(e: Event) {
71
+ checked = (e.target as HTMLInputElement).checked;
72
+ onchange?.(checked);
73
+ }
74
+ </script>
75
+
76
+ <ChoiceCard
77
+ {title}
78
+ {description}
79
+ {icon}
80
+ {children}
81
+ {size}
82
+ {disabled}
83
+ {controlPosition}
84
+ for={inputId}
85
+ descriptionId={descId}
86
+ selectedTint={false}
87
+ class={className}
88
+ >
89
+ {#snippet control()}
90
+ <input
91
+ type="checkbox"
92
+ role="switch"
93
+ class={toggleClass}
94
+ id={inputId}
95
+ {name}
96
+ {checked}
97
+ {disabled}
98
+ aria-checked={checked}
99
+ aria-describedby={descId}
100
+ onchange={handleChange}
101
+ />
102
+ {/snippet}
103
+ </ChoiceCard>
104
+
105
+ <style>
106
+ .jatui-switch-card-toggle {
107
+ transition: background-color var(--motion-fast) var(--motion-spring);
108
+ }
109
+ @media (prefers-reduced-motion: reduce) {
110
+ .jatui-switch-card-toggle {
111
+ transition: none;
112
+ }
113
+ }
114
+ </style>
@@ -0,0 +1,28 @@
1
+ /**
2
+ * SwitchCard — a settings row where the whole card toggles: title +
3
+ * description (+ icon) with a switch at the end. The switch is a native
4
+ * checkbox with `role="switch"` (DaisyUI `toggle` skin, same motion-token
5
+ * spring as `Switch`), and the description is its `aria-describedby`.
6
+ *
7
+ * Card variant after BoardUI's switch-card (MIT © 2026 Mertcan Dundar
8
+ * Esmergul); the gradient/inset skin is deliberately not ported.
9
+ */
10
+ import type { Snippet } from 'svelte';
11
+ interface Props {
12
+ checked?: boolean;
13
+ title: string;
14
+ description?: string;
15
+ icon?: Snippet;
16
+ children?: Snippet;
17
+ disabled?: boolean;
18
+ name?: string;
19
+ id?: string;
20
+ size?: 'sm' | 'md';
21
+ variant?: 'default' | 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'error' | 'info';
22
+ controlPosition?: 'start' | 'end';
23
+ onchange?: (checked: boolean) => void;
24
+ class?: string;
25
+ }
26
+ declare const SwitchCard: import("svelte").Component<Props, {}, "checked">;
27
+ type SwitchCard = ReturnType<typeof SwitchCard>;
28
+ export default SwitchCard;