@mrintel/villain-ui 0.3.0 → 0.6.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 (128) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +60 -46
  15. package/dist/components/cards/Card.svelte.d.ts +6 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +282 -57
  57. package/dist/components/forms/Input.svelte.d.ts +9 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Switch.svelte +44 -56
  65. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  66. package/dist/components/forms/Textarea.svelte +52 -57
  67. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  68. package/dist/components/forms/TimePicker.svelte +63 -0
  69. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  70. package/dist/components/forms/formClasses.d.ts +3 -0
  71. package/dist/components/forms/formClasses.js +3 -0
  72. package/dist/components/forms/index.d.ts +3 -0
  73. package/dist/components/forms/index.js +3 -0
  74. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  75. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  76. package/dist/components/navigation/ContextMenu.svelte +133 -83
  77. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  78. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  79. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  80. package/dist/components/navigation/Menu.svelte +72 -48
  81. package/dist/components/navigation/Navbar.svelte +111 -32
  82. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  83. package/dist/components/navigation/Sidebar.svelte +236 -35
  84. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  85. package/dist/components/navigation/Tabs.svelte +86 -54
  86. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  87. package/dist/components/overlays/Alert.svelte +81 -99
  88. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  89. package/dist/components/overlays/CommandPalette.svelte +182 -217
  90. package/dist/components/overlays/Drawer.svelte +158 -167
  91. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  92. package/dist/components/overlays/Dropdown.svelte +62 -30
  93. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  94. package/dist/components/overlays/Modal.svelte +125 -130
  95. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  96. package/dist/components/overlays/Popover.svelte +106 -131
  97. package/dist/components/overlays/ProgressBar.svelte +29 -45
  98. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  99. package/dist/components/overlays/Spinner.svelte +33 -43
  100. package/dist/components/overlays/Toast.svelte +111 -140
  101. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  102. package/dist/components/overlays/Tooltip.svelte +94 -115
  103. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  104. package/dist/components/typography/Code.svelte +10 -14
  105. package/dist/components/typography/Heading.svelte +15 -22
  106. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  107. package/dist/components/typography/Text.svelte +21 -24
  108. package/dist/components/typography/Text.svelte.d.ts +2 -1
  109. package/dist/components/utilities/Accordion.svelte +54 -67
  110. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  111. package/dist/components/utilities/Carousel.svelte +124 -152
  112. package/dist/components/utilities/Collapse.svelte +46 -60
  113. package/dist/components/utilities/Hero.svelte +42 -0
  114. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  115. package/dist/components/utilities/Portal.svelte +47 -72
  116. package/dist/components/utilities/ScrollArea.svelte +33 -41
  117. package/dist/components/utilities/SystemConsole.svelte +310 -0
  118. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  119. package/dist/components/utilities/SystemInterface.svelte +726 -0
  120. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  121. package/dist/components/utilities/index.d.ts +4 -0
  122. package/dist/components/utilities/index.js +3 -0
  123. package/dist/components/utilities/utilities.types.d.ts +46 -0
  124. package/dist/components/utilities/utilities.types.js +4 -0
  125. package/dist/index.d.ts +49 -4
  126. package/dist/index.js +4 -4
  127. package/dist/theme.css +2821 -218
  128. package/package.json +83 -76
