@aiaiai-pt/design-system 0.1.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 (52) hide show
  1. package/components/Alert.svelte +100 -0
  2. package/components/Badge.svelte +108 -0
  3. package/components/BottomNav.svelte +37 -0
  4. package/components/BottomNavItem.svelte +121 -0
  5. package/components/Button.svelte +269 -0
  6. package/components/Card.svelte +108 -0
  7. package/components/Checkbox.svelte +138 -0
  8. package/components/CodeBlock.svelte +187 -0
  9. package/components/CodeEditor.svelte +221 -0
  10. package/components/CollapsibleSection.svelte +160 -0
  11. package/components/Combobox.svelte +396 -0
  12. package/components/EmptyState.svelte +148 -0
  13. package/components/FileUpload.svelte +280 -0
  14. package/components/FileUploadItem.svelte +222 -0
  15. package/components/Input.svelte +222 -0
  16. package/components/KeyValue.svelte +79 -0
  17. package/components/Label.svelte +49 -0
  18. package/components/List.svelte +70 -0
  19. package/components/ListItem.svelte +125 -0
  20. package/components/Menu.svelte +161 -0
  21. package/components/MenuItem.svelte +120 -0
  22. package/components/MenuSeparator.svelte +34 -0
  23. package/components/Modal.svelte +260 -0
  24. package/components/OptionGrid.svelte +195 -0
  25. package/components/Panel.svelte +256 -0
  26. package/components/Popover.svelte +194 -0
  27. package/components/Progress.svelte +78 -0
  28. package/components/Select.svelte +182 -0
  29. package/components/Separator.svelte +47 -0
  30. package/components/Sidebar.svelte +106 -0
  31. package/components/SidebarItem.svelte +154 -0
  32. package/components/SidebarSection.svelte +43 -0
  33. package/components/Skeleton.svelte +79 -0
  34. package/components/Status.svelte +104 -0
  35. package/components/Stepper.svelte +142 -0
  36. package/components/Tab.svelte +94 -0
  37. package/components/TabList.svelte +36 -0
  38. package/components/TabPanel.svelte +45 -0
  39. package/components/Tabs.svelte +46 -0
  40. package/components/Tag.svelte +96 -0
  41. package/components/Textarea.svelte +143 -0
  42. package/components/Toast.svelte +114 -0
  43. package/components/Toggle.svelte +132 -0
  44. package/components/index.js +70 -0
  45. package/package.json +45 -0
  46. package/tokens/base.css +175 -0
  47. package/tokens/components.css +530 -0
  48. package/tokens/semantic.css +211 -0
  49. package/tokens/themes/aiaiai.css +53 -0
  50. package/tokens/themes/bespoke-example.css +148 -0
  51. package/tokens/themes/branded-example.css +55 -0
  52. package/tokens/utilities.css +1865 -0
