@aiaiai-pt/design-system 0.3.4 → 0.3.5
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/components/Alert.svelte +3 -3
- package/components/BottomNavItem.svelte +2 -2
- package/components/Button.svelte +2 -2
- package/components/CardGrid.svelte +104 -0
- package/components/Checkbox.svelte +2 -2
- package/components/Combobox.svelte +1 -1
- package/components/CommandPalette.svelte +605 -0
- package/components/ConditionTable.svelte +12 -1
- package/components/FileUploadItem.svelte +3 -3
- package/components/FilterBar.svelte +255 -0
- package/components/Input.svelte +3 -3
- package/components/LogViewer.svelte +410 -0
- package/components/Modal.svelte +2 -2
- package/components/SearchInput.svelte +473 -0
- package/components/Select.svelte +14 -2
- package/components/SidebarItem.svelte +2 -2
- package/components/Stepper.svelte +2 -2
- package/components/index.js +6 -0
- package/package.json +1 -1
- package/tokens/base.css +8 -0
- package/tokens/components.css +131 -9
- package/tokens/semantic.css +17 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component SearchInput
|
|
3
|
+
|
|
4
|
+
Text input optimized for search with built-in icon, clear button,
|
|
5
|
+
debounced callback, optional keyboard shortcut hint, and loading state.
|
|
6
|
+
Extends the Input token system (--input-*) with search-specific tokens (--search-*).
|
|
7
|
+
|
|
8
|
+
@example Basic
|
|
9
|
+
<SearchInput placeholder="Search transforms..." onsearch={handleSearch} />
|
|
10
|
+
|
|
11
|
+
@example With shortcut hint
|
|
12
|
+
<SearchInput placeholder="Search..." shortcutHint="⌘K" onsearch={handleSearch} />
|
|
13
|
+
|
|
14
|
+
@example Loading state
|
|
15
|
+
<SearchInput placeholder="Search..." loading={true} onsearch={handleSearch} />
|
|
16
|
+
|
|
17
|
+
@example Collapsible (for toolbars / mobile)
|
|
18
|
+
<SearchInput collapsible placeholder="Search..." onsearch={handleSearch} />
|
|
19
|
+
|
|
20
|
+
@example Inside a modal (preserve value on Escape)
|
|
21
|
+
<SearchInput holdValueOnEscape placeholder="Search..." onsearch={handleSearch} />
|
|
22
|
+
|
|
23
|
+
@example No debounce (instant)
|
|
24
|
+
<SearchInput debounce={0} onsearch={handleSearch} />
|
|
25
|
+
-->
|
|
26
|
+
<script module>
|
|
27
|
+
let _searchUid = 0;
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {'sm' | 'md' | 'lg'} Size
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
/** @type {string} */
|
|
37
|
+
value = $bindable(''),
|
|
38
|
+
/** @type {string} */
|
|
39
|
+
placeholder = 'Search...',
|
|
40
|
+
/** @type {Size} */
|
|
41
|
+
size = 'md',
|
|
42
|
+
/** @type {number} Debounce delay in ms. 0 = disabled. */
|
|
43
|
+
debounce = 300,
|
|
44
|
+
/** @type {boolean} Show spinner instead of search icon */
|
|
45
|
+
loading = false,
|
|
46
|
+
/** @type {boolean} Collapse to icon-only when empty and blurred */
|
|
47
|
+
collapsible = false,
|
|
48
|
+
/** @type {string | null} Keyboard shortcut hint badge (e.g., "⌘K", "/") */
|
|
49
|
+
shortcutHint = null,
|
|
50
|
+
/** @type {boolean} Preserve value when Escape is pressed (for use inside modals) */
|
|
51
|
+
holdValueOnEscape = false,
|
|
52
|
+
/** @type {boolean} */
|
|
53
|
+
disabled = false,
|
|
54
|
+
/** @type {string} Accessible label (sr-only) */
|
|
55
|
+
label = 'Search',
|
|
56
|
+
/** @type {string | undefined} */
|
|
57
|
+
id = undefined,
|
|
58
|
+
/** @type {string} */
|
|
59
|
+
class: className = '',
|
|
60
|
+
/** @type {((value: string) => void) | undefined} Fires on every keystroke */
|
|
61
|
+
oninput = undefined,
|
|
62
|
+
/** @type {((value: string) => void) | undefined} Fires after debounce delay */
|
|
63
|
+
onsearch = undefined,
|
|
64
|
+
/** @type {(() => void) | undefined} Fires when cleared (X click or Escape) */
|
|
65
|
+
onclear = undefined,
|
|
66
|
+
...rest
|
|
67
|
+
} = $props();
|
|
68
|
+
|
|
69
|
+
const fallbackId = `search-${_searchUid++}`;
|
|
70
|
+
const inputId = $derived(id ?? fallbackId);
|
|
71
|
+
|
|
72
|
+
/** @type {HTMLInputElement | undefined} */
|
|
73
|
+
let inputEl = $state();
|
|
74
|
+
let collapsed = $state(collapsible);
|
|
75
|
+
let focused = $state(false);
|
|
76
|
+
|
|
77
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
78
|
+
let debounceTimer;
|
|
79
|
+
|
|
80
|
+
const hasValue = $derived(value.length > 0);
|
|
81
|
+
|
|
82
|
+
// React to collapsible prop changes
|
|
83
|
+
$effect(() => {
|
|
84
|
+
if (collapsible && !hasValue) collapsed = true;
|
|
85
|
+
if (!collapsible) collapsed = false;
|
|
86
|
+
});
|
|
87
|
+
const showShortcut = $derived(shortcutHint && !focused && !hasValue);
|
|
88
|
+
|
|
89
|
+
function handleInput() {
|
|
90
|
+
oninput?.(value);
|
|
91
|
+
|
|
92
|
+
if (debounce <= 0) {
|
|
93
|
+
onsearch?.(value);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
clearTimeout(debounceTimer);
|
|
98
|
+
debounceTimer = setTimeout(() => {
|
|
99
|
+
onsearch?.(value);
|
|
100
|
+
}, debounce);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handleClear() {
|
|
104
|
+
value = '';
|
|
105
|
+
clearTimeout(debounceTimer);
|
|
106
|
+
onsearch?.('');
|
|
107
|
+
onclear?.();
|
|
108
|
+
inputEl?.focus();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** @param {KeyboardEvent} e */
|
|
112
|
+
function handleKeydown(e) {
|
|
113
|
+
if (e.key === 'Escape') {
|
|
114
|
+
if (holdValueOnEscape) {
|
|
115
|
+
// Let the event propagate (parent modal handles Escape)
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (hasValue) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
handleClear();
|
|
122
|
+
} else if (collapsible) {
|
|
123
|
+
collapsed = true;
|
|
124
|
+
inputEl?.blur();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function handleFocus() {
|
|
130
|
+
focused = true;
|
|
131
|
+
if (collapsible && collapsed) {
|
|
132
|
+
collapsed = false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function handleBlur() {
|
|
137
|
+
focused = false;
|
|
138
|
+
if (collapsible && !hasValue) {
|
|
139
|
+
collapsed = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handleExpandClick() {
|
|
144
|
+
collapsed = false;
|
|
145
|
+
// Focus after the transition
|
|
146
|
+
requestAnimationFrame(() => {
|
|
147
|
+
inputEl?.focus();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Cleanup on destroy
|
|
152
|
+
$effect(() => {
|
|
153
|
+
return () => clearTimeout(debounceTimer);
|
|
154
|
+
});
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
{#if collapsed}
|
|
158
|
+
<button
|
|
159
|
+
class="search-collapsed search-collapsed-{size} {className}"
|
|
160
|
+
onclick={handleExpandClick}
|
|
161
|
+
aria-label={label}
|
|
162
|
+
{disabled}
|
|
163
|
+
{...rest}
|
|
164
|
+
>
|
|
165
|
+
<svg class="search-icon search-icon-{size}" viewBox="0 0 256 256" fill="none" aria-hidden="true">
|
|
166
|
+
<circle cx="115.5" cy="115.5" r="67.5" stroke="currentColor" stroke-width="16" fill="none"/>
|
|
167
|
+
<line x1="164.2" y1="164.2" x2="224" y2="224" stroke="currentColor" stroke-width="16" stroke-linecap="round"/>
|
|
168
|
+
</svg>
|
|
169
|
+
</button>
|
|
170
|
+
{:else}
|
|
171
|
+
<div
|
|
172
|
+
class="search-group {className}"
|
|
173
|
+
class:search-group-focused={focused}
|
|
174
|
+
role="search"
|
|
175
|
+
aria-label={label}
|
|
176
|
+
{...rest}
|
|
177
|
+
>
|
|
178
|
+
<label class="sr-only" for={inputId}>{label}</label>
|
|
179
|
+
|
|
180
|
+
<span class="search-leading search-leading-{size}" aria-hidden="true">
|
|
181
|
+
{#if loading}
|
|
182
|
+
<span class="search-spinner search-spinner-{size}"></span>
|
|
183
|
+
{:else}
|
|
184
|
+
<svg class="search-icon search-icon-{size}" viewBox="0 0 256 256" fill="none">
|
|
185
|
+
<circle cx="115.5" cy="115.5" r="67.5" stroke="currentColor" stroke-width="16" fill="none"/>
|
|
186
|
+
<line x1="164.2" y1="164.2" x2="224" y2="224" stroke="currentColor" stroke-width="16" stroke-linecap="round"/>
|
|
187
|
+
</svg>
|
|
188
|
+
{/if}
|
|
189
|
+
</span>
|
|
190
|
+
|
|
191
|
+
<input
|
|
192
|
+
bind:this={inputEl}
|
|
193
|
+
id={inputId}
|
|
194
|
+
type="search"
|
|
195
|
+
class="search-input search-input-{size}"
|
|
196
|
+
{placeholder}
|
|
197
|
+
{disabled}
|
|
198
|
+
bind:value
|
|
199
|
+
oninput={handleInput}
|
|
200
|
+
onkeydown={handleKeydown}
|
|
201
|
+
onfocus={handleFocus}
|
|
202
|
+
onblur={handleBlur}
|
|
203
|
+
autocomplete="off"
|
|
204
|
+
spellcheck="false"
|
|
205
|
+
aria-label={label}
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
{#if hasValue}
|
|
209
|
+
<button
|
|
210
|
+
class="search-clear search-clear-{size}"
|
|
211
|
+
onclick={handleClear}
|
|
212
|
+
aria-label="Clear search"
|
|
213
|
+
tabindex="-1"
|
|
214
|
+
type="button"
|
|
215
|
+
>
|
|
216
|
+
<svg viewBox="0 0 256 256" fill="none" aria-hidden="true">
|
|
217
|
+
<line x1="80" y1="80" x2="176" y2="176" stroke="currentColor" stroke-width="16" stroke-linecap="round"/>
|
|
218
|
+
<line x1="176" y1="80" x2="80" y2="176" stroke="currentColor" stroke-width="16" stroke-linecap="round"/>
|
|
219
|
+
</svg>
|
|
220
|
+
</button>
|
|
221
|
+
{:else if showShortcut}
|
|
222
|
+
<kbd class="search-shortcut search-shortcut-{size}">{shortcutHint}</kbd>
|
|
223
|
+
{/if}
|
|
224
|
+
</div>
|
|
225
|
+
{/if}
|
|
226
|
+
|
|
227
|
+
<style>
|
|
228
|
+
.sr-only {
|
|
229
|
+
position: absolute;
|
|
230
|
+
width: 1px;
|
|
231
|
+
height: 1px;
|
|
232
|
+
padding: 0;
|
|
233
|
+
margin: -1px;
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
clip: rect(0, 0, 0, 0);
|
|
236
|
+
white-space: nowrap;
|
|
237
|
+
border-width: 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* ─── Container ─── */
|
|
241
|
+
.search-group {
|
|
242
|
+
position: relative;
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
width: 100%;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* ─── Input ─── */
|
|
249
|
+
.search-input {
|
|
250
|
+
font-family: var(--input-font);
|
|
251
|
+
font-size: var(--input-font-size);
|
|
252
|
+
border: var(--input-border);
|
|
253
|
+
border-radius: var(--input-radius);
|
|
254
|
+
background: var(--input-bg);
|
|
255
|
+
color: var(--input-text);
|
|
256
|
+
transition: border var(--input-transition);
|
|
257
|
+
width: 100%;
|
|
258
|
+
/* Remove native search decorations */
|
|
259
|
+
-webkit-appearance: none;
|
|
260
|
+
appearance: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.search-input::-webkit-search-cancel-button,
|
|
264
|
+
.search-input::-webkit-search-decoration {
|
|
265
|
+
display: none;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.search-input::placeholder {
|
|
269
|
+
color: var(--input-placeholder);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.search-input:focus {
|
|
273
|
+
outline: none;
|
|
274
|
+
border: var(--input-border-focus);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.search-input:disabled {
|
|
278
|
+
opacity: 0.5;
|
|
279
|
+
cursor: not-allowed;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* Size variants — padding accounts for icon + clear/shortcut */
|
|
283
|
+
.search-input-sm {
|
|
284
|
+
height: var(--input-sm-height);
|
|
285
|
+
padding: 0 var(--input-sm-padding-x);
|
|
286
|
+
padding-left: calc(var(--input-sm-padding-x) + var(--search-icon-size-sm) + var(--space-xs));
|
|
287
|
+
padding-right: calc(var(--input-sm-padding-x) + var(--search-icon-size-sm) + var(--space-xs));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.search-input-md {
|
|
291
|
+
height: var(--input-md-height);
|
|
292
|
+
padding: 0 var(--input-md-padding-x);
|
|
293
|
+
padding-left: calc(var(--input-md-padding-x) + var(--search-icon-size-md) + var(--space-xs));
|
|
294
|
+
padding-right: calc(var(--input-md-padding-x) + var(--search-icon-size-md) + var(--space-xs));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.search-input-lg {
|
|
298
|
+
height: var(--input-lg-height);
|
|
299
|
+
padding: 0 var(--input-lg-padding-x);
|
|
300
|
+
padding-left: calc(var(--input-lg-padding-x) + var(--search-icon-size-lg) + var(--space-xs));
|
|
301
|
+
padding-right: calc(var(--input-lg-padding-x) + var(--search-icon-size-lg) + var(--space-xs));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* ─── Leading icon / spinner ─── */
|
|
305
|
+
.search-leading {
|
|
306
|
+
position: absolute;
|
|
307
|
+
top: 50%;
|
|
308
|
+
transform: translateY(-50%);
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
justify-content: center;
|
|
312
|
+
color: var(--search-icon-color);
|
|
313
|
+
pointer-events: none;
|
|
314
|
+
transition: color var(--input-transition);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.search-group-focused .search-leading {
|
|
318
|
+
color: var(--search-icon-color-focus);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.search-leading-sm { left: var(--input-sm-padding-x); }
|
|
322
|
+
.search-leading-md { left: var(--input-md-padding-x); }
|
|
323
|
+
.search-leading-lg { left: var(--input-lg-padding-x); }
|
|
324
|
+
|
|
325
|
+
.search-icon {
|
|
326
|
+
display: block;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.search-icon-sm { width: var(--search-icon-size-sm); height: var(--search-icon-size-sm); }
|
|
330
|
+
.search-icon-md { width: var(--search-icon-size-md); height: var(--search-icon-size-md); }
|
|
331
|
+
.search-icon-lg { width: var(--search-icon-size-lg); height: var(--search-icon-size-lg); }
|
|
332
|
+
|
|
333
|
+
/* ─── Spinner ─── */
|
|
334
|
+
.search-spinner {
|
|
335
|
+
border: var(--border-width-thick) solid var(--color-border);
|
|
336
|
+
border-top-color: var(--search-spinner-color);
|
|
337
|
+
border-radius: var(--radius-circle);
|
|
338
|
+
animation: search-spin 0.6s linear infinite;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.search-spinner-sm { width: var(--search-icon-size-sm); height: var(--search-icon-size-sm); }
|
|
342
|
+
.search-spinner-md { width: var(--search-icon-size-md); height: var(--search-icon-size-md); }
|
|
343
|
+
.search-spinner-lg { width: var(--search-icon-size-lg); height: var(--search-icon-size-lg); }
|
|
344
|
+
|
|
345
|
+
@keyframes search-spin {
|
|
346
|
+
to { transform: rotate(360deg); }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* ─── Clear button ─── */
|
|
350
|
+
.search-clear {
|
|
351
|
+
all: unset;
|
|
352
|
+
box-sizing: border-box;
|
|
353
|
+
position: absolute;
|
|
354
|
+
top: 50%;
|
|
355
|
+
transform: translateY(-50%);
|
|
356
|
+
display: flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
justify-content: center;
|
|
359
|
+
cursor: pointer;
|
|
360
|
+
color: var(--search-clear-color);
|
|
361
|
+
border-radius: var(--radius-sm);
|
|
362
|
+
transition: color var(--input-transition);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.search-clear:hover {
|
|
366
|
+
color: var(--search-clear-color-hover);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.search-clear:focus-visible {
|
|
370
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
371
|
+
outline-offset: var(--focus-ring-offset);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.search-clear svg {
|
|
375
|
+
width: 100%;
|
|
376
|
+
height: 100%;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.search-clear-sm {
|
|
380
|
+
right: var(--input-sm-padding-x);
|
|
381
|
+
width: var(--search-icon-size-sm);
|
|
382
|
+
height: var(--search-icon-size-sm);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.search-clear-md {
|
|
386
|
+
right: var(--input-md-padding-x);
|
|
387
|
+
width: var(--search-icon-size-md);
|
|
388
|
+
height: var(--search-icon-size-md);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.search-clear-lg {
|
|
392
|
+
right: var(--input-lg-padding-x);
|
|
393
|
+
width: var(--search-icon-size-lg);
|
|
394
|
+
height: var(--search-icon-size-lg);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* ─── Shortcut badge ─── */
|
|
398
|
+
.search-shortcut {
|
|
399
|
+
position: absolute;
|
|
400
|
+
top: 50%;
|
|
401
|
+
transform: translateY(-50%);
|
|
402
|
+
font-family: var(--search-shortcut-font);
|
|
403
|
+
font-size: var(--search-shortcut-size);
|
|
404
|
+
color: var(--search-shortcut-color);
|
|
405
|
+
background: var(--search-shortcut-bg);
|
|
406
|
+
border-radius: var(--search-shortcut-radius);
|
|
407
|
+
padding: var(--search-shortcut-padding);
|
|
408
|
+
pointer-events: none;
|
|
409
|
+
line-height: 1;
|
|
410
|
+
border: var(--elevation-border);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.search-shortcut-sm { right: var(--input-sm-padding-x); }
|
|
414
|
+
.search-shortcut-md { right: var(--input-md-padding-x); }
|
|
415
|
+
.search-shortcut-lg { right: var(--input-lg-padding-x); }
|
|
416
|
+
|
|
417
|
+
/* ─── Collapsed (icon-only) ─── */
|
|
418
|
+
.search-collapsed {
|
|
419
|
+
all: unset;
|
|
420
|
+
box-sizing: border-box;
|
|
421
|
+
cursor: pointer;
|
|
422
|
+
display: flex;
|
|
423
|
+
align-items: center;
|
|
424
|
+
justify-content: center;
|
|
425
|
+
border: var(--input-border);
|
|
426
|
+
border-radius: var(--input-radius);
|
|
427
|
+
background: var(--input-bg);
|
|
428
|
+
color: var(--search-icon-color);
|
|
429
|
+
transition: all var(--search-collapse-transition);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.search-collapsed:hover {
|
|
433
|
+
color: var(--search-icon-color-focus);
|
|
434
|
+
border: var(--input-border-focus);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.search-collapsed:focus-visible {
|
|
438
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
439
|
+
outline-offset: var(--focus-ring-offset);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.search-collapsed:disabled {
|
|
443
|
+
opacity: 0.5;
|
|
444
|
+
cursor: not-allowed;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.search-collapsed-sm {
|
|
448
|
+
width: var(--input-sm-height);
|
|
449
|
+
height: var(--input-sm-height);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.search-collapsed-md {
|
|
453
|
+
width: var(--input-md-height);
|
|
454
|
+
height: var(--input-md-height);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.search-collapsed-lg {
|
|
458
|
+
width: var(--input-lg-height);
|
|
459
|
+
height: var(--input-lg-height);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
@media (prefers-reduced-motion: reduce) {
|
|
463
|
+
.search-input,
|
|
464
|
+
.search-leading,
|
|
465
|
+
.search-clear,
|
|
466
|
+
.search-collapsed {
|
|
467
|
+
transition: none;
|
|
468
|
+
}
|
|
469
|
+
.search-spinner {
|
|
470
|
+
animation: none;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
</style>
|
package/components/Select.svelte
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
Native select with label, help text, and error state.
|
|
5
5
|
Consumes --input-* tokens from components.css.
|
|
6
6
|
|
|
7
|
+
Usage patterns:
|
|
8
|
+
- Two-way binding: `<Select bind:value={myVar} options={opts} />`
|
|
9
|
+
- Controlled: `<Select value={myVar} onchange={(val) => myVar = val} options={opts} />`
|
|
10
|
+
|
|
7
11
|
@example
|
|
8
12
|
<Select label="COUNTRY" placeholder="Select a country" options={[
|
|
9
13
|
{ value: 'pt', label: 'Portugal' },
|
|
@@ -25,10 +29,12 @@
|
|
|
25
29
|
label = undefined,
|
|
26
30
|
/** @type {string | undefined} */
|
|
27
31
|
placeholder = undefined,
|
|
28
|
-
/** @type {string} */
|
|
29
|
-
value = $bindable(
|
|
32
|
+
/** @type {string | undefined} */
|
|
33
|
+
value = $bindable(),
|
|
30
34
|
/** @type {Option[]} */
|
|
31
35
|
options = [],
|
|
36
|
+
/** @type {((value: string) => void) | undefined} */
|
|
37
|
+
onchange = undefined,
|
|
32
38
|
/** @type {string | undefined} */
|
|
33
39
|
help = undefined,
|
|
34
40
|
/** @type {string | undefined} */
|
|
@@ -48,6 +54,12 @@
|
|
|
48
54
|
const selectId = $derived(id ?? fallbackId);
|
|
49
55
|
const hintId = $derived(`${selectId}-hint`);
|
|
50
56
|
const hasHint = $derived(!!error || !!help);
|
|
57
|
+
|
|
58
|
+
let mounted = false;
|
|
59
|
+
$effect(() => {
|
|
60
|
+
if (!mounted) { mounted = true; return; }
|
|
61
|
+
if (onchange && value !== undefined) onchange(value);
|
|
62
|
+
});
|
|
51
63
|
</script>
|
|
52
64
|
|
|
53
65
|
<div class="input-group {className}">
|
package/components/index.js
CHANGED
|
@@ -45,6 +45,11 @@ export { default as MenuSeparator } from "./MenuSeparator.svelte";
|
|
|
45
45
|
// Form controls — composite
|
|
46
46
|
export { default as Combobox } from "./Combobox.svelte";
|
|
47
47
|
|
|
48
|
+
// Search
|
|
49
|
+
export { default as SearchInput } from "./SearchInput.svelte";
|
|
50
|
+
export { default as FilterBar } from "./FilterBar.svelte";
|
|
51
|
+
export { default as CommandPalette } from "./CommandPalette.svelte";
|
|
52
|
+
|
|
48
53
|
// Tabs
|
|
49
54
|
export { default as Tabs } from "./Tabs.svelte";
|
|
50
55
|
export { default as TabList } from "./TabList.svelte";
|
|
@@ -70,3 +75,4 @@ export { default as CodeEditor } from "./CodeEditor.svelte";
|
|
|
70
75
|
export { default as CollapsibleSection } from "./CollapsibleSection.svelte";
|
|
71
76
|
export { default as OptionGrid } from "./OptionGrid.svelte";
|
|
72
77
|
export { default as ConditionTable } from "./ConditionTable.svelte";
|
|
78
|
+
export { default as LogViewer } from "./LogViewer.svelte";
|
package/package.json
CHANGED
package/tokens/base.css
CHANGED
|
@@ -119,6 +119,14 @@
|
|
|
119
119
|
--raw-tracking-micro: 0.005em;
|
|
120
120
|
--raw-tracking-loose: 0.01em;
|
|
121
121
|
|
|
122
|
+
/* ─── Icon Sizes ─── */
|
|
123
|
+
--raw-icon-12: 0.75rem;
|
|
124
|
+
--raw-icon-16: 1rem;
|
|
125
|
+
--raw-icon-20: 1.25rem;
|
|
126
|
+
--raw-icon-24: 1.5rem;
|
|
127
|
+
--raw-icon-32: 2rem;
|
|
128
|
+
--raw-icon-48: 3rem;
|
|
129
|
+
|
|
122
130
|
/* ─── Spacing ─── */
|
|
123
131
|
--raw-space-2: 0.125rem;
|
|
124
132
|
--raw-space-4: 0.25rem;
|