svelte-comp 1.3.5 → 1.3.6

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 (46) hide show
  1. package/LICENSE.md +21 -21
  2. package/README.md +101 -101
  3. package/dist/App.svelte +1046 -1046
  4. package/dist/Container.svelte +59 -59
  5. package/dist/app.css +234 -234
  6. package/dist/app.d.ts +10 -10
  7. package/dist/lib/Accordion.svelte +155 -155
  8. package/dist/lib/Badge.svelte +44 -44
  9. package/dist/lib/Button.svelte +185 -185
  10. package/dist/lib/Calendar.svelte +384 -384
  11. package/dist/lib/Card.svelte +103 -103
  12. package/dist/lib/Carousel.svelte +293 -293
  13. package/dist/lib/CheckBox.svelte +210 -210
  14. package/dist/lib/CodeView.svelte +308 -308
  15. package/dist/lib/ColorPicker.svelte +159 -159
  16. package/dist/lib/ContextMenu.svelte +328 -328
  17. package/dist/lib/DatePicker.svelte +246 -246
  18. package/dist/lib/Dialog.svelte +233 -233
  19. package/dist/lib/Field.svelte +299 -299
  20. package/dist/lib/FilePicker.svelte +295 -295
  21. package/dist/lib/Form.svelte +438 -438
  22. package/dist/lib/Hamburger.svelte +217 -217
  23. package/dist/lib/InstallPWA.svelte +94 -94
  24. package/dist/lib/Menu.svelte +623 -623
  25. package/dist/lib/NoticeBase.svelte +140 -140
  26. package/dist/lib/PaginatedCard.svelte +73 -73
  27. package/dist/lib/Pagination.svelte +119 -119
  28. package/dist/lib/PrimaryColorSelect.svelte +111 -111
  29. package/dist/lib/ProgressBar.svelte +141 -141
  30. package/dist/lib/ProgressCircle.svelte +190 -190
  31. package/dist/lib/Radio.svelte +189 -189
  32. package/dist/lib/SearchInput.svelte +104 -104
  33. package/dist/lib/Select.svelte +524 -524
  34. package/dist/lib/Slider.svelte +253 -253
  35. package/dist/lib/Splitter.svelte +159 -159
  36. package/dist/lib/Switch.svelte +168 -168
  37. package/dist/lib/Table.svelte +299 -299
  38. package/dist/lib/Tabs.svelte +213 -213
  39. package/dist/lib/ThemeToggle.svelte +128 -128
  40. package/dist/lib/TimePicker.svelte +312 -312
  41. package/dist/lib/TimePickerNew.svelte +634 -634
  42. package/dist/lib/Toast.svelte +123 -123
  43. package/dist/lib/Tooltip.svelte +110 -110
  44. package/dist/lib/Topbar.svelte +112 -112
  45. package/dist/styles.css +234 -234
  46. package/package.json +52 -52