@@ -0,0 +1,160 @@
1
+ <!--
2
+ @component CollapsibleSection
3
+
4
+ Animated expand/collapse container for grouped content.
5
+ Built on native <details>/<summary> for no-JS accessibility.
6
+ Consumes --type-label-* and --color-* tokens.
7
+
8
+ @example Basic
9
+ <CollapsibleSection title="Advanced Options" bind:open>
10
+ <p>Content here</p>
11
+ </CollapsibleSection>
12
+
13
+ @example With badge count and action buttons
14
+ <CollapsibleSection title="Filters" bind:open badge={3}>
15
+ {#snippet actions()}
16
+ <Button variant="ghost" size="sm">Clear</Button>
17
+ {/snippet}
18
+ <p>Filter content</p>
19
+ </CollapsibleSection>
20
+ -->
21
+ <script>
22
+ import Badge from './Badge.svelte';
23
+
24
+ let {
25
+ /** @type {string} */
26
+ title = '',
27
+ /** @type {boolean} */
28
+ open = $bindable(false),
29
+ /** @type {string | number | undefined} */
30
+ badge = undefined,
31
+ /** @type {boolean} */
32
+ defaultOpen = false,
33
+ /** @type {string} */
34
+ class: className = '',
35
+ /** @type {import('svelte').Snippet | undefined} */
36
+ actions = undefined,
37
+ /** @type {import('svelte').Snippet | undefined} */
38
+ children = undefined,
39
+ ...rest
40
+ } = $props();
41
+
42
+ // Seed open from defaultOpen at creation time (mount-only, no effect needed)
43
+ if (defaultOpen && !open) open = defaultOpen;
44
+
45
+ /** @param {Event} e */
46
+ function handleToggle(e) {
47
+ open = /** @type {HTMLDetailsElement} */ (e.currentTarget).open;
48
+ }
49
+ </script>
50
+
51
+ <details
52
+ class="collapsible-section {className}"
53
+ {open}
54
+ ontoggle={handleToggle}
55
+ {...rest}
56
+ >
57
+ <summary class="collapsible-summary">
58
+ <div class="collapsible-header">
59
+ <svg
60
+ class="collapsible-caret"
61
+ class:collapsible-caret--open={open}
62
+ width="14"
63
+ height="14"
64
+ viewBox="0 0 14 14"
65
+ fill="none"
66
+ aria-hidden="true"
67
+ >
68
+ <path d="M5 3L10 7L5 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
69
+ </svg>
70
+ <span class="collapsible-title">{title}</span>
71
+ {#if badge !== undefined}
72
+ <Badge variant="neutral">{badge}</Badge>
73
+ {/if}
74
+ </div>
75
+ {#if actions}
76
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
77
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
78
+ <div class="collapsible-actions" onclick={(e) => e.stopPropagation()}>
79
+ {@render actions()}
80
+ </div>
81
+ {/if}
82
+ </summary>
83
+
84
+ {#if open && children}
85
+ <div class="collapsible-content">
86
+ {@render children()}
87
+ </div>
88
+ {/if}
89
+ </details>
90
+
91
+ <style>
92
+ .collapsible-section {
93
+ border-bottom: var(--elevation-border);
94
+ }
95
+
96
+ .collapsible-summary {
97
+ display: flex;
98
+ align-items: center;
99
+ justify-content: space-between;
100
+ cursor: pointer;
101
+ user-select: none;
102
+ padding: var(--space-sm) 0;
103
+ list-style: none;
104
+ }
105
+
106
+ /* Remove default marker */
107
+ .collapsible-summary::-webkit-details-marker {
108
+ display: none;
109
+ }
110
+ .collapsible-summary::marker {
111
+ display: none;
112
+ content: '';
113
+ }
114
+
115
+ .collapsible-header {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: var(--space-sm);
119
+ }
120
+
121
+ .collapsible-caret {
122
+ color: var(--color-text-secondary);
123
+ transition: transform var(--duration-fast) var(--easing-default);
124
+ flex-shrink: 0;
125
+ }
126
+
127
+ .collapsible-caret--open {
128
+ transform: rotate(90deg);
129
+ }
130
+
131
+ .collapsible-title {
132
+ font-family: var(--type-label-font);
133
+ font-size: var(--type-label-size);
134
+ font-weight: var(--type-label-weight);
135
+ letter-spacing: var(--type-label-tracking);
136
+ line-height: var(--type-label-leading);
137
+ text-transform: uppercase;
138
+ color: var(--color-text-secondary);
139
+ }
140
+
141
+ .collapsible-actions {
142
+ display: flex;
143
+ align-items: center;
144
+ gap: var(--space-xs);
145
+ }
146
+
147
+ .collapsible-content {
148
+ padding-top: var(--space-md);
149
+ padding-bottom: var(--space-md);
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: var(--space-sm);
153
+ }
154
+
155
+ @media (prefers-reduced-motion: reduce) {
156
+ .collapsible-caret {
157
+ transition: none;
158
+ }
159
+ }
160
+ </style>
@@ -0,0 +1,396 @@
1
+ <!--
2
+ @component Combobox
3
+
4
+ Searchable select composing Input + Popover + filtered list.
5
+ Supports grouped items, description text, and highlighted matches.
6
+ Consumes --combobox-* tokens from components.css.
7
+
8
+ @example Basic
9
+ <Combobox
10
+ label="FUNCTION"
11
+ items={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]}
12
+ bind:value={selected}
13
+ />
14
+
15
+ @example With groups and descriptions
16
+ <Combobox
17
+ label="NODE TYPE"
18
+ placeholder="Search node types..."
19
+ items={[
20
+ { value: 'llm', label: 'LLM', group: 'Processing', description: 'AI model call' },
21
+ { value: 'fn', label: 'Function', group: 'Integration', description: 'HTTP endpoint' },
22
+ ]}
23
+ bind:value={selected}
24
+ />
25
+ -->
26
+ <script module>
27
+ let _comboboxUid = 0;
28
+ </script>
29
+
30
+ <script>
31
+ /**
32
+ * @typedef {{ value: string, label: string, group?: string, description?: string }} ComboboxItem
33
+ */
34
+
35
+ import Popover from './Popover.svelte';
36
+
37
+ let {
38
+ /** @type {ComboboxItem[]} */
39
+ items = [],
40
+ /** @type {string} */
41
+ value = $bindable(''),
42
+ /** @type {string | undefined} */
43
+ label = undefined,
44
+ /** @type {string | undefined} */
45
+ placeholder = 'Search...',
46
+ /** @type {boolean} */
47
+ disabled = false,
48
+ /** @type {string | undefined} */
49
+ error = undefined,
50
+ /** @type {string | undefined} */
51
+ help = undefined,
52
+ /** @type {string} */
53
+ size = 'md',
54
+ /** @type {((value: string) => void) | undefined} */
55
+ onchange = undefined,
56
+ /** @type {string} */
57
+ class: className = '',
58
+ ...rest
59
+ } = $props();
60
+
61
+ const comboboxId = `combobox-${_comboboxUid++}`;
62
+ const listboxId = `${comboboxId}-listbox`;
63
+
64
+ /** @type {HTMLInputElement | undefined} */
65
+ let inputEl;
66
+ /** @type {HTMLElement | undefined} */
67
+ let anchorEl = $state();
68
+
69
+ let open = $state(false);
70
+ let query = $state('');
71
+ let activeIndex = $state(-1);
72
+
73
+ const hintId = $derived(`${comboboxId}-hint`);
74
+ const hasHint = $derived(!!error || !!help);
75
+
76
+ // Selected item label for display
77
+ const selectedItem = $derived(items.find(i => i.value === value));
78
+
79
+ // Filter items by query
80
+ const filtered = $derived.by(() => {
81
+ if (!query) return items;
82
+ const q = query.toLowerCase();
83
+ return items.filter(i =>
84
+ i.label.toLowerCase().includes(q) ||
85
+ (i.description && i.description.toLowerCase().includes(q))
86
+ );
87
+ });
88
+
89
+ // Group filtered items
90
+ const grouped = $derived.by(() => {
91
+ /** @type {Map<string, ComboboxItem[]>} */
92
+ const groups = new Map();
93
+ for (const item of filtered) {
94
+ const key = item.group ?? '';
95
+ if (!groups.has(key)) groups.set(key, []);
96
+ groups.get(key)?.push(item);
97
+ }
98
+ return groups;
99
+ });
100
+
101
+ // Precompute index map for O(1) lookup in grouped render loop
102
+ const filteredIndexMap = $derived(new Map(filtered.map((item, i) => [item, i])));
103
+
104
+ /**
105
+ * @param {ComboboxItem} item
106
+ */
107
+ function selectItem(item) {
108
+ value = item.value;
109
+ query = '';
110
+ open = false;
111
+ onchange?.(item.value);
112
+ inputEl?.focus();
113
+ }
114
+
115
+ /** @param {KeyboardEvent} e */
116
+ function handleKeydown(e) {
117
+ if (!open) {
118
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter') {
119
+ e.preventDefault();
120
+ open = true;
121
+ return;
122
+ }
123
+ return;
124
+ }
125
+
126
+ switch (e.key) {
127
+ case 'ArrowDown':
128
+ e.preventDefault();
129
+ activeIndex = (activeIndex + 1) % filtered.length;
130
+ scrollActiveIntoView();
131
+ break;
132
+ case 'ArrowUp':
133
+ e.preventDefault();
134
+ activeIndex = activeIndex <= 0 ? filtered.length - 1 : activeIndex - 1;
135
+ scrollActiveIntoView();
136
+ break;
137
+ case 'Enter':
138
+ e.preventDefault();
139
+ if (activeIndex >= 0 && activeIndex < filtered.length) {
140
+ selectItem(filtered[activeIndex]);
141
+ }
142
+ break;
143
+ case 'Escape':
144
+ // Popover handles Escape close
145
+ break;
146
+ case 'Tab':
147
+ open = false;
148
+ break;
149
+ }
150
+ }
151
+
152
+ function scrollActiveIntoView() {
153
+ requestAnimationFrame(() => {
154
+ const el = document.getElementById(`${comboboxId}-option-${activeIndex}`);
155
+ el?.scrollIntoView({ block: 'nearest' });
156
+ });
157
+ }
158
+
159
+ function handleInput() {
160
+ if (!open) open = true;
161
+ activeIndex = -1;
162
+ }
163
+
164
+ function handleFocus() {
165
+ if (!disabled) open = true;
166
+ }
167
+
168
+ function handlePopoverClose() {
169
+ query = '';
170
+ activeIndex = -1;
171
+ }
172
+
173
+ /**
174
+ * Highlight matching text in a label
175
+ * @param {string} text
176
+ * @param {string} q
177
+ * @returns {{ before: string, match: string, after: string } | null}
178
+ */
179
+ function getHighlight(text, q) {
180
+ if (!q) return null;
181
+ const lower = text.toLowerCase();
182
+ const idx = lower.indexOf(q.toLowerCase());
183
+ if (idx === -1) return null;
184
+ return {
185
+ before: text.slice(0, idx),
186
+ match: text.slice(idx, idx + q.length),
187
+ after: text.slice(idx + q.length),
188
+ };
189
+ }
190
+ </script>
191
+
192
+ <div class="combobox {className}" bind:this={anchorEl} {...rest}>
193
+ {#if label}
194
+ <label class="combobox-label" for={comboboxId}>{label}</label>
195
+ {/if}
196
+
197
+ <input
198
+ bind:this={inputEl}
199
+ id={comboboxId}
200
+ type="text"
201
+ class="combobox-input combobox-input-{size}"
202
+ class:combobox-input-error={!!error}
203
+ role="combobox"
204
+ aria-expanded={open}
205
+ aria-controls={listboxId}
206
+ aria-activedescendant={activeIndex >= 0 ? `${comboboxId}-option-${activeIndex}` : undefined}
207
+ aria-invalid={error ? true : undefined}
208
+ aria-describedby={hasHint ? hintId : undefined}
209
+ {placeholder}
210
+ {disabled}
211
+ value={open ? query : (selectedItem?.label ?? '')}
212
+ oninput={(e) => { query = /** @type {HTMLInputElement} */ (e.currentTarget).value; handleInput(); }}
213
+ onkeydown={handleKeydown}
214
+ onfocus={handleFocus}
215
+ autocomplete="off"
216
+ />
217
+
218
+ {#if error}
219
+ <span id={hintId} class="combobox-error-text" role="alert">{error}</span>
220
+ {:else if help}
221
+ <span id={hintId} class="combobox-help">{help}</span>
222
+ {/if}
223
+
224
+ <Popover bind:open anchor={anchorEl} matchWidth onclose={handlePopoverClose}>
225
+ <ul
226
+ id={listboxId}
227
+ class="combobox-list"
228
+ role="listbox"
229
+ aria-label={label ?? 'Options'}
230
+ >
231
+ {#if filtered.length === 0}
232
+ <li class="combobox-empty" role="option" aria-selected="false" aria-disabled="true">
233
+ No results found
234
+ </li>
235
+ {:else}
236
+ {#each [...grouped.entries()] as [group, groupItems]}
237
+ {#if group}
238
+ <li class="combobox-group" role="presentation">{group}</li>
239
+ {/if}
240
+ {#each groupItems as item}
241
+ {@const idx = filteredIndexMap.get(item) ?? -1}
242
+ {@const isActive = idx === activeIndex}
243
+ {@const isSelected = item.value === value}
244
+ {@const hl = getHighlight(item.label, query)}
245
+ <li
246
+ id="{comboboxId}-option-{idx}"
247
+ class="combobox-item"
248
+ class:combobox-item-active={isActive}
249
+ class:combobox-item-selected={isSelected}
250
+ role="option"
251
+ aria-selected={isSelected}
252
+ onmousedown={(e) => { e.preventDefault(); selectItem(item); }}
253
+ onmouseenter={() => { activeIndex = idx; }}
254
+ >
255
+ <span class="combobox-item-label">
256
+ {#if hl}
257
+ {hl.before}<mark class="combobox-highlight">{hl.match}</mark>{hl.after}
258
+ {:else}
259
+ {item.label}
260
+ {/if}
261
+ </span>
262
+ {#if item.description}
263
+ <span class="combobox-item-description">{item.description}</span>
264
+ {/if}
265
+ </li>
266
+ {/each}
267
+ {/each}
268
+ {/if}
269
+ </ul>
270
+ </Popover>
271
+ </div>
272
+
273
+ <style>
274
+ .combobox {
275
+ display: flex;
276
+ flex-direction: column;
277
+ gap: var(--input-label-gap);
278
+ width: 100%;
279
+ position: relative;
280
+ }
281
+
282
+ .combobox-label {
283
+ font-family: var(--input-label-font);
284
+ font-size: var(--input-label-size);
285
+ letter-spacing: var(--input-label-tracking);
286
+ color: var(--input-label-color);
287
+ }
288
+
289
+ .combobox-input {
290
+ font-family: var(--input-font);
291
+ font-size: var(--input-font-size);
292
+ border: var(--input-border);
293
+ border-radius: var(--input-radius);
294
+ background: var(--input-bg);
295
+ color: var(--input-text);
296
+ transition: border var(--input-transition);
297
+ width: 100%;
298
+ }
299
+
300
+ .combobox-input-sm { height: var(--input-sm-height); padding: 0 var(--input-sm-padding-x); }
301
+ .combobox-input-md { height: var(--input-md-height); padding: 0 var(--input-md-padding-x); }
302
+ .combobox-input-lg { height: var(--input-lg-height); padding: 0 var(--input-lg-padding-x); }
303
+
304
+ .combobox-input::placeholder { color: var(--input-placeholder); }
305
+ .combobox-input:focus { outline: none; border: var(--input-border-focus); }
306
+ .combobox-input:disabled { opacity: 0.5; cursor: not-allowed; }
307
+ .combobox-input-error { border-color: var(--input-error-border-color); }
308
+
309
+ .combobox-help {
310
+ font-family: var(--input-help-font);
311
+ font-size: var(--input-help-size);
312
+ color: var(--input-help-color);
313
+ }
314
+
315
+ .combobox-error-text {
316
+ font-family: var(--input-help-font);
317
+ font-size: var(--input-help-size);
318
+ color: var(--input-error-text);
319
+ }
320
+
321
+ /* ─── Dropdown list ─── */
322
+ .combobox-list {
323
+ list-style: none;
324
+ margin: 0;
325
+ padding: var(--space-xs) 0;
326
+ max-height: var(--combobox-list-max-height);
327
+ overflow-y: auto;
328
+ }
329
+
330
+ .combobox-group {
331
+ font-family: var(--combobox-group-font);
332
+ font-size: var(--combobox-group-size);
333
+ letter-spacing: var(--combobox-group-tracking);
334
+ color: var(--combobox-group-color);
335
+ padding: var(--combobox-group-padding);
336
+ text-transform: uppercase;
337
+ }
338
+
339
+ .combobox-item {
340
+ display: flex;
341
+ flex-direction: column;
342
+ gap: 2px;
343
+ padding: var(--combobox-item-padding);
344
+ border-radius: var(--combobox-item-radius);
345
+ margin: 0 var(--space-xs);
346
+ cursor: pointer;
347
+ font-family: var(--input-font);
348
+ font-size: var(--input-font-size);
349
+ color: var(--color-text);
350
+ }
351
+
352
+ .combobox-item-active,
353
+ .combobox-item:hover {
354
+ background: var(--combobox-item-hover-bg);
355
+ }
356
+
357
+ .combobox-item-selected {
358
+ background: var(--combobox-item-active-bg);
359
+ color: var(--combobox-item-active-color);
360
+ }
361
+
362
+ .combobox-item-selected.combobox-item-active {
363
+ background: var(--combobox-item-active-bg);
364
+ }
365
+
366
+ .combobox-item-label {
367
+ line-height: 1.4;
368
+ }
369
+
370
+ .combobox-item-description {
371
+ font-family: var(--combobox-description-font);
372
+ font-size: var(--combobox-description-size);
373
+ color: var(--combobox-description-color);
374
+ }
375
+
376
+ .combobox-highlight {
377
+ background: transparent;
378
+ color: var(--combobox-highlight-color);
379
+ font-weight: var(--raw-font-weight-semibold);
380
+ }
381
+
382
+ .combobox-empty {
383
+ padding: var(--combobox-item-padding);
384
+ margin: 0 var(--space-xs);
385
+ color: var(--combobox-empty-color);
386
+ font-family: var(--input-font);
387
+ font-size: var(--input-font-size);
388
+ font-style: italic;
389
+ }
390
+
391
+ @media (prefers-reduced-motion: reduce) {
392
+ .combobox-input {
393
+ transition: none;
394
+ }
395
+ }
396
+ </style>
@@ -0,0 +1,148 @@
1
+ <!--
2
+ @component EmptyState
3
+
4
+ Shown when there's nothing to display. Different contexts need different copy.
5
+ Consumes --empty-* tokens from components.css.
6
+
7
+ @example First use
8
+ <EmptyState
9
+ heading="Create your first project"
10
+ body="Projects organize your work into focused spaces."
11
+ actionLabel="NEW PROJECT"
12
+ onaction={() => create()}
13
+ >
14
+ {#snippet icon()}
15
+ <PhPlusCircle size={48} />
16
+ {/snippet}
17
+ </EmptyState>
18
+
19
+ @example Error recovery
20
+ <EmptyState
21
+ heading="Couldn't load your projects"
22
+ body="The server didn't respond. Check your connection."
23
+ actionLabel="TRY AGAIN"
24
+ actionVariant="secondary"
25
+ onaction={() => retry()}
26
+ >
27
+ {#snippet icon()}
28
+ <PhWarningCircle size={48} />
29
+ {/snippet}
30
+ </EmptyState>
31
+ -->
32
+ <script>
33
+ let {
34
+ /** @type {string} */
35
+ heading,
36
+ /** @type {string | undefined} */
37
+ body = undefined,
38
+ /** @type {string | undefined} */
39
+ actionLabel = undefined,
40
+ /** @type {'primary' | 'secondary'} */
41
+ actionVariant = 'primary',
42
+ /** @type {(() => void) | undefined} */
43
+ onaction = undefined,
44
+ /** @type {string} */
45
+ class: className = '',
46
+ /** @type {import('svelte').Snippet | undefined} */
47
+ icon = undefined,
48
+ ...rest
49
+ } = $props();
50
+ </script>
51
+
52
+ <div class="empty-state {className}" {...rest}>
53
+ {#if icon}
54
+ <div class="empty-icon">{@render icon()}</div>
55
+ {/if}
56
+
57
+ <h3 class="empty-heading">{heading}</h3>
58
+
59
+ {#if body}
60
+ <p class="empty-body">{body}</p>
61
+ {/if}
62
+
63
+ {#if actionLabel && onaction}
64
+ <button
65
+ class="empty-action"
66
+ class:empty-action-secondary={actionVariant === 'secondary'}
67
+ onclick={onaction}
68
+ >
69
+ <span class="empty-action-label">{actionLabel}</span>
70
+ </button>
71
+ {/if}
72
+ </div>
73
+
74
+ <style>
75
+ .empty-state {
76
+ display: flex;
77
+ flex-direction: column;
78
+ align-items: center;
79
+ gap: var(--empty-gap);
80
+ text-align: center;
81
+ padding: var(--space-2xl) var(--space-lg);
82
+ }
83
+
84
+ .empty-icon {
85
+ width: var(--empty-icon-size);
86
+ height: var(--empty-icon-size);
87
+ color: var(--empty-icon-color);
88
+ }
89
+
90
+ .empty-icon :global(svg) {
91
+ width: 100%;
92
+ height: 100%;
93
+ }
94
+
95
+ .empty-heading {
96
+ font-family: var(--empty-heading-font);
97
+ font-size: var(--empty-heading-size);
98
+ color: var(--color-text);
99
+ margin: 0;
100
+ }
101
+
102
+ .empty-body {
103
+ font-family: var(--empty-body-font);
104
+ font-size: var(--empty-body-size);
105
+ color: var(--empty-body-color);
106
+ margin: 0;
107
+ max-width: 320px;
108
+ }
109
+
110
+ .empty-action {
111
+ font-family: var(--button-font);
112
+ font-size: var(--button-md-font-size);
113
+ letter-spacing: var(--button-tracking);
114
+ height: var(--button-md-height);
115
+ padding: 0 var(--button-md-padding-x);
116
+ border: none;
117
+ border-radius: var(--button-radius);
118
+ background: var(--color-accent);
119
+ color: var(--color-text-on-accent);
120
+ cursor: pointer;
121
+ transition: background var(--button-transition);
122
+ }
123
+
124
+ .empty-action:hover {
125
+ background: var(--color-accent-hover);
126
+ }
127
+
128
+ .empty-action:focus-visible {
129
+ outline: var(--focus-ring-width) solid var(--focus-ring-color);
130
+ outline-offset: var(--focus-ring-offset);
131
+ }
132
+
133
+ .empty-action-secondary {
134
+ background: transparent;
135
+ color: var(--color-text);
136
+ border: var(--elevation-border);
137
+ }
138
+
139
+ .empty-action-secondary:hover {
140
+ background: var(--color-surface-secondary);
141
+ }
142
+
143
+ .empty-action-label {
144
+ font-family: inherit;
145
+ font-size: inherit;
146
+ letter-spacing: inherit;
147
+ }
148
+ </style>