@@ -1,57 +1,282 @@
1
- <script lang="ts">
2
- import { createId } from '../../lib/internal/id.js';
3
-
4
- interface Props {
5
- type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
6
- value?: string;
7
- placeholder?: string;
8
- disabled?: boolean;
9
- error?: boolean;
10
- label?: string;
11
- id?: string;
12
- oninput?: (event: Event) => void;
13
- }
14
-
15
- let {
16
- type = 'text',
17
- value = $bindable(''),
18
- placeholder,
19
- disabled = false,
20
- error = false,
21
- label,
22
- id = createId('input'),
23
- oninput
24
- }: Props = $props();
25
-
26
- const baseClasses = 'glass-panel rounded-lg px-4 py-3 font-body text-text placeholder:text-text-muted transition-all duration-300 ease-luxe w-full';
27
- const focusClasses = 'focus:outline-none focus:border-accent focus:accent-glow';
28
- const errorClasses = error ? 'border-error' : '';
29
- const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
30
- </script>
31
-
32
- {#if label}
33
- <div>
34
- <label for={id} class="text-text-soft text-sm mb-2 block">
35
- {label}
36
- </label>
37
- <input
38
- {type}
39
- {id}
40
- {placeholder}
41
- {disabled}
42
- bind:value
43
- oninput={oninput}
44
- class="{baseClasses} {focusClasses} {errorClasses} {disabledClasses}"
45
- />
46
- </div>
47
- {:else}
48
- <input
49
- {type}
50
- {id}
51
- {placeholder}
52
- {disabled}
53
- bind:value
54
- oninput={oninput}
55
- class="{baseClasses} {focusClasses} {errorClasses} {disabledClasses}"
56
- />
57
- {/if}
1
+ <script lang="ts">import { createId } from '../../lib/internal/id.js';
2
+ import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
3
+ let { type = 'text', value = $bindable(''), placeholder, disabled = false, error = false, label, id = createId('input'), oninput, iconBefore, iconAfter, validate, validationMessage, showValidation = true, class: className = '', } = $props();
4
+ // Built-in validation patterns
5
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
+ const urlPattern = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
7
+ const telPattern = /^[\d\s\-\+\(\)]+$/;
8
+ // Validation logic
9
+ let validationError = $state(null);
10
+ function performValidation(val) {
11
+ if (!val || val === '') {
12
+ validationError = null;
13
+ return true;
14
+ }
15
+ // Custom validation function
16
+ if (validate) {
17
+ const result = validate(val);
18
+ if (result === true) {
19
+ validationError = null;
20
+ return true;
21
+ }
22
+ else if (typeof result === 'string') {
23
+ validationError = result;
24
+ return false;
25
+ }
26
+ else {
27
+ validationError = validationMessage || 'Invalid input';
28
+ return false;
29
+ }
30
+ }
31
+ // Built-in validation based on type
32
+ const stringVal = String(val);
33
+ switch (type) {
34
+ case 'email':
35
+ if (!emailPattern.test(stringVal)) {
36
+ validationError = validationMessage || 'Please enter a valid email address';
37
+ return false;
38
+ }
39
+ break;
40
+ case 'url':
41
+ if (!urlPattern.test(stringVal)) {
42
+ validationError = validationMessage || 'Please enter a valid URL';
43
+ return false;
44
+ }
45
+ break;
46
+ case 'tel':
47
+ if (!telPattern.test(stringVal)) {
48
+ validationError = validationMessage || 'Please enter a valid phone number';
49
+ return false;
50
+ }
51
+ break;
52
+ }
53
+ validationError = null;
54
+ return true;
55
+ }
56
+ // Validate on value change
57
+ $effect(() => {
58
+ if (value !== undefined && value !== '') {
59
+ performValidation(value);
60
+ }
61
+ });
62
+ const hasError = $derived(error || (validationError !== null));
63
+ const errorClasses = $derived(hasError ? 'error-state' : '');
64
+ function increment() {
65
+ if (disabled || type !== 'number')
66
+ return;
67
+ value = Number(value || 0) + 1;
68
+ }
69
+ function decrement() {
70
+ if (disabled || type !== 'number')
71
+ return;
72
+ value = Number(value || 0) - 1;
73
+ }
74
+ </script>
75
+
76
+ {#if label}
77
+ <div>
78
+ <label for={id} class="text-text-soft text-sm mb-2 block">
79
+ {label}
80
+ </label>
81
+ <div class="relative flex items-center">
82
+ <!-- BEFORE ICON -->
83
+ {#if iconBefore}
84
+ <span
85
+ class="absolute left-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
86
+ >
87
+ {@render iconBefore()}
88
+ </span>
89
+ {/if}
90
+
91
+ <!-- INPUT FIELD -->
92
+ <input
93
+ {type}
94
+ {id}
95
+ {placeholder}
96
+ {disabled}
97
+ bind:value
98
+ {oninput}
99
+ class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
100
+ ? disabledClasses
101
+ : ''} {className}"
102
+ class:pl-12={iconBefore}
103
+ class:pr-12={iconAfter || type === 'number'}
104
+ />
105
+
106
+ <!-- AFTER ICON (non-number) -->
107
+ {#if iconAfter && type !== 'number'}
108
+ <span
109
+ class="absolute right-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
110
+ >
111
+ {@render iconAfter()}
112
+ </span>
113
+ {/if}
114
+
115
+ <!-- CUSTOM ARROWS (NUMBER INPUT ONLY) -->
116
+ {#if type === 'number'}
117
+ <div class="number-arrows">
118
+ <button
119
+ type="button"
120
+ onclick={increment}
121
+ class="arrow-btn arrow-up"
122
+ tabindex="-1"
123
+ >
124
+
125
+ </button>
126
+ <button
127
+ type="button"
128
+ onclick={decrement}
129
+ class="arrow-btn arrow-down"
130
+ tabindex="-1"
131
+ >
132
+
133
+ </button>
134
+ </div>
135
+ {/if}
136
+ </div>
137
+ {#if showValidation && validationError}
138
+ <p class="text-error text-xs mt-1.5">
139
+ {validationError}
140
+ </p>
141
+ {/if}
142
+ </div>
143
+ {:else}
144
+ <div>
145
+ <div class="relative flex items-center">
146
+ {#if iconBefore}
147
+ <span
148
+ class="absolute left-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
149
+ >
150
+ {@render iconBefore()}
151
+ </span>
152
+ {/if}
153
+
154
+ <input
155
+ {type}
156
+ {id}
157
+ {placeholder}
158
+ {disabled}
159
+ bind:value
160
+ {oninput}
161
+ class="{baseInputClasses} {focusClasses} {errorClasses} {disabled
162
+ ? disabledClasses
163
+ : ''} {className}"
164
+ class:pl-12={iconBefore}
165
+ class:pr-12={iconAfter || type === 'number'}
166
+ />
167
+
168
+ {#if iconAfter && type !== 'number'}
169
+ <span
170
+ class="absolute right-4 z-10 inline-flex items-center justify-center text-text-soft pointer-events-none"
171
+ >
172
+ {@render iconAfter()}
173
+ </span>
174
+ {/if}
175
+
176
+ {#if type === 'number'}
177
+ <div class="number-arrows">
178
+ <button
179
+ type="button"
180
+ onclick={increment}
181
+ class="arrow-btn arrow-up"
182
+ tabindex="-1"
183
+ >
184
+
185
+ </button>
186
+ <button
187
+ type="button"
188
+ onclick={decrement}
189
+ class="arrow-btn arrow-down"
190
+ tabindex="-1"
191
+ >
192
+
193
+ </button>
194
+ </div>
195
+ {/if}
196
+ </div>
197
+ {#if showValidation && validationError}
198
+ <p class="text-error text-xs mt-1.5">
199
+ {validationError}
200
+ </p>
201
+ {/if}
202
+ </div>
203
+ {/if}
204
+
205
+ <style>
206
+ .number-arrows {
207
+ position: absolute;
208
+ top: 0;
209
+ right: 0;
210
+ height: 100%;
211
+ width: 2.25rem; /* doesn't overlap your icons */
212
+ display: flex;
213
+ flex-direction: column;
214
+ border-left: 1px solid var(--color-border-strong);
215
+ background: color-mix(in srgb, var(--color-accent) 8%, transparent);
216
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
217
+ overflow: hidden;
218
+ }
219
+
220
+ .arrow-btn {
221
+ flex: 1;
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ background: transparent;
226
+ color: var(--color-text-soft);
227
+ font-size: 0.7rem;
228
+ cursor: pointer;
229
+ transition: all 120ms var(--ease-sharp);
230
+ user-select: none;
231
+ }
232
+
233
+ .arrow-btn:hover {
234
+ background: var(--color-accent-overlay-20);
235
+ color: var(--color-accent-soft);
236
+ }
237
+
238
+ .arrow-btn:active {
239
+ background: var(--color-accent-overlay-30);
240
+ color: var(--color-accent);
241
+ }
242
+
243
+ .arrow-btn:disabled,
244
+ .arrow-btn[disabled] {
245
+ opacity: 0.4;
246
+ cursor: not-allowed;
247
+ }
248
+
249
+ /* Remove default browser arrows */
250
+ input[type='number']::-webkit-inner-spin-button,
251
+ input[type='number']::-webkit-outer-spin-button {
252
+ -webkit-appearance: none;
253
+ margin: 0;
254
+ }
255
+
256
+ input[type='number'] {
257
+ -moz-appearance: textfield;
258
+ appearance: textfield;
259
+ }
260
+
261
+ /* Color input styling */
262
+ input[type='color'] {
263
+ width: 60px;
264
+ min-width: 60px;
265
+ min-height: 3rem;
266
+ padding: 0.375rem;
267
+ cursor: pointer;
268
+ }
269
+
270
+ input[type='color']::-webkit-color-swatch-wrapper {
271
+ padding: 0;
272
+ }
273
+
274
+ input[type='color']::-webkit-color-swatch {
275
+ border: 1px solid var(--color-border-strong);
276
+ border-radius: calc(var(--radius-md) - 2px);
277
+ }
278
+
279
+ input[type='color']::-moz-color-swatch {
280
+ border: 1px solid var(--color-border-strong);
281
+ border-radius: calc(var(--radius-md) - 2px);
282
+ }</style>
@@ -1,12 +1,18 @@
1
- interface Props {
2
- type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
3
- value?: string;
1
+ export interface Props {
2
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'color' | 'search';
3
+ value?: string | number;
4
4
  placeholder?: string;
5
5
  disabled?: boolean;
6
6
  error?: boolean;
7
7
  label?: string;
8
8
  id?: string;
9
9
  oninput?: (event: Event) => void;
10
+ iconBefore?: import('svelte').Snippet;
11
+ iconAfter?: import('svelte').Snippet;
12
+ validate?: (value: string | number) => boolean | string;
13
+ validationMessage?: string;
14
+ showValidation?: boolean;
15
+ class?: string;
10
16
  }
11
17
  declare const Input: import("svelte").Component<Props, {}, "value">;
12
18
  type Input = ReturnType<typeof Input>;
@@ -1,7 +1,7 @@
1
- <script lang="ts">
2
- // No props needed for standard slot usage
3
- </script>
4
-
5
- <div class="flex items-stretch rounded-[var(--radius-lg)] overflow-hidden glass-panel [&>*:not(:first-child)]:ml-[-1px] [&>*:not(:first-child):not(:last-child)]:rounded-none [&>*:first-child]:rounded-r-none [&>*:last-child]:rounded-l-none">
6
- <slot />
7
- </div>
1
+ <script lang="ts">"use strict";
2
+ // No props needed for standard slot usage
3
+ </script>
4
+
5
+ <div class="flex items-stretch rounded-[var(--radius-lg)] overflow-hidden panel-raised [&>*:not(:first-child)]:ml-[-1px] [&>*:not(:first-child):not(:last-child)]:rounded-none [&>*:first-child]:rounded-r-none [&>*:last-child]:rounded-l-none">
6
+ <slot />
7
+ </div>
@@ -1,87 +1,77 @@
1
- <script lang="ts">
2
- interface Props {
3
- value?: string;
4
- options: Array<{ value: string; label: string }>;
5
- name: string;
6
- disabled?: boolean;
7
- orientation?: 'vertical' | 'horizontal';
8
- label?: string;
9
- onchange?: (event: Event) => void;
10
- }
11
-
12
- let {
13
- value = $bindable(''),
14
- options,
15
- name,
16
- disabled = false,
17
- orientation = 'vertical',
18
- label,
19
- onchange
20
- }: Props = $props();
21
-
22
- const containerClasses = $derived(orientation === 'vertical' ? 'flex flex-col gap-3' : 'flex flex-row gap-4');
23
- </script>
24
-
25
- {#if label}
26
- <fieldset class={disabled ? 'opacity-50' : ''}>
27
- <legend class="text-[var(--color-text-soft)] text-sm mb-3 block">
28
- {label}
29
- </legend>
30
- <div class={containerClasses}>
31
- {#each options as option}
32
- {@const radioId = `${name}-${option.value}`}
33
- <label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'cursor-not-allowed' : ''}">
34
- <input
35
- type="radio"
36
- id={radioId}
37
- {name}
38
- value={option.value}
39
- {disabled}
40
- bind:group={value}
41
- onchange={onchange}
42
- class="w-5 h-5 rounded-[var(--radius-pill)] border-2 border-[var(--color-border-strong)] bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
43
- />
44
- <span class="text-[var(--color-text)] text-sm select-none">
45
- {option.label}
46
- </span>
47
- </label>
48
- {/each}
49
- </div>
50
- </fieldset>
51
- {:else}
52
- <div class={containerClasses}>
53
- {#each options as option}
54
- {@const radioId = `${name}-${option.value}`}
55
- <label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''}">
56
- <input
57
- type="radio"
58
- id={radioId}
59
- {name}
60
- value={option.value}
61
- {disabled}
62
- bind:group={value}
63
- onchange={onchange}
64
- class="w-5 h-5 rounded-[var(--radius-pill)] border-2 border-[var(--color-border-strong)] bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
65
- />
66
- <span class="text-[var(--color-text)] text-sm select-none">
67
- {option.label}
68
- </span>
69
- </label>
70
- {/each}
71
- </div>
72
- {/if}
73
-
74
- <style>
75
- input[type="radio"]:checked::after {
76
- content: '';
77
- position: absolute;
78
- left: 50%;
79
- top: 50%;
80
- transform: translate(-50%, -50%);
81
- width: 0.625rem;
82
- height: 0.625rem;
83
- border-radius: var(--radius-pill);
84
- background: var(--color-accent);
85
- box-shadow: var(--shadow-accent-glow);
86
- }
87
- </style>
1
+ <script lang="ts">let { value = $bindable(''), options, name, disabled = false, orientation = 'vertical', label, onchange, class: className = '' } = $props();
2
+ const containerClasses = $derived(orientation === 'vertical' ? 'flex flex-col gap-3' : 'flex flex-row gap-4');
3
+ export {};
4
+ </script>
5
+
6
+ {#if label}
7
+ <fieldset class="{disabled ? 'opacity-50' : ''} {className}">
8
+ <legend class="text-[var(--color-text-soft)] text-sm mb-3 block">
9
+ {label}
10
+ </legend>
11
+ <div class={containerClasses}>
12
+ {#each options as option}
13
+ {@const radioId = `${name}-${option.value}`}
14
+ <label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'cursor-not-allowed' : ''}">
15
+ <input
16
+ type="radio"
17
+ id={radioId}
18
+ {name}
19
+ value={option.value}
20
+ {disabled}
21
+ bind:group={value}
22
+ onchange={onchange}
23
+ class="w-6 h-6 rounded-[var(--radius-pill)] border-2 border-[var(--color-border-strong)] bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
24
+ />
25
+ {#if option.iconBefore}
26
+ <span class="inline-flex items-center justify-center text-text-soft">
27
+ {@render option.iconBefore()}
28
+ </span>
29
+ {/if}
30
+ <span class="text-[var(--color-text)] text-sm select-none">
31
+ {option.label}
32
+ </span>
33
+ </label>
34
+ {/each}
35
+ </div>
36
+ </fieldset>
37
+ {:else}
38
+ <div class="{containerClasses} {className}">
39
+ {#each options as option}
40
+ {@const radioId = `${name}-${option.value}`}
41
+ <label for={radioId} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''}">
42
+ <input
43
+ type="radio"
44
+ id={radioId}
45
+ {name}
46
+ value={option.value}
47
+ {disabled}
48
+ bind:group={value}
49
+ onchange={onchange}
50
+ class="w-6 h-6 rounded-[var(--radius-pill)] border-2 border-[var(--color-border-strong)] bg-transparent appearance-none transition-all duration-200 ease-[var(--ease-luxe)] cursor-pointer checked:border-[var(--color-accent)] focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)] focus:ring-offset-2 focus:ring-offset-[var(--color-base-1)] relative {disabled ? 'cursor-not-allowed' : ''}"
51
+ />
52
+ {#if option.iconBefore}
53
+ <span class="inline-flex items-center justify-center text-text-soft">
54
+ {@render option.iconBefore()}
55
+ </span>
56
+ {/if}
57
+ <span class="text-[var(--color-text)] text-sm select-none">
58
+ {option.label}
59
+ </span>
60
+ </label>
61
+ {/each}
62
+ </div>
63
+ {/if}
64
+
65
+ <style>
66
+ input[type="radio"]:checked::after {
67
+ content: '';
68
+ position: absolute;
69
+ left: 50%;
70
+ top: 50%;
71
+ transform: translate(-50%, -50%);
72
+ width: 0.75rem;
73
+ height: 0.75rem;
74
+ border-radius: var(--radius-pill);
75
+ background: var(--color-accent);
76
+ box-shadow: var(--shadow-accent-glow);
77
+ }</style>
@@ -1,14 +1,16 @@
1
- interface Props {
1
+ export interface Props {
2
2
  value?: string;
3
3
  options: Array<{
4
4
  value: string;
5
5
  label: string;
6
+ iconBefore?: import('svelte').Snippet;
6
7
  }>;
7
8
  name: string;
8
9
  disabled?: boolean;
9
10
  orientation?: 'vertical' | 'horizontal';
10
11
  label?: string;
11
12
  onchange?: (event: Event) => void;
13
+ class?: string;
12
14
  }
13
15
  declare const RadioGroup: import("svelte").Component<Props, {}, "value">;
14
16
  type RadioGroup = ReturnType<typeof RadioGroup>;