package/dist/App.svelte CHANGED
@@ -1,1046 +1,1046 @@
1
- <script lang="ts">
2
- import Accordion from "./lib/Accordion.svelte";
3
- import Badge from "./lib/Badge.svelte";
4
- import Button from "./lib/Button.svelte";
5
- import Calendar from "./lib/Calendar.svelte";
6
- import Card from "./lib/Card.svelte";
7
- import Carousel from "./lib/Carousel.svelte";
8
- import CheckBox from "./lib/CheckBox.svelte";
9
- import CodeView from "./lib/CodeView.svelte";
10
- import ColorPicker from "./lib/ColorPicker.svelte";
11
- import ContextMenu from "./lib/ContextMenu.svelte";
12
- import DatePicker from "./lib/DatePicker.svelte";
13
- import Dialog from "./lib/Dialog.svelte";
14
- import Field from "./lib/Field.svelte";
15
- import FilePicker from "./lib/FilePicker.svelte";
16
- import Form from "./lib/Form.svelte";
17
- import InstallPWA from "./lib/InstallPWA.svelte";
18
- import Menu from "./lib/Menu.svelte";
19
- import NoticeBase from "./lib/NoticeBase.svelte";
20
- import PaginatedCard from "./lib/PaginatedCard.svelte";
21
- import Pagination from "./lib/Pagination.svelte";
22
- import PrimaryColorSelect from "./lib/PrimaryColorSelect.svelte";
23
- import ProgressBar from "./lib/ProgressBar.svelte";
24
- import ProgressCircle from "./lib/ProgressCircle.svelte";
25
- import Radio from "./lib/Radio.svelte";
26
- import SearchInput from "./lib/SearchInput.svelte";
27
- import Select from "./lib/Select.svelte";
28
- import Slider from "./lib/Slider.svelte";
29
- import Splitter from "./lib/Splitter.svelte";
30
- import Switch from "./lib/Switch.svelte";
31
- import Table from "./lib/Table.svelte";
32
- import Tabs from "./lib/Tabs.svelte";
33
- import ThemeToggle from "./lib/ThemeToggle.svelte";
34
- import TimePicker from "./lib/TimePicker.svelte";
35
- import Toast from "./lib/Toast.svelte";
36
- import Tooltip from "./lib/Tooltip.svelte";
37
- import Topbar from "./lib/Topbar.svelte";
38
- import type { FieldSchema, MenuAction, MenuItem, SizeKey, ToastVariant } from "./lib/types";
39
- import Container from "./Container.svelte";
40
-
41
- const tabs = [
42
- { id: "overview", label: "Overview" },
43
- { id: "team", label: "Team" },
44
- { id: "table", label: "Data" },
45
- ];
46
- let activeTab = $state(tabs[0].id);
47
-
48
- let sliderValue = $state(68);
49
- let autoplay = $state(true);
50
-
51
- const planOptions = [
52
- { label: "Starter", value: "starter" },
53
- { label: "Pro", value: "pro" },
54
- { label: "Enterprise", value: "enterprise" },
55
- ];
56
-
57
- let selectedPlan = $state(planOptions[1].value);
58
- let featureName = $state("Dashboard 2.0");
59
- let contactEmail = $state("team@studio.dev");
60
- let dateValue = $state<string | null>(null);
61
- let timeValue = $state<string | null>(null);
62
- let accentColor = $state<string | null>("#7c3aed");
63
-
64
- const accordionItems = [
65
- {
66
- title: "Composition",
67
- content:
68
- "Card, Tabs, Table and Carousel let you assemble complex screens without extra layout work.",
69
- },
70
- {
71
- title: "Forms",
72
- content:
73
- "Field, Select, DatePicker, TimePicker and ColorPicker share spacing, tokens and state handling.",
74
- },
75
- {
76
- title: "Feedback",
77
- content:
78
- "ProgressBar, ProgressCircle, Toast and Dialog keep users informed without noise.",
79
- },
80
- ];
81
-
82
- const carouselItems = [
83
- {
84
- title: "Smooth forms",
85
- content:
86
- "Clean fields, tight spacing and built-in hints help ship surveys in minutes.",
87
- },
88
- {
89
- title: "Smart navigation",
90
- content:
91
- "Tabs and Accordion keep content nearby while Carousel saves horizontal space.",
92
- },
93
- {
94
- title: "Status signals",
95
- content:
96
- "Toast and progress indicators deliver context quickly without blocking flows.",
97
- },
98
- ];
99
-
100
- const tableColumns = [
101
- { key: "name", label: "Feature", width: "42%" },
102
- { key: "owner", label: "Owner" },
103
- { key: "status", label: "Status" },
104
- { key: "eta", label: "ETA", align: "end" },
105
- ] as const;
106
-
107
- const tableRows = [
108
- { id: 1, name: "Onboarding", owner: "Anna", status: "Ready", eta: "Today" },
109
- { id: 2, name: "Theming", owner: "Mark", status: "In progress", eta: "Fri" },
110
- { id: 3, name: "Notifications", owner: "Oleg", status: "Testing", eta: "Thu" },
111
- { id: 4, name: "Data tables", owner: "Ira", status: "In progress", eta: "Next week" },
112
- { id: 5, name: "Carousel", owner: "Anton", status: "Design", eta: "In two weeks" },
113
- { id: 6, name: "Accessibility", owner: "Sasha", status: "Review", eta: "Today" },
114
- ];
115
-
116
- const pageSize = 4;
117
- let tablePage = $state(1);
118
- const totalPages = $derived(
119
- Math.max(1, Math.ceil(tableRows.length / pageSize))
120
- );
121
- const pagedRows = $derived(
122
- tableRows.slice((tablePage - 1) * pageSize, tablePage * pageSize)
123
- );
124
-
125
- $effect(() => {
126
- if (tablePage > totalPages) tablePage = totalPages;
127
- if (tablePage < 1) tablePage = 1;
128
- });
129
-
130
- const quality = [
131
- { label: "UI polish", value: 86 },
132
- { label: "Accessibility", value: 72 },
133
- { label: "Performance", value: 64 },
134
- ];
135
-
136
- const team = [
137
- { name: "Anna", role: "Product", focus: "UX flows" },
138
- { name: "Mark", role: "Frontend", focus: "Components" },
139
- { name: "Oleg", role: "QA", focus: "Automation" },
140
- ];
141
-
142
- const navItems = [
143
- { id: "overview", label: "Overview" },
144
- { id: "inputs", label: "Inputs" },
145
- { id: "layout", label: "Layout" },
146
- { id: "data", label: "Data" },
147
- ];
148
- let activeNav = $state(navItems[0].id);
149
-
150
- let searchQuery = $state("");
151
- let calendarValue = $state<string | null>(null);
152
- let fileList = $state<FileList | null>(null);
153
- let dialogOpen = $state(false);
154
- let radioGroup = $state("daily");
155
- let newsletterChecked = $state(false);
156
- let mixedChecked = $state(false);
157
- let mixedState = $state(true);
158
- let menuSelection = $state("Pick an action from the menu bar");
159
- let menuSize = $state<SizeKey>("sm");
160
- let standalonePage = $state(1);
161
- const standaloneTotal = 5;
162
- let codeEditable = $state(false);
163
- let codeSample = $state(
164
- "const tokens = ['spacing', 'radius', 'colors'];\n\nexport function tokensReady() {\n return tokens.length > 0;\n}\n"
165
- );
166
- let contextMenuStatus = $state("Right-click the panel to open the menu");
167
- let contextMenuRef = $state<{
168
- openAt: (event: MouseEvent) => void;
169
- close: () => void;
170
- } | null>(null);
171
-
172
- const menuData: MenuItem[] = [
173
- {
174
- name: "File",
175
- actions: [
176
- { id: "new", label: "New", shortcut: "Ctrl+N" },
177
- { id: "open", label: "Open", shortcut: "Ctrl+O" },
178
- { type: "separator" },
179
- { id: "export", label: "Export", shortcut: "Ctrl+E" },
180
- ],
181
- },
182
- {
183
- name: "View",
184
- actions: [
185
- { id: "xs", label: "XS" },
186
- { id: "sm", label: "SM" },
187
- { id: "md", label: "MD" },
188
- { id: "lg", label: "LG" },
189
- { id: "xl", label: "XL" },
190
- ],
191
- },
192
- ];
193
-
194
- const miniFormSchema: FieldSchema[] = [
195
- { name: "project", type: "text", label: "Project", required: true },
196
- { name: "owner", type: "email", label: "Owner email", required: true },
197
- {
198
- name: "priority",
199
- type: "select",
200
- label: "Priority",
201
- options: [
202
- { label: "Low", value: "low" },
203
- { label: "Medium", value: "medium" },
204
- { label: "High", value: "high" },
205
- ],
206
- },
207
- { name: "subscribe", type: "checkbox", label: "Send updates" },
208
- ];
209
- let formResult = $state("Not submitted yet");
210
-
211
- type ToastItem = { id: number; title?: string; message: string; variant: ToastVariant };
212
- let toasts = $state<ToastItem[]>([]);
213
- let toastId = 0;
214
-
215
- function pushToast(variant: ToastVariant) {
216
- const messageMap: Record<ToastVariant, string> = {
217
- success: "Changes saved and ready to roll out.",
218
- info: "Components share tokens, typography, and behavior.",
219
- warning: "Double-check disabled, focus, and hover states.",
220
- danger: "Tests caught a blocking issue. Investigate.",
221
- };
222
- const titleMap: Record<ToastVariant, string> = {
223
- success: "Success",
224
- info: "Heads up",
225
- warning: "Warning",
226
- danger: "Error",
227
- };
228
-
229
- const id = ++toastId;
230
- toasts = [
231
- ...toasts,
232
- { id, variant, title: titleMap[variant], message: messageMap[variant] },
233
- ];
234
- }
235
-
236
- function removeToast(id: number) {
237
- toasts = toasts.filter((t) => t.id !== id);
238
- }
239
-
240
- function handleMenuSelect(menu: string, action: MenuAction) {
241
- const label =
242
- typeof action === "string"
243
- ? action
244
- : action.label || action.id || "Action";
245
- const id = typeof action === "string" ? action : action.id;
246
- if (id && ["xs", "sm", "md", "lg", "xl"].includes(id)) {
247
- menuSize = id as SizeKey;
248
- }
249
- menuSelection = `${menu}: ${label}`;
250
- }
251
-
252
- function handleMiniSubmit(values: Record<string, unknown>) {
253
- formResult = JSON.stringify(values, null, 2);
254
- }
255
-
256
- function handleContextAction(label: string, variant: ToastVariant) {
257
- contextMenuStatus = `Last action: ${label}`;
258
- pushToast(variant);
259
- }
260
- </script>
261
-
262
- <Topbar
263
- title="svelte-comp"
264
- showHamburger={true}
265
- menuItems={navItems}
266
- onMenuSelect={(id) => (activeNav = id)}
267
- />
268
-
269
- <Container>
270
- <div class="relative mx-auto max-w-6xl space-y-8 px-6 pb-10 pt-24">
271
- <section
272
- class="relative rounded-[28px] border border-[var(--border-color-default)] bg-gradient-to-br from-[var(--color-bg-surface)] via-white/70 to-[var(--color-bg-muted)] shadow-[0_20px_60px_-25px_var(--shadow-color)] dark:from-[var(--color-bg-surface)] dark:via-slate-900/70 dark:to-slate-900/50"
273
- >
274
- <div
275
- class="absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(99,102,241,0.18),transparent_35%),radial-gradient(circle_at_80%_0%,rgba(16,185,129,0.14),transparent_25%)]"
276
- ></div>
277
-
278
- <div class="relative grid gap-8 p-8 md:p-10 lg:grid-cols-[1.1fr_0.9fr]">
279
- <div class="space-y-4">
280
- <p class="text-xs uppercase tracking-[0.25em] text-[var(--color-text-muted)]">
281
- svelte-comp
282
- </p>
283
- <h1 class="text-3xl font-bold leading-tight md:text-4xl">
284
- Component showcase
285
- </h1>
286
- <p class="text-[var(--color-text-muted)] md:w-3/4">
287
- Toggle theme, tweak the primary color, and try the main lib components in a cohesive layout.
288
- </p>
289
-
290
- <div class="flex flex-wrap gap-3">
291
- <Button variant="primary" onClick={() => pushToast("info")} sz="lg">
292
- Launch interactive
293
- </Button>
294
- <Button variant="ghost" onClick={() => pushToast("warning")} sz="lg">
295
- Remind to review
296
- </Button>
297
- </div>
298
-
299
- <div class="flex flex-wrap gap-3 text-sm text-[var(--color-text-muted)]">
300
- <span
301
- class="inline-flex items-center gap-2 rounded-full border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] px-3 py-2"
302
- >
303
- <span class="h-2 w-2 animate-pulse rounded-full bg-[var(--color-bg-primary)]"></span>
304
- Live preview is on
305
- </span>
306
- <span
307
- class="inline-flex items-center gap-2 rounded-full border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] px-3 py-2"
308
- >
309
- <span class="h-2 w-2 rounded-full bg-[var(--color-bg-secondary)]"></span>
310
- {tabs.length} sections below
311
- </span>
312
- </div>
313
- </div>
314
-
315
- <div
316
- class="space-y-4 rounded-2xl border border-[var(--border-color-default)] bg-white/70 p-5 shadow-lg dark:bg-slate-900/60"
317
- >
318
- <div class="flex items-start justify-between gap-3">
319
- <div>
320
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
321
- Theme
322
- </p>
323
- <p class="text-lg font-semibold">Mode and accent</p>
324
- </div>
325
-
326
- <Tooltip text="Toggles document-level theme" position="bottom">
327
- <ThemeToggle class="relative shadow-sm" sz="sm" />
328
- </Tooltip>
329
- </div>
330
-
331
- <PrimaryColorSelect class="w-full" />
332
-
333
- <div
334
- class="flex items-center justify-between rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-muted)]/70 px-4 py-3"
335
- >
336
- <div>
337
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
338
- Autoplay
339
- </p>
340
- <p class="text-sm font-medium">Carousel</p>
341
- </div>
342
- <Switch
343
- checked={autoplay}
344
- onChange={(v) => (autoplay = v)}
345
- sz="sm"
346
- rightLabel={autoplay ? "On" : "Off"}
347
- />
348
- </div>
349
-
350
- <div class="grid gap-3 md:grid-cols-2">
351
- <ProgressCircle value={sliderValue} label="Readiness" />
352
- <ProgressBar value={sliderValue} label="Sprint focus" />
353
- </div>
354
- </div>
355
- </div>
356
- </section>
357
-
358
- {#snippet controlsHeader()}
359
- <div class="flex items-center justify-between gap-2">
360
- <div>
361
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
362
- Actions
363
- </p>
364
- <h2 class="text-lg font-semibold leading-tight">Quick triggers</h2>
365
- </div>
366
- <Tooltip text="Buttons use the full variant set" position="left">
367
- <span
368
- class="inline-flex h-9 w-9 items-center justify-center rounded-full bg-[var(--color-bg-muted)] text-sm font-semibold text-[var(--color-text-default)]"
369
- >
370
- ?
371
- </span>
372
- </Tooltip>
373
- </div>
374
- {/snippet}
375
-
376
- {#snippet formHeader()}
377
- <div class="flex items-center justify-between">
378
- <div>
379
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
380
- Form
381
- </p>
382
- <h2 class="text-lg font-semibold leading-tight">Mini brief</h2>
383
- </div>
384
- <Tooltip text="Field, Select, DatePicker, TimePicker and ColorPicker" position="left">
385
- <span
386
- class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-[var(--color-bg-muted)] text-sm font-semibold text-[var(--color-text-default)]"
387
- >
388
- i
389
- </span>
390
- </Tooltip>
391
- </div>
392
- {/snippet}
393
-
394
- <div class="grid gap-6 lg:grid-cols-2">
395
- <Card header={controlsHeader}>
396
- <div class="space-y-4">
397
- <div class="flex flex-wrap gap-2">
398
- <Button variant="primary" onClick={() => pushToast("success")}>Primary</Button>
399
- <Button variant="secondary">Secondary</Button>
400
- <Button variant="ghost">Ghost</Button>
401
- <Button variant="pill">Pill</Button>
402
- <Button variant="danger" onClick={() => pushToast("danger")}>Danger</Button>
403
- </div>
404
-
405
- <div
406
- class="flex items-center justify-between gap-3 rounded-xl border border-[var(--border-color-default)] px-4 py-3"
407
- >
408
- <div class="space-y-1">
409
- <p class="text-sm font-medium">Live mode</p>
410
- <p class="text-xs text-[var(--color-text-muted)]">
411
- Keep autoplay and progress linked across the page
412
- </p>
413
- </div>
414
- <Switch checked={autoplay} onChange={(v) => (autoplay = v)} sz="sm" />
415
- </div>
416
-
417
- <div class="space-y-2">
418
- <div class="flex items-center justify-between text-sm">
419
- <span class="text-[var(--color-text-muted)]">Drag to refresh progress</span>
420
- <span class="font-mono text-[var(--color-text-default)]">{sliderValue}%</span>
421
- </div>
422
- <Slider
423
- value={sliderValue}
424
- min={0}
425
- max={100}
426
- onInput={(v) => (sliderValue = v)}
427
- showValue={false}
428
- />
429
- </div>
430
-
431
- <div class="grid gap-3 md:grid-cols-2">
432
- <ProgressBar value={sliderValue} label="Iteration" />
433
- <ProgressCircle value={sliderValue} label="Readiness" />
434
- </div>
435
-
436
- <div class="flex flex-wrap gap-2">
437
- <Button variant="success" onClick={() => pushToast("success")} sz="sm">
438
- Success toast
439
- </Button>
440
- <Button variant="warning" onClick={() => pushToast("warning")} sz="sm">
441
- Warning toast
442
- </Button>
443
- <Button variant="info" onClick={() => pushToast("info")} sz="sm">
444
- Info toast
445
- </Button>
446
- </div>
447
- </div>
448
- </Card>
449
-
450
- <Card header={formHeader}>
451
- <div class="space-y-4">
452
- <div class="grid gap-3 md:grid-cols-2">
453
- <Field
454
- label="Name"
455
- value={featureName}
456
- onChange={(v) => (featureName = String(v))}
457
- />
458
- <Field
459
- label="Contact email"
460
- type="email"
461
- value={contactEmail}
462
- onChange={(v) => (contactEmail = String(v))}
463
- />
464
- </div>
465
-
466
- <Select
467
- label="Plan"
468
- options={planOptions}
469
- value={selectedPlan}
470
- placeholder="Choose a plan"
471
- onChange={(v) => (selectedPlan = v)}
472
- />
473
-
474
- <div class="grid gap-3 md:grid-cols-3">
475
- <DatePicker
476
- label="Launch date"
477
- value={dateValue}
478
- onChange={(v) => (dateValue = v)}
479
- />
480
- <TimePicker
481
- label="Release time"
482
- value={timeValue}
483
- onChange={(v) => (timeValue = v)}
484
- />
485
- <ColorPicker
486
- label="Accent"
487
- value={accentColor}
488
- onChange={(v) => (accentColor = v)}
489
- />
490
- </div>
491
-
492
- <div class="flex justify-end gap-3">
493
- <Button
494
- variant="ghost"
495
- onClick={() => {
496
- featureName = "Dashboard 2.0";
497
- selectedPlan = planOptions[1].value;
498
- contactEmail = "team@studio.dev";
499
- dateValue = null;
500
- timeValue = null;
501
- accentColor = "#7c3aed";
502
- }}
503
- >
504
- Reset
505
- </Button>
506
- <Button variant="primary" onClick={() => pushToast("success")}>
507
- Save
508
- </Button>
509
- </div>
510
- </div>
511
- </Card>
512
- </div>
513
-
514
- {#snippet dataHeader()}
515
- <div class="flex items-center justify-between gap-3">
516
- <div>
517
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
518
- Data
519
- </p>
520
- <h2 class="text-lg font-semibold leading-tight">Sprint snapshot</h2>
521
- </div>
522
- <Button variant="secondary" sz="sm" onClick={() => pushToast("info")}>
523
- Refresh
524
- </Button>
525
- </div>
526
- {/snippet}
527
-
528
- {#snippet accordionHeader()}
529
- <div class="flex items-center justify-between gap-3">
530
- <div>
531
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
532
- Details
533
- </p>
534
- <h3 class="text-lg font-semibold leading-tight">Sections</h3>
535
- </div>
536
- </div>
537
- {/snippet}
538
-
539
- {#snippet carouselHeader()}
540
- <div class="flex items-center justify-between gap-3">
541
- <div>
542
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
543
- Carousel
544
- </p>
545
- <h3 class="text-lg font-semibold leading-tight">Stories</h3>
546
- </div>
547
- <span class="text-xs text-[var(--color-text-muted)]">
548
- Autoplay {autoplay ? "on" : "off"}
549
- </span>
550
- </div>
551
- {/snippet}
552
-
553
- <div class="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
554
- <Card header={dataHeader} class="h-full">
555
- <Tabs
556
- tabs={tabs}
557
- activeTab={activeTab}
558
- onChange={(id) => (activeTab = id)}
559
- variant="underline"
560
- fitted={true}
561
- >
562
- {#if activeTab === "overview"}
563
- <div class="grid gap-4 md:grid-cols-3">
564
- <div
565
- class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4"
566
- >
567
- <p class="text-xs uppercase tracking-[0.1em] text-[var(--color-text-muted)]">
568
- Readiness
569
- </p>
570
- <p class="text-2xl font-bold">{sliderValue}%</p>
571
- <p class="text-sm text-[var(--color-text-muted)]">
572
- Synced with the slider above
573
- </p>
574
- </div>
575
-
576
- <div
577
- class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4"
578
- >
579
- <p class="text-xs uppercase tracking-[0.1em] text-[var(--color-text-muted)]">
580
- Plan
581
- </p>
582
- <p class="text-lg font-semibold">{selectedPlan}</p>
583
- <p class="text-sm text-[var(--color-text-muted)]">
584
- Contact: {contactEmail}
585
- </p>
586
- </div>
587
-
588
- <div
589
- class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4"
590
- >
591
- <p class="text-xs uppercase tracking-[0.1em] text-[var(--color-text-muted)]">
592
- Release window
593
- </p>
594
- <p class="text-lg font-semibold">
595
- {dateValue ?? "Not selected yet"}
596
- </p>
597
- <p class="text-sm text-[var(--color-text-muted)]">
598
- {timeValue ?? "Time is being planned"}
599
- </p>
600
- </div>
601
- </div>
602
-
603
- <div class="mt-4 grid gap-3 md:grid-cols-3">
604
- {#each quality as metric (metric.label)}
605
- <ProgressBar value={metric.value} label={metric.label} sz="sm" />
606
- {/each}
607
- </div>
608
- {:else if activeTab === "team"}
609
- <div class="space-y-3">
610
- {#each team as person (person.name)}
611
- <div
612
- class="flex items-center justify-between rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] px-4 py-3"
613
- >
614
- <div>
615
- <p class="font-semibold">{person.name}</p>
616
- <p class="text-sm text-[var(--color-text-muted)]">{person.role}</p>
617
- </div>
618
- <span
619
- class="rounded-full bg-[var(--color-bg-muted)] px-3 py-1 text-xs text-[var(--color-text-default)]"
620
- >
621
- {person.focus}
622
- </span>
623
- </div>
624
- {/each}
625
- </div>
626
- {:else}
627
- <Table
628
- columns={tableColumns}
629
- rows={pagedRows}
630
- variant="zebra"
631
- pagination={{ currentPage: tablePage, totalPages, onPageChange: (p) => (tablePage = p) }}
632
- />
633
- {/if}
634
- </Tabs>
635
- </Card>
636
-
637
- <div class="grid gap-6">
638
- <Card header={accordionHeader}>
639
- <Accordion items={accordionItems} multiple={true} defaultOpen={[0]} />
640
- </Card>
641
-
642
- <Card header={carouselHeader}>
643
- <Carousel
644
- items={carouselItems}
645
- autoplay={autoplay}
646
- interval={4200}
647
- showDots={true}
648
- showArrows={true}
649
- sz="sm"
650
- />
651
- </Card>
652
- </div>
653
- </div>
654
-
655
- {#snippet statusHeader()}
656
- <div class="flex items-center justify-between gap-2">
657
- <div>
658
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
659
- Status
660
- </p>
661
- <h2 class="text-lg font-semibold leading-tight">Search and signals</h2>
662
- </div>
663
- </div>
664
- {/snippet}
665
-
666
- {#snippet optionsHeader()}
667
- <div class="flex items-center justify-between gap-2">
668
- <div>
669
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
670
- Options
671
- </p>
672
- <h2 class="text-lg font-semibold leading-tight">Toggles and calendar</h2>
673
- </div>
674
- </div>
675
- {/snippet}
676
-
677
- <div class="grid gap-6 lg:grid-cols-2">
678
- <Card header={statusHeader}>
679
- <div class="space-y-4">
680
- <SearchInput
681
- label="Search components"
682
- placeholder="Filter by name"
683
- bind:value={searchQuery}
684
- />
685
-
686
- <div class="flex flex-wrap gap-2">
687
- <Badge message="Live" variant="success" showIcon />
688
- <Badge message="Needs review" variant="warning" showIcon />
689
- <Badge message="Queued" variant="info" />
690
- </div>
691
-
692
- {#snippet noticeEnd()}
693
- <Button variant="ghost" sz="xs">Undo</Button>
694
- {/snippet}
695
-
696
- <NoticeBase
697
- title="Release note"
698
- message="New components landed in the demo."
699
- variant="info"
700
- size="md"
701
- end={noticeEnd}
702
- />
703
- </div>
704
- </Card>
705
-
706
- <Card header={optionsHeader}>
707
- <div class="space-y-4">
708
- <div class="space-y-2">
709
- <Radio
710
- label="Daily updates"
711
- value="daily"
712
- bind:group={radioGroup}
713
- />
714
- <Radio
715
- label="Weekly updates"
716
- value="weekly"
717
- bind:group={radioGroup}
718
- />
719
- <Radio
720
- label="Monthly updates"
721
- value="monthly"
722
- bind:group={radioGroup}
723
- />
724
- </div>
725
-
726
- <div class="flex flex-wrap gap-3">
727
- <CheckBox
728
- label="Send digest emails"
729
- bind:checked={newsletterChecked}
730
- />
731
- <CheckBox
732
- label="Mixed state"
733
- indeterminate={mixedState}
734
- checked={mixedChecked}
735
- onChange={(v) => {
736
- mixedChecked = v;
737
- mixedState = false;
738
- }}
739
- />
740
- </div>
741
-
742
- <div class="grid gap-3 md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
743
- <div class="min-w-0">
744
- <Calendar
745
- value={calendarValue}
746
- onChange={(v) => (calendarValue = v)}
747
- showOutsideDays={true}
748
- class="w-full"
749
- />
750
- </div>
751
- <div class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4">
752
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
753
- Selected date
754
- </p>
755
- <p class="text-lg font-semibold">
756
- {calendarValue ?? "No date yet"}
757
- </p>
758
- <p class="text-sm text-[var(--color-text-muted)] mt-2">
759
- Plan: {radioGroup}
760
- </p>
761
- </div>
762
- </div>
763
- </div>
764
- </Card>
765
- </div>
766
-
767
- {#snippet filesHeader()}
768
- <div class="flex items-center justify-between gap-2">
769
- <div>
770
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
771
- Files
772
- </p>
773
- <h2 class="text-lg font-semibold leading-tight">Uploads and dialogs</h2>
774
- </div>
775
- <InstallPWA inline={true} alwaysShow={true} />
776
- </div>
777
- {/snippet}
778
-
779
- {#snippet formGeneratorHeader()}
780
- <div class="flex items-center justify-between gap-2">
781
- <div>
782
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
783
- Form
784
- </p>
785
- <h2 class="text-lg font-semibold leading-tight">Schema-driven form</h2>
786
- </div>
787
- </div>
788
- {/snippet}
789
-
790
- <div class="grid gap-6 lg:grid-cols-2">
791
- <Card header={filesHeader}>
792
- <div class="space-y-4">
793
- <FilePicker
794
- value={fileList}
795
- onFilesSelected={(files) => (fileList = files)}
796
- accept="image/*,.pdf"
797
- />
798
-
799
- <div class="flex items-center justify-between rounded-xl border border-[var(--border-color-default)] px-4 py-3">
800
- <div>
801
- <p class="text-sm font-medium">Confirm release</p>
802
- <p class="text-xs text-[var(--color-text-muted)]">
803
- Last file: {fileList?.[0]?.name ?? "none"}
804
- </p>
805
- </div>
806
- <Button variant="primary" onClick={() => (dialogOpen = true)} sz="sm">
807
- Open dialog
808
- </Button>
809
- </div>
810
- </div>
811
- </Card>
812
-
813
- <Card header={formGeneratorHeader}>
814
- <div class="space-y-4">
815
- <Form
816
- schema={miniFormSchema}
817
- onSubmit={handleMiniSubmit}
818
- validateOn="submit"
819
- />
820
- <div class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-3">
821
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
822
- Last submit
823
- </p>
824
- <pre class="text-xs whitespace-pre-wrap text-[var(--color-text-default)]">
825
- {formResult}
826
- </pre>
827
- </div>
828
- </div>
829
- </Card>
830
- </div>
831
-
832
- {#snippet menuHeader()}
833
- <div class="flex items-center justify-between gap-2">
834
- <div>
835
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
836
- Menu
837
- </p>
838
- <h2 class="text-lg font-semibold leading-tight">Navigation controls</h2>
839
- </div>
840
- </div>
841
- {/snippet}
842
-
843
- {#snippet layoutHeader()}
844
- <div class="flex items-center justify-between gap-2">
845
- <div>
846
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
847
- Layout
848
- </p>
849
- <h2 class="text-lg font-semibold leading-tight">Splitter + context menu</h2>
850
- </div>
851
- </div>
852
- {/snippet}
853
-
854
- {#snippet firstPane()}
855
- <div class="h-full grid place-items-center text-sm font-medium">
856
- First panel
857
- </div>
858
- {/snippet}
859
-
860
- {#snippet secondPane()}
861
- <div class="h-full grid place-items-center text-sm font-medium">
862
- Second panel
863
- </div>
864
- {/snippet}
865
-
866
- <div class="grid gap-6 lg:grid-cols-2">
867
- <Card header={menuHeader}>
868
- <div class="space-y-4">
869
- <Menu menus={menuData} sz={menuSize} onSelect={handleMenuSelect} />
870
-
871
- <div class="text-sm text-[var(--color-text-muted)]">
872
- {menuSelection}
873
- </div>
874
-
875
- <div class="rounded-xl border border-[var(--border-color-default)] p-4">
876
- <Pagination
877
- currentPage={standalonePage}
878
- totalPages={standaloneTotal}
879
- onPageChange={(p) => (standalonePage = p)}
880
- />
881
- </div>
882
-
883
- {#snippet pageOne()}
884
- <div class="p-4 rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]">
885
- <p class="text-sm font-medium">Release notes</p>
886
- <p class="text-xs text-[var(--color-text-muted)]">
887
- Snapshot of changes across components.
888
- </p>
889
- </div>
890
- {/snippet}
891
- {#snippet pageTwo()}
892
- <div class="p-4 rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]">
893
- <p class="text-sm font-medium">Design tasks</p>
894
- <p class="text-xs text-[var(--color-text-muted)]">
895
- Review tokens and spacing.
896
- </p>
897
- </div>
898
- {/snippet}
899
- {#snippet pageThree()}
900
- <div class="p-4 rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]">
901
- <p class="text-sm font-medium">QA checklist</p>
902
- <p class="text-xs text-[var(--color-text-muted)]">
903
- Validate keyboard and focus behavior.
904
- </p>
905
- </div>
906
- {/snippet}
907
-
908
- <PaginatedCard
909
- items={[pageOne, pageTwo, pageThree]}
910
- itemsPerPage={1}
911
- />
912
- </div>
913
- </Card>
914
-
915
- <Card header={layoutHeader}>
916
- <div class="space-y-4">
917
- <div class="h-56 rounded-xl border border-[var(--border-color-default)] overflow-hidden">
918
- <Splitter
919
- direction="horizontal"
920
- initialSize={45}
921
- dividerSize={6}
922
- minSize={20}
923
- maxSize={80}
924
- first={firstPane}
925
- second={secondPane}
926
- />
927
- </div>
928
-
929
- <div
930
- class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4 text-sm text-[var(--color-text-muted)]"
931
- oncontextmenu={(e) => contextMenuRef?.openAt(e)}
932
- role="button"
933
- tabindex="0"
934
- >
935
- {contextMenuStatus}
936
- </div>
937
-
938
- <ContextMenu
939
- bind:this={contextMenuRef}
940
- onUndo={() => handleContextAction("Undo", "info")}
941
- onRedo={() => handleContextAction("Redo", "info")}
942
- onCopy={() => handleContextAction("Copy", "success")}
943
- onCut={() => handleContextAction("Cut", "warning")}
944
- onPaste={() => handleContextAction("Paste", "success")}
945
- onDelete={() => handleContextAction("Delete", "danger")}
946
- />
947
- </div>
948
- </Card>
949
- </div>
950
-
951
- {#snippet codeHeader()}
952
- <div class="flex items-center justify-between gap-2">
953
- <div>
954
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
955
- Code
956
- </p>
957
- <h2 class="text-lg font-semibold leading-tight">CodeView editor</h2>
958
- </div>
959
- <Switch
960
- checked={codeEditable}
961
- onChange={(v) => (codeEditable = v)}
962
- rightLabel={codeEditable ? "Editable" : "Read only"}
963
- sz="sm"
964
- />
965
- </div>
966
- {/snippet}
967
-
968
- {#snippet quickHeader()}
969
- <div class="flex items-center justify-between gap-2">
970
- <div>
971
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
972
- Actions
973
- </p>
974
- <h2 class="text-lg font-semibold leading-tight">Quick actions</h2>
975
- </div>
976
- </div>
977
- {/snippet}
978
-
979
- <div class="grid gap-6 lg:grid-cols-2">
980
- <Card header={codeHeader} class="min-h-[360px]">
981
- <CodeView
982
- bind:code={codeSample}
983
- language="js"
984
- title="tokens.js"
985
- showCopyButton={true}
986
- showLineNumbers={true}
987
- editable={codeEditable}
988
- activeLine={true}
989
- class="h-[320px]"
990
- />
991
- </Card>
992
-
993
- <Card header={quickHeader}>
994
- <div class="space-y-4">
995
- <div class="flex flex-wrap gap-2">
996
- <Button variant="secondary" onClick={() => pushToast("info")}>
997
- Notify
998
- </Button>
999
- <Button variant="success" onClick={() => pushToast("success")}>
1000
- Success
1001
- </Button>
1002
- <Button variant="danger" onClick={() => pushToast("danger")}>
1003
- Error
1004
- </Button>
1005
- </div>
1006
-
1007
- <div class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4">
1008
- <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
1009
- Active nav
1010
- </p>
1011
- <p class="text-lg font-semibold">{activeNav}</p>
1012
- <p class="text-sm text-[var(--color-text-muted)]">
1013
- Search: {searchQuery || "empty"}
1014
- </p>
1015
- </div>
1016
- </div>
1017
- </Card>
1018
- </div>
1019
-
1020
- <Dialog
1021
- open={dialogOpen}
1022
- title="Confirm release"
1023
- message="Ready to ship the selected files?"
1024
- onConfirm={() => {
1025
- dialogOpen = false;
1026
- pushToast("success");
1027
- }}
1028
- onCancel={() => {
1029
- dialogOpen = false;
1030
- }}
1031
- onClose={() => {
1032
- dialogOpen = false;
1033
- }}
1034
- />
1035
- </div>
1036
-
1037
- {#each toasts as toast (toast.id)}
1038
- <Toast
1039
- title={toast.title}
1040
- message={toast.message}
1041
- variant={toast.variant}
1042
- onClose={() => removeToast(toast.id)}
1043
- timeout={3500}
1044
- />
1045
- {/each}
1046
- </Container>
1
+ <script lang="ts">
2
+ import Accordion from "./lib/Accordion.svelte";
3
+ import Badge from "./lib/Badge.svelte";
4
+ import Button from "./lib/Button.svelte";
5
+ import Calendar from "./lib/Calendar.svelte";
6
+ import Card from "./lib/Card.svelte";
7
+ import Carousel from "./lib/Carousel.svelte";
8
+ import CheckBox from "./lib/CheckBox.svelte";
9
+ import CodeView from "./lib/CodeView.svelte";
10
+ import ColorPicker from "./lib/ColorPicker.svelte";
11
+ import ContextMenu from "./lib/ContextMenu.svelte";
12
+ import DatePicker from "./lib/DatePicker.svelte";
13
+ import Dialog from "./lib/Dialog.svelte";
14
+ import Field from "./lib/Field.svelte";
15
+ import FilePicker from "./lib/FilePicker.svelte";
16
+ import Form from "./lib/Form.svelte";
17
+ import InstallPWA from "./lib/InstallPWA.svelte";
18
+ import Menu from "./lib/Menu.svelte";
19
+ import NoticeBase from "./lib/NoticeBase.svelte";
20
+ import PaginatedCard from "./lib/PaginatedCard.svelte";
21
+ import Pagination from "./lib/Pagination.svelte";
22
+ import PrimaryColorSelect from "./lib/PrimaryColorSelect.svelte";
23
+ import ProgressBar from "./lib/ProgressBar.svelte";
24
+ import ProgressCircle from "./lib/ProgressCircle.svelte";
25
+ import Radio from "./lib/Radio.svelte";
26
+ import SearchInput from "./lib/SearchInput.svelte";
27
+ import Select from "./lib/Select.svelte";
28
+ import Slider from "./lib/Slider.svelte";
29
+ import Splitter from "./lib/Splitter.svelte";
30
+ import Switch from "./lib/Switch.svelte";
31
+ import Table from "./lib/Table.svelte";
32
+ import Tabs from "./lib/Tabs.svelte";
33
+ import ThemeToggle from "./lib/ThemeToggle.svelte";
34
+ import TimePicker from "./lib/TimePicker.svelte";
35
+ import Toast from "./lib/Toast.svelte";
36
+ import Tooltip from "./lib/Tooltip.svelte";
37
+ import Topbar from "./lib/Topbar.svelte";
38
+ import type { FieldSchema, MenuAction, MenuItem, SizeKey, ToastVariant } from "./lib/types";
39
+ import Container from "./Container.svelte";
40
+
41
+ const tabs = [
42
+ { id: "overview", label: "Overview" },
43
+ { id: "team", label: "Team" },
44
+ { id: "table", label: "Data" },
45
+ ];
46
+ let activeTab = $state(tabs[0].id);
47
+
48
+ let sliderValue = $state(68);
49
+ let autoplay = $state(true);
50
+
51
+ const planOptions = [
52
+ { label: "Starter", value: "starter" },
53
+ { label: "Pro", value: "pro" },
54
+ { label: "Enterprise", value: "enterprise" },
55
+ ];
56
+
57
+ let selectedPlan = $state(planOptions[1].value);
58
+ let featureName = $state("Dashboard 2.0");
59
+ let contactEmail = $state("team@studio.dev");
60
+ let dateValue = $state<string | null>(null);
61
+ let timeValue = $state<string | null>(null);
62
+ let accentColor = $state<string | null>("#7c3aed");
63
+
64
+ const accordionItems = [
65
+ {
66
+ title: "Composition",
67
+ content:
68
+ "Card, Tabs, Table and Carousel let you assemble complex screens without extra layout work.",
69
+ },
70
+ {
71
+ title: "Forms",
72
+ content:
73
+ "Field, Select, DatePicker, TimePicker and ColorPicker share spacing, tokens and state handling.",
74
+ },
75
+ {
76
+ title: "Feedback",
77
+ content:
78
+ "ProgressBar, ProgressCircle, Toast and Dialog keep users informed without noise.",
79
+ },
80
+ ];
81
+
82
+ const carouselItems = [
83
+ {
84
+ title: "Smooth forms",
85
+ content:
86
+ "Clean fields, tight spacing and built-in hints help ship surveys in minutes.",
87
+ },
88
+ {
89
+ title: "Smart navigation",
90
+ content:
91
+ "Tabs and Accordion keep content nearby while Carousel saves horizontal space.",
92
+ },
93
+ {
94
+ title: "Status signals",
95
+ content:
96
+ "Toast and progress indicators deliver context quickly without blocking flows.",
97
+ },
98
+ ];
99
+
100
+ const tableColumns = [
101
+ { key: "name", label: "Feature", width: "42%" },
102
+ { key: "owner", label: "Owner" },
103
+ { key: "status", label: "Status" },
104
+ { key: "eta", label: "ETA", align: "end" },
105
+ ] as const;
106
+
107
+ const tableRows = [
108
+ { id: 1, name: "Onboarding", owner: "Anna", status: "Ready", eta: "Today" },
109
+ { id: 2, name: "Theming", owner: "Mark", status: "In progress", eta: "Fri" },
110
+ { id: 3, name: "Notifications", owner: "Oleg", status: "Testing", eta: "Thu" },
111
+ { id: 4, name: "Data tables", owner: "Ira", status: "In progress", eta: "Next week" },
112
+ { id: 5, name: "Carousel", owner: "Anton", status: "Design", eta: "In two weeks" },
113
+ { id: 6, name: "Accessibility", owner: "Sasha", status: "Review", eta: "Today" },
114
+ ];
115
+
116
+ const pageSize = 4;
117
+ let tablePage = $state(1);
118
+ const totalPages = $derived(
119
+ Math.max(1, Math.ceil(tableRows.length / pageSize))
120
+ );
121
+ const pagedRows = $derived(
122
+ tableRows.slice((tablePage - 1) * pageSize, tablePage * pageSize)
123
+ );
124
+
125
+ $effect(() => {
126
+ if (tablePage > totalPages) tablePage = totalPages;
127
+ if (tablePage < 1) tablePage = 1;
128
+ });
129
+
130
+ const quality = [
131
+ { label: "UI polish", value: 86 },
132
+ { label: "Accessibility", value: 72 },
133
+ { label: "Performance", value: 64 },
134
+ ];
135
+
136
+ const team = [
137
+ { name: "Anna", role: "Product", focus: "UX flows" },
138
+ { name: "Mark", role: "Frontend", focus: "Components" },
139
+ { name: "Oleg", role: "QA", focus: "Automation" },
140
+ ];
141
+
142
+ const navItems = [
143
+ { id: "overview", label: "Overview" },
144
+ { id: "inputs", label: "Inputs" },
145
+ { id: "layout", label: "Layout" },
146
+ { id: "data", label: "Data" },
147
+ ];
148
+ let activeNav = $state(navItems[0].id);
149
+
150
+ let searchQuery = $state("");
151
+ let calendarValue = $state<string | null>(null);
152
+ let fileList = $state<FileList | null>(null);
153
+ let dialogOpen = $state(false);
154
+ let radioGroup = $state("daily");
155
+ let newsletterChecked = $state(false);
156
+ let mixedChecked = $state(false);
157
+ let mixedState = $state(true);
158
+ let menuSelection = $state("Pick an action from the menu bar");
159
+ let menuSize = $state<SizeKey>("sm");
160
+ let standalonePage = $state(1);
161
+ const standaloneTotal = 5;
162
+ let codeEditable = $state(false);
163
+ let codeSample = $state(
164
+ "const tokens = ['spacing', 'radius', 'colors'];\n\nexport function tokensReady() {\n return tokens.length > 0;\n}\n"
165
+ );
166
+ let contextMenuStatus = $state("Right-click the panel to open the menu");
167
+ let contextMenuRef = $state<{
168
+ openAt: (event: MouseEvent) => void;
169
+ close: () => void;
170
+ } | null>(null);
171
+
172
+ const menuData: MenuItem[] = [
173
+ {
174
+ name: "File",
175
+ actions: [
176
+ { id: "new", label: "New", shortcut: "Ctrl+N" },
177
+ { id: "open", label: "Open", shortcut: "Ctrl+O" },
178
+ { type: "separator" },
179
+ { id: "export", label: "Export", shortcut: "Ctrl+E" },
180
+ ],
181
+ },
182
+ {
183
+ name: "View",
184
+ actions: [
185
+ { id: "xs", label: "XS" },
186
+ { id: "sm", label: "SM" },
187
+ { id: "md", label: "MD" },
188
+ { id: "lg", label: "LG" },
189
+ { id: "xl", label: "XL" },
190
+ ],
191
+ },
192
+ ];
193
+
194
+ const miniFormSchema: FieldSchema[] = [
195
+ { name: "project", type: "text", label: "Project", required: true },
196
+ { name: "owner", type: "email", label: "Owner email", required: true },
197
+ {
198
+ name: "priority",
199
+ type: "select",
200
+ label: "Priority",
201
+ options: [
202
+ { label: "Low", value: "low" },
203
+ { label: "Medium", value: "medium" },
204
+ { label: "High", value: "high" },
205
+ ],
206
+ },
207
+ { name: "subscribe", type: "checkbox", label: "Send updates" },
208
+ ];
209
+ let formResult = $state("Not submitted yet");
210
+
211
+ type ToastItem = { id: number; title?: string; message: string; variant: ToastVariant };
212
+ let toasts = $state<ToastItem[]>([]);
213
+ let toastId = 0;
214
+
215
+ function pushToast(variant: ToastVariant) {
216
+ const messageMap: Record<ToastVariant, string> = {
217
+ success: "Changes saved and ready to roll out.",
218
+ info: "Components share tokens, typography, and behavior.",
219
+ warning: "Double-check disabled, focus, and hover states.",
220
+ danger: "Tests caught a blocking issue. Investigate.",
221
+ };
222
+ const titleMap: Record<ToastVariant, string> = {
223
+ success: "Success",
224
+ info: "Heads up",
225
+ warning: "Warning",
226
+ danger: "Error",
227
+ };
228
+
229
+ const id = ++toastId;
230
+ toasts = [
231
+ ...toasts,
232
+ { id, variant, title: titleMap[variant], message: messageMap[variant] },
233
+ ];
234
+ }
235
+
236
+ function removeToast(id: number) {
237
+ toasts = toasts.filter((t) => t.id !== id);
238
+ }
239
+
240
+ function handleMenuSelect(menu: string, action: MenuAction) {
241
+ const label =
242
+ typeof action === "string"
243
+ ? action
244
+ : action.label || action.id || "Action";
245
+ const id = typeof action === "string" ? action : action.id;
246
+ if (id && ["xs", "sm", "md", "lg", "xl"].includes(id)) {
247
+ menuSize = id as SizeKey;
248
+ }
249
+ menuSelection = `${menu}: ${label}`;
250
+ }
251
+
252
+ function handleMiniSubmit(values: Record<string, unknown>) {
253
+ formResult = JSON.stringify(values, null, 2);
254
+ }
255
+
256
+ function handleContextAction(label: string, variant: ToastVariant) {
257
+ contextMenuStatus = `Last action: ${label}`;
258
+ pushToast(variant);
259
+ }
260
+ </script>
261
+
262
+ <Topbar
263
+ title="svelte-comp"
264
+ showHamburger={true}
265
+ menuItems={navItems}
266
+ onMenuSelect={(id) => (activeNav = id)}
267
+ />
268
+
269
+ <Container>
270
+ <div class="relative mx-auto max-w-6xl space-y-8 px-6 pb-10 pt-24">
271
+ <section
272
+ class="relative rounded-[28px] border border-[var(--border-color-default)] bg-gradient-to-br from-[var(--color-bg-surface)] via-white/70 to-[var(--color-bg-muted)] shadow-[0_20px_60px_-25px_var(--shadow-color)] dark:from-[var(--color-bg-surface)] dark:via-slate-900/70 dark:to-slate-900/50"
273
+ >
274
+ <div
275
+ class="absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(99,102,241,0.18),transparent_35%),radial-gradient(circle_at_80%_0%,rgba(16,185,129,0.14),transparent_25%)]"
276
+ ></div>
277
+
278
+ <div class="relative grid gap-8 p-8 md:p-10 lg:grid-cols-[1.1fr_0.9fr]">
279
+ <div class="space-y-4">
280
+ <p class="text-xs uppercase tracking-[0.25em] text-[var(--color-text-muted)]">
281
+ svelte-comp
282
+ </p>
283
+ <h1 class="text-3xl font-bold leading-tight md:text-4xl">
284
+ Component showcase
285
+ </h1>
286
+ <p class="text-[var(--color-text-muted)] md:w-3/4">
287
+ Toggle theme, tweak the primary color, and try the main lib components in a cohesive layout.
288
+ </p>
289
+
290
+ <div class="flex flex-wrap gap-3">
291
+ <Button variant="primary" onClick={() => pushToast("info")} sz="lg">
292
+ Launch interactive
293
+ </Button>
294
+ <Button variant="ghost" onClick={() => pushToast("warning")} sz="lg">
295
+ Remind to review
296
+ </Button>
297
+ </div>
298
+
299
+ <div class="flex flex-wrap gap-3 text-sm text-[var(--color-text-muted)]">
300
+ <span
301
+ class="inline-flex items-center gap-2 rounded-full border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] px-3 py-2"
302
+ >
303
+ <span class="h-2 w-2 animate-pulse rounded-full bg-[var(--color-bg-primary)]"></span>
304
+ Live preview is on
305
+ </span>
306
+ <span
307
+ class="inline-flex items-center gap-2 rounded-full border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] px-3 py-2"
308
+ >
309
+ <span class="h-2 w-2 rounded-full bg-[var(--color-bg-secondary)]"></span>
310
+ {tabs.length} sections below
311
+ </span>
312
+ </div>
313
+ </div>
314
+
315
+ <div
316
+ class="space-y-4 rounded-2xl border border-[var(--border-color-default)] bg-white/70 p-5 shadow-lg dark:bg-slate-900/60"
317
+ >
318
+ <div class="flex items-start justify-between gap-3">
319
+ <div>
320
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
321
+ Theme
322
+ </p>
323
+ <p class="text-lg font-semibold">Mode and accent</p>
324
+ </div>
325
+
326
+ <Tooltip text="Toggles document-level theme" position="bottom">
327
+ <ThemeToggle class="relative shadow-sm" sz="sm" />
328
+ </Tooltip>
329
+ </div>
330
+
331
+ <PrimaryColorSelect class="w-full" />
332
+
333
+ <div
334
+ class="flex items-center justify-between rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-muted)]/70 px-4 py-3"
335
+ >
336
+ <div>
337
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
338
+ Autoplay
339
+ </p>
340
+ <p class="text-sm font-medium">Carousel</p>
341
+ </div>
342
+ <Switch
343
+ checked={autoplay}
344
+ onChange={(v) => (autoplay = v)}
345
+ sz="sm"
346
+ rightLabel={autoplay ? "On" : "Off"}
347
+ />
348
+ </div>
349
+
350
+ <div class="grid gap-3 md:grid-cols-2">
351
+ <ProgressCircle value={sliderValue} label="Readiness" />
352
+ <ProgressBar value={sliderValue} label="Sprint focus" />
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </section>
357
+
358
+ {#snippet controlsHeader()}
359
+ <div class="flex items-center justify-between gap-2">
360
+ <div>
361
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
362
+ Actions
363
+ </p>
364
+ <h2 class="text-lg font-semibold leading-tight">Quick triggers</h2>
365
+ </div>
366
+ <Tooltip text="Buttons use the full variant set" position="left">
367
+ <span
368
+ class="inline-flex h-9 w-9 items-center justify-center rounded-full bg-[var(--color-bg-muted)] text-sm font-semibold text-[var(--color-text-default)]"
369
+ >
370
+ ?
371
+ </span>
372
+ </Tooltip>
373
+ </div>
374
+ {/snippet}
375
+
376
+ {#snippet formHeader()}
377
+ <div class="flex items-center justify-between">
378
+ <div>
379
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
380
+ Form
381
+ </p>
382
+ <h2 class="text-lg font-semibold leading-tight">Mini brief</h2>
383
+ </div>
384
+ <Tooltip text="Field, Select, DatePicker, TimePicker and ColorPicker" position="left">
385
+ <span
386
+ class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-[var(--color-bg-muted)] text-sm font-semibold text-[var(--color-text-default)]"
387
+ >
388
+ i
389
+ </span>
390
+ </Tooltip>
391
+ </div>
392
+ {/snippet}
393
+
394
+ <div class="grid gap-6 lg:grid-cols-2">
395
+ <Card header={controlsHeader}>
396
+ <div class="space-y-4">
397
+ <div class="flex flex-wrap gap-2">
398
+ <Button variant="primary" onClick={() => pushToast("success")}>Primary</Button>
399
+ <Button variant="secondary">Secondary</Button>
400
+ <Button variant="ghost">Ghost</Button>
401
+ <Button variant="pill">Pill</Button>
402
+ <Button variant="danger" onClick={() => pushToast("danger")}>Danger</Button>
403
+ </div>
404
+
405
+ <div
406
+ class="flex items-center justify-between gap-3 rounded-xl border border-[var(--border-color-default)] px-4 py-3"
407
+ >
408
+ <div class="space-y-1">
409
+ <p class="text-sm font-medium">Live mode</p>
410
+ <p class="text-xs text-[var(--color-text-muted)]">
411
+ Keep autoplay and progress linked across the page
412
+ </p>
413
+ </div>
414
+ <Switch checked={autoplay} onChange={(v) => (autoplay = v)} sz="sm" />
415
+ </div>
416
+
417
+ <div class="space-y-2">
418
+ <div class="flex items-center justify-between text-sm">
419
+ <span class="text-[var(--color-text-muted)]">Drag to refresh progress</span>
420
+ <span class="font-mono text-[var(--color-text-default)]">{sliderValue}%</span>
421
+ </div>
422
+ <Slider
423
+ value={sliderValue}
424
+ min={0}
425
+ max={100}
426
+ onInput={(v) => (sliderValue = v)}
427
+ showValue={false}
428
+ />
429
+ </div>
430
+
431
+ <div class="grid gap-3 md:grid-cols-2">
432
+ <ProgressBar value={sliderValue} label="Iteration" />
433
+ <ProgressCircle value={sliderValue} label="Readiness" />
434
+ </div>
435
+
436
+ <div class="flex flex-wrap gap-2">
437
+ <Button variant="success" onClick={() => pushToast("success")} sz="sm">
438
+ Success toast
439
+ </Button>
440
+ <Button variant="warning" onClick={() => pushToast("warning")} sz="sm">
441
+ Warning toast
442
+ </Button>
443
+ <Button variant="info" onClick={() => pushToast("info")} sz="sm">
444
+ Info toast
445
+ </Button>
446
+ </div>
447
+ </div>
448
+ </Card>
449
+
450
+ <Card header={formHeader}>
451
+ <div class="space-y-4">
452
+ <div class="grid gap-3 md:grid-cols-2">
453
+ <Field
454
+ label="Name"
455
+ value={featureName}
456
+ onChange={(v) => (featureName = String(v))}
457
+ />
458
+ <Field
459
+ label="Contact email"
460
+ type="email"
461
+ value={contactEmail}
462
+ onChange={(v) => (contactEmail = String(v))}
463
+ />
464
+ </div>
465
+
466
+ <Select
467
+ label="Plan"
468
+ options={planOptions}
469
+ value={selectedPlan}
470
+ placeholder="Choose a plan"
471
+ onChange={(v) => (selectedPlan = v)}
472
+ />
473
+
474
+ <div class="grid gap-3 md:grid-cols-3">
475
+ <DatePicker
476
+ label="Launch date"
477
+ value={dateValue}
478
+ onChange={(v) => (dateValue = v)}
479
+ />
480
+ <TimePicker
481
+ label="Release time"
482
+ value={timeValue}
483
+ onChange={(v) => (timeValue = v)}
484
+ />
485
+ <ColorPicker
486
+ label="Accent"
487
+ value={accentColor}
488
+ onChange={(v) => (accentColor = v)}
489
+ />
490
+ </div>
491
+
492
+ <div class="flex justify-end gap-3">
493
+ <Button
494
+ variant="ghost"
495
+ onClick={() => {
496
+ featureName = "Dashboard 2.0";
497
+ selectedPlan = planOptions[1].value;
498
+ contactEmail = "team@studio.dev";
499
+ dateValue = null;
500
+ timeValue = null;
501
+ accentColor = "#7c3aed";
502
+ }}
503
+ >
504
+ Reset
505
+ </Button>
506
+ <Button variant="primary" onClick={() => pushToast("success")}>
507
+ Save
508
+ </Button>
509
+ </div>
510
+ </div>
511
+ </Card>
512
+ </div>
513
+
514
+ {#snippet dataHeader()}
515
+ <div class="flex items-center justify-between gap-3">
516
+ <div>
517
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
518
+ Data
519
+ </p>
520
+ <h2 class="text-lg font-semibold leading-tight">Sprint snapshot</h2>
521
+ </div>
522
+ <Button variant="secondary" sz="sm" onClick={() => pushToast("info")}>
523
+ Refresh
524
+ </Button>
525
+ </div>
526
+ {/snippet}
527
+
528
+ {#snippet accordionHeader()}
529
+ <div class="flex items-center justify-between gap-3">
530
+ <div>
531
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
532
+ Details
533
+ </p>
534
+ <h3 class="text-lg font-semibold leading-tight">Sections</h3>
535
+ </div>
536
+ </div>
537
+ {/snippet}
538
+
539
+ {#snippet carouselHeader()}
540
+ <div class="flex items-center justify-between gap-3">
541
+ <div>
542
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
543
+ Carousel
544
+ </p>
545
+ <h3 class="text-lg font-semibold leading-tight">Stories</h3>
546
+ </div>
547
+ <span class="text-xs text-[var(--color-text-muted)]">
548
+ Autoplay {autoplay ? "on" : "off"}
549
+ </span>
550
+ </div>
551
+ {/snippet}
552
+
553
+ <div class="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
554
+ <Card header={dataHeader} class="h-full">
555
+ <Tabs
556
+ tabs={tabs}
557
+ activeTab={activeTab}
558
+ onChange={(id) => (activeTab = id)}
559
+ variant="underline"
560
+ fitted={true}
561
+ >
562
+ {#if activeTab === "overview"}
563
+ <div class="grid gap-4 md:grid-cols-3">
564
+ <div
565
+ class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4"
566
+ >
567
+ <p class="text-xs uppercase tracking-[0.1em] text-[var(--color-text-muted)]">
568
+ Readiness
569
+ </p>
570
+ <p class="text-2xl font-bold">{sliderValue}%</p>
571
+ <p class="text-sm text-[var(--color-text-muted)]">
572
+ Synced with the slider above
573
+ </p>
574
+ </div>
575
+
576
+ <div
577
+ class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4"
578
+ >
579
+ <p class="text-xs uppercase tracking-[0.1em] text-[var(--color-text-muted)]">
580
+ Plan
581
+ </p>
582
+ <p class="text-lg font-semibold">{selectedPlan}</p>
583
+ <p class="text-sm text-[var(--color-text-muted)]">
584
+ Contact: {contactEmail}
585
+ </p>
586
+ </div>
587
+
588
+ <div
589
+ class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4"
590
+ >
591
+ <p class="text-xs uppercase tracking-[0.1em] text-[var(--color-text-muted)]">
592
+ Release window
593
+ </p>
594
+ <p class="text-lg font-semibold">
595
+ {dateValue ?? "Not selected yet"}
596
+ </p>
597
+ <p class="text-sm text-[var(--color-text-muted)]">
598
+ {timeValue ?? "Time is being planned"}
599
+ </p>
600
+ </div>
601
+ </div>
602
+
603
+ <div class="mt-4 grid gap-3 md:grid-cols-3">
604
+ {#each quality as metric (metric.label)}
605
+ <ProgressBar value={metric.value} label={metric.label} sz="sm" />
606
+ {/each}
607
+ </div>
608
+ {:else if activeTab === "team"}
609
+ <div class="space-y-3">
610
+ {#each team as person (person.name)}
611
+ <div
612
+ class="flex items-center justify-between rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] px-4 py-3"
613
+ >
614
+ <div>
615
+ <p class="font-semibold">{person.name}</p>
616
+ <p class="text-sm text-[var(--color-text-muted)]">{person.role}</p>
617
+ </div>
618
+ <span
619
+ class="rounded-full bg-[var(--color-bg-muted)] px-3 py-1 text-xs text-[var(--color-text-default)]"
620
+ >
621
+ {person.focus}
622
+ </span>
623
+ </div>
624
+ {/each}
625
+ </div>
626
+ {:else}
627
+ <Table
628
+ columns={tableColumns}
629
+ rows={pagedRows}
630
+ variant="zebra"
631
+ pagination={{ currentPage: tablePage, totalPages, onPageChange: (p) => (tablePage = p) }}
632
+ />
633
+ {/if}
634
+ </Tabs>
635
+ </Card>
636
+
637
+ <div class="grid gap-6">
638
+ <Card header={accordionHeader}>
639
+ <Accordion items={accordionItems} multiple={true} defaultOpen={[0]} />
640
+ </Card>
641
+
642
+ <Card header={carouselHeader}>
643
+ <Carousel
644
+ items={carouselItems}
645
+ autoplay={autoplay}
646
+ interval={4200}
647
+ showDots={true}
648
+ showArrows={true}
649
+ sz="sm"
650
+ />
651
+ </Card>
652
+ </div>
653
+ </div>
654
+
655
+ {#snippet statusHeader()}
656
+ <div class="flex items-center justify-between gap-2">
657
+ <div>
658
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
659
+ Status
660
+ </p>
661
+ <h2 class="text-lg font-semibold leading-tight">Search and signals</h2>
662
+ </div>
663
+ </div>
664
+ {/snippet}
665
+
666
+ {#snippet optionsHeader()}
667
+ <div class="flex items-center justify-between gap-2">
668
+ <div>
669
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
670
+ Options
671
+ </p>
672
+ <h2 class="text-lg font-semibold leading-tight">Toggles and calendar</h2>
673
+ </div>
674
+ </div>
675
+ {/snippet}
676
+
677
+ <div class="grid gap-6 lg:grid-cols-2">
678
+ <Card header={statusHeader}>
679
+ <div class="space-y-4">
680
+ <SearchInput
681
+ label="Search components"
682
+ placeholder="Filter by name"
683
+ bind:value={searchQuery}
684
+ />
685
+
686
+ <div class="flex flex-wrap gap-2">
687
+ <Badge message="Live" variant="success" showIcon />
688
+ <Badge message="Needs review" variant="warning" showIcon />
689
+ <Badge message="Queued" variant="info" />
690
+ </div>
691
+
692
+ {#snippet noticeEnd()}
693
+ <Button variant="ghost" sz="xs">Undo</Button>
694
+ {/snippet}
695
+
696
+ <NoticeBase
697
+ title="Release note"
698
+ message="New components landed in the demo."
699
+ variant="info"
700
+ size="md"
701
+ end={noticeEnd}
702
+ />
703
+ </div>
704
+ </Card>
705
+
706
+ <Card header={optionsHeader}>
707
+ <div class="space-y-4">
708
+ <div class="space-y-2">
709
+ <Radio
710
+ label="Daily updates"
711
+ value="daily"
712
+ bind:group={radioGroup}
713
+ />
714
+ <Radio
715
+ label="Weekly updates"
716
+ value="weekly"
717
+ bind:group={radioGroup}
718
+ />
719
+ <Radio
720
+ label="Monthly updates"
721
+ value="monthly"
722
+ bind:group={radioGroup}
723
+ />
724
+ </div>
725
+
726
+ <div class="flex flex-wrap gap-3">
727
+ <CheckBox
728
+ label="Send digest emails"
729
+ bind:checked={newsletterChecked}
730
+ />
731
+ <CheckBox
732
+ label="Mixed state"
733
+ indeterminate={mixedState}
734
+ checked={mixedChecked}
735
+ onChange={(v) => {
736
+ mixedChecked = v;
737
+ mixedState = false;
738
+ }}
739
+ />
740
+ </div>
741
+
742
+ <div class="grid gap-3 md:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
743
+ <div class="min-w-0">
744
+ <Calendar
745
+ value={calendarValue}
746
+ onChange={(v) => (calendarValue = v)}
747
+ showOutsideDays={true}
748
+ class="w-full"
749
+ />
750
+ </div>
751
+ <div class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4">
752
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
753
+ Selected date
754
+ </p>
755
+ <p class="text-lg font-semibold">
756
+ {calendarValue ?? "No date yet"}
757
+ </p>
758
+ <p class="text-sm text-[var(--color-text-muted)] mt-2">
759
+ Plan: {radioGroup}
760
+ </p>
761
+ </div>
762
+ </div>
763
+ </div>
764
+ </Card>
765
+ </div>
766
+
767
+ {#snippet filesHeader()}
768
+ <div class="flex items-center justify-between gap-2">
769
+ <div>
770
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
771
+ Files
772
+ </p>
773
+ <h2 class="text-lg font-semibold leading-tight">Uploads and dialogs</h2>
774
+ </div>
775
+ <InstallPWA inline={true} alwaysShow={true} />
776
+ </div>
777
+ {/snippet}
778
+
779
+ {#snippet formGeneratorHeader()}
780
+ <div class="flex items-center justify-between gap-2">
781
+ <div>
782
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
783
+ Form
784
+ </p>
785
+ <h2 class="text-lg font-semibold leading-tight">Schema-driven form</h2>
786
+ </div>
787
+ </div>
788
+ {/snippet}
789
+
790
+ <div class="grid gap-6 lg:grid-cols-2">
791
+ <Card header={filesHeader}>
792
+ <div class="space-y-4">
793
+ <FilePicker
794
+ value={fileList}
795
+ onFilesSelected={(files) => (fileList = files)}
796
+ accept="image/*,.pdf"
797
+ />
798
+
799
+ <div class="flex items-center justify-between rounded-xl border border-[var(--border-color-default)] px-4 py-3">
800
+ <div>
801
+ <p class="text-sm font-medium">Confirm release</p>
802
+ <p class="text-xs text-[var(--color-text-muted)]">
803
+ Last file: {fileList?.[0]?.name ?? "none"}
804
+ </p>
805
+ </div>
806
+ <Button variant="primary" onClick={() => (dialogOpen = true)} sz="sm">
807
+ Open dialog
808
+ </Button>
809
+ </div>
810
+ </div>
811
+ </Card>
812
+
813
+ <Card header={formGeneratorHeader}>
814
+ <div class="space-y-4">
815
+ <Form
816
+ schema={miniFormSchema}
817
+ onSubmit={handleMiniSubmit}
818
+ validateOn="submit"
819
+ />
820
+ <div class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-3">
821
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
822
+ Last submit
823
+ </p>
824
+ <pre class="text-xs whitespace-pre-wrap text-[var(--color-text-default)]">
825
+ {formResult}
826
+ </pre>
827
+ </div>
828
+ </div>
829
+ </Card>
830
+ </div>
831
+
832
+ {#snippet menuHeader()}
833
+ <div class="flex items-center justify-between gap-2">
834
+ <div>
835
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
836
+ Menu
837
+ </p>
838
+ <h2 class="text-lg font-semibold leading-tight">Navigation controls</h2>
839
+ </div>
840
+ </div>
841
+ {/snippet}
842
+
843
+ {#snippet layoutHeader()}
844
+ <div class="flex items-center justify-between gap-2">
845
+ <div>
846
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
847
+ Layout
848
+ </p>
849
+ <h2 class="text-lg font-semibold leading-tight">Splitter + context menu</h2>
850
+ </div>
851
+ </div>
852
+ {/snippet}
853
+
854
+ {#snippet firstPane()}
855
+ <div class="h-full grid place-items-center text-sm font-medium">
856
+ First panel
857
+ </div>
858
+ {/snippet}
859
+
860
+ {#snippet secondPane()}
861
+ <div class="h-full grid place-items-center text-sm font-medium">
862
+ Second panel
863
+ </div>
864
+ {/snippet}
865
+
866
+ <div class="grid gap-6 lg:grid-cols-2">
867
+ <Card header={menuHeader}>
868
+ <div class="space-y-4">
869
+ <Menu menus={menuData} sz={menuSize} onSelect={handleMenuSelect} />
870
+
871
+ <div class="text-sm text-[var(--color-text-muted)]">
872
+ {menuSelection}
873
+ </div>
874
+
875
+ <div class="rounded-xl border border-[var(--border-color-default)] p-4">
876
+ <Pagination
877
+ currentPage={standalonePage}
878
+ totalPages={standaloneTotal}
879
+ onPageChange={(p) => (standalonePage = p)}
880
+ />
881
+ </div>
882
+
883
+ {#snippet pageOne()}
884
+ <div class="p-4 rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]">
885
+ <p class="text-sm font-medium">Release notes</p>
886
+ <p class="text-xs text-[var(--color-text-muted)]">
887
+ Snapshot of changes across components.
888
+ </p>
889
+ </div>
890
+ {/snippet}
891
+ {#snippet pageTwo()}
892
+ <div class="p-4 rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]">
893
+ <p class="text-sm font-medium">Design tasks</p>
894
+ <p class="text-xs text-[var(--color-text-muted)]">
895
+ Review tokens and spacing.
896
+ </p>
897
+ </div>
898
+ {/snippet}
899
+ {#snippet pageThree()}
900
+ <div class="p-4 rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]">
901
+ <p class="text-sm font-medium">QA checklist</p>
902
+ <p class="text-xs text-[var(--color-text-muted)]">
903
+ Validate keyboard and focus behavior.
904
+ </p>
905
+ </div>
906
+ {/snippet}
907
+
908
+ <PaginatedCard
909
+ items={[pageOne, pageTwo, pageThree]}
910
+ itemsPerPage={1}
911
+ />
912
+ </div>
913
+ </Card>
914
+
915
+ <Card header={layoutHeader}>
916
+ <div class="space-y-4">
917
+ <div class="h-56 rounded-xl border border-[var(--border-color-default)] overflow-hidden">
918
+ <Splitter
919
+ direction="horizontal"
920
+ initialSize={45}
921
+ dividerSize={6}
922
+ minSize={20}
923
+ maxSize={80}
924
+ first={firstPane}
925
+ second={secondPane}
926
+ />
927
+ </div>
928
+
929
+ <div
930
+ class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4 text-sm text-[var(--color-text-muted)]"
931
+ oncontextmenu={(e) => contextMenuRef?.openAt(e)}
932
+ role="button"
933
+ tabindex="0"
934
+ >
935
+ {contextMenuStatus}
936
+ </div>
937
+
938
+ <ContextMenu
939
+ bind:this={contextMenuRef}
940
+ onUndo={() => handleContextAction("Undo", "info")}
941
+ onRedo={() => handleContextAction("Redo", "info")}
942
+ onCopy={() => handleContextAction("Copy", "success")}
943
+ onCut={() => handleContextAction("Cut", "warning")}
944
+ onPaste={() => handleContextAction("Paste", "success")}
945
+ onDelete={() => handleContextAction("Delete", "danger")}
946
+ />
947
+ </div>
948
+ </Card>
949
+ </div>
950
+
951
+ {#snippet codeHeader()}
952
+ <div class="flex items-center justify-between gap-2">
953
+ <div>
954
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
955
+ Code
956
+ </p>
957
+ <h2 class="text-lg font-semibold leading-tight">CodeView editor</h2>
958
+ </div>
959
+ <Switch
960
+ checked={codeEditable}
961
+ onChange={(v) => (codeEditable = v)}
962
+ rightLabel={codeEditable ? "Editable" : "Read only"}
963
+ sz="sm"
964
+ />
965
+ </div>
966
+ {/snippet}
967
+
968
+ {#snippet quickHeader()}
969
+ <div class="flex items-center justify-between gap-2">
970
+ <div>
971
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
972
+ Actions
973
+ </p>
974
+ <h2 class="text-lg font-semibold leading-tight">Quick actions</h2>
975
+ </div>
976
+ </div>
977
+ {/snippet}
978
+
979
+ <div class="grid gap-6 lg:grid-cols-2">
980
+ <Card header={codeHeader} class="min-h-[360px]">
981
+ <CodeView
982
+ bind:code={codeSample}
983
+ language="js"
984
+ title="tokens.js"
985
+ showCopyButton={true}
986
+ showLineNumbers={true}
987
+ editable={codeEditable}
988
+ activeLine={true}
989
+ class="h-[320px]"
990
+ />
991
+ </Card>
992
+
993
+ <Card header={quickHeader}>
994
+ <div class="space-y-4">
995
+ <div class="flex flex-wrap gap-2">
996
+ <Button variant="secondary" onClick={() => pushToast("info")}>
997
+ Notify
998
+ </Button>
999
+ <Button variant="success" onClick={() => pushToast("success")}>
1000
+ Success
1001
+ </Button>
1002
+ <Button variant="danger" onClick={() => pushToast("danger")}>
1003
+ Error
1004
+ </Button>
1005
+ </div>
1006
+
1007
+ <div class="rounded-xl border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] p-4">
1008
+ <p class="text-xs uppercase tracking-[0.15em] text-[var(--color-text-muted)]">
1009
+ Active nav
1010
+ </p>
1011
+ <p class="text-lg font-semibold">{activeNav}</p>
1012
+ <p class="text-sm text-[var(--color-text-muted)]">
1013
+ Search: {searchQuery || "empty"}
1014
+ </p>
1015
+ </div>
1016
+ </div>
1017
+ </Card>
1018
+ </div>
1019
+
1020
+ <Dialog
1021
+ open={dialogOpen}
1022
+ title="Confirm release"
1023
+ message="Ready to ship the selected files?"
1024
+ onConfirm={() => {
1025
+ dialogOpen = false;
1026
+ pushToast("success");
1027
+ }}
1028
+ onCancel={() => {
1029
+ dialogOpen = false;
1030
+ }}
1031
+ onClose={() => {
1032
+ dialogOpen = false;
1033
+ }}
1034
+ />
1035
+ </div>
1036
+
1037
+ {#each toasts as toast (toast.id)}
1038
+ <Toast
1039
+ title={toast.title}
1040
+ message={toast.message}
1041
+ variant={toast.variant}
1042
+ onClose={() => removeToast(toast.id)}
1043
+ timeout={3500}
1044
+ />
1045
+ {/each}
1046
+ </Container>