svelora 3.0.6 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BentoGrid/BentoCard.svelte +45 -0
- package/dist/BentoGrid/BentoCard.svelte.d.ts +4 -0
- package/dist/BentoGrid/BentoGrid.svelte +9 -0
- package/dist/BentoGrid/BentoGrid.svelte.d.ts +4 -0
- package/dist/BentoGrid/bento-grid.types.d.ts +47 -0
- package/dist/BentoGrid/bento-grid.types.js +1 -0
- package/dist/BentoGrid/bento-grid.variants.d.ts +30 -0
- package/dist/BentoGrid/bento-grid.variants.js +16 -0
- package/dist/BentoGrid/index.d.ts +5 -0
- package/dist/BentoGrid/index.js +5 -0
- package/dist/Chart/Chart.svelte +47 -0
- package/dist/Chart/Chart.svelte.d.ts +4 -0
- package/dist/Chart/chart.types.d.ts +20 -0
- package/dist/Chart/chart.types.js +1 -0
- package/dist/Chart/chart.variants.d.ts +3 -0
- package/dist/Chart/chart.variants.js +4 -0
- package/dist/Chart/index.d.ts +4 -0
- package/dist/Chart/index.js +4 -0
- package/dist/Chat/ChatBubble.svelte +30 -0
- package/dist/Chat/ChatBubble.svelte.d.ts +4 -0
- package/dist/Chat/ChatInput.svelte +50 -0
- package/dist/Chat/ChatInput.svelte.d.ts +4 -0
- package/dist/Chat/ChatMessage.svelte +15 -0
- package/dist/Chat/ChatMessage.svelte.d.ts +4 -0
- package/dist/Chat/chat.types.d.ts +63 -0
- package/dist/Chat/chat.types.js +1 -0
- package/dist/Chat/chat.variants.d.ts +117 -0
- package/dist/Chat/chat.variants.js +47 -0
- package/dist/Chat/index.d.ts +6 -0
- package/dist/Chat/index.js +6 -0
- package/dist/ColorPicker/ColorPicker.svelte +109 -0
- package/dist/ColorPicker/ColorPicker.svelte.d.ts +4 -0
- package/dist/ColorPicker/color-picker.types.d.ts +26 -0
- package/dist/ColorPicker/color-picker.types.js +1 -0
- package/dist/ColorPicker/color-picker.variants.d.ts +69 -0
- package/dist/ColorPicker/color-picker.variants.js +13 -0
- package/dist/ColorPicker/index.d.ts +4 -0
- package/dist/ColorPicker/index.js +4 -0
- package/dist/DateRangePicker/DateRangePicker.svelte +59 -0
- package/dist/DateRangePicker/DateRangePicker.svelte.d.ts +4 -0
- package/dist/DateRangePicker/date-range-picker.types.d.ts +34 -0
- package/dist/DateRangePicker/date-range-picker.types.js +1 -0
- package/dist/DateRangePicker/date-range-picker.variants.d.ts +39 -0
- package/dist/DateRangePicker/date-range-picker.variants.js +20 -0
- package/dist/DateRangePicker/index.d.ts +4 -0
- package/dist/DateRangePicker/index.js +4 -0
- package/dist/List/List.svelte +14 -0
- package/dist/List/List.svelte.d.ts +4 -0
- package/dist/List/ListItem.svelte +64 -0
- package/dist/List/ListItem.svelte.d.ts +4 -0
- package/dist/List/index.d.ts +5 -0
- package/dist/List/index.js +5 -0
- package/dist/List/list.types.d.ts +62 -0
- package/dist/List/list.types.js +1 -0
- package/dist/List/list.variants.d.ts +99 -0
- package/dist/List/list.variants.js +42 -0
- package/dist/Marquee/Marquee.svelte +50 -0
- package/dist/Marquee/Marquee.svelte.d.ts +4 -0
- package/dist/Marquee/index.d.ts +4 -0
- package/dist/Marquee/index.js +4 -0
- package/dist/Marquee/marquee.types.d.ts +38 -0
- package/dist/Marquee/marquee.types.js +1 -0
- package/dist/Marquee/marquee.variants.d.ts +78 -0
- package/dist/Marquee/marquee.variants.js +28 -0
- package/dist/Menu/Menu.svelte +134 -0
- package/dist/Menu/Menu.svelte.d.ts +4 -0
- package/dist/Menu/index.d.ts +4 -0
- package/dist/Menu/index.js +4 -0
- package/dist/Menu/menu.types.d.ts +82 -0
- package/dist/Menu/menu.types.js +1 -0
- package/dist/Menu/menu.variants.d.ts +46 -0
- package/dist/Menu/menu.variants.js +32 -0
- package/dist/NumberTicker/NumberTicker.svelte +59 -0
- package/dist/NumberTicker/NumberTicker.svelte.d.ts +4 -0
- package/dist/NumberTicker/index.d.ts +4 -0
- package/dist/NumberTicker/index.js +4 -0
- package/dist/NumberTicker/number-ticker.types.d.ts +26 -0
- package/dist/NumberTicker/number-ticker.types.js +1 -0
- package/dist/NumberTicker/number-ticker.variants.d.ts +27 -0
- package/dist/NumberTicker/number-ticker.variants.js +6 -0
- package/dist/PasswordInput/PasswordInput.svelte +74 -0
- package/dist/PasswordInput/PasswordInput.svelte.d.ts +4 -0
- package/dist/PasswordInput/index.d.ts +4 -0
- package/dist/PasswordInput/index.js +4 -0
- package/dist/PasswordInput/password-input.types.d.ts +18 -0
- package/dist/PasswordInput/password-input.types.js +1 -0
- package/dist/PasswordInput/password-input.variants.d.ts +57 -0
- package/dist/PasswordInput/password-input.variants.js +11 -0
- package/dist/Prose/Prose.svelte +13 -0
- package/dist/Prose/Prose.svelte.d.ts +4 -0
- package/dist/Prose/index.d.ts +4 -0
- package/dist/Prose/index.js +4 -0
- package/dist/Prose/prose.types.d.ts +22 -0
- package/dist/Prose/prose.types.js +1 -0
- package/dist/Prose/prose.variants.d.ts +45 -0
- package/dist/Prose/prose.variants.js +45 -0
- package/dist/Rating/Rating.svelte +93 -0
- package/dist/Rating/Rating.svelte.d.ts +4 -0
- package/dist/Rating/index.d.ts +4 -0
- package/dist/Rating/index.js +4 -0
- package/dist/Rating/rating.types.d.ts +59 -0
- package/dist/Rating/rating.types.js +1 -0
- package/dist/Rating/rating.variants.d.ts +93 -0
- package/dist/Rating/rating.variants.js +32 -0
- package/dist/Resizable/Resizable.svelte +9 -0
- package/dist/Resizable/Resizable.svelte.d.ts +4 -0
- package/dist/Resizable/index.d.ts +4 -0
- package/dist/Resizable/index.js +4 -0
- package/dist/Resizable/resizable.types.d.ts +18 -0
- package/dist/Resizable/resizable.types.js +1 -0
- package/dist/Resizable/resizable.variants.d.ts +48 -0
- package/dist/Resizable/resizable.variants.js +17 -0
- package/dist/ScrollArea/ScrollArea.svelte +54 -0
- package/dist/ScrollArea/ScrollArea.svelte.d.ts +4 -0
- package/dist/ScrollArea/index.d.ts +4 -0
- package/dist/ScrollArea/index.js +4 -0
- package/dist/ScrollArea/scroll-area.types.d.ts +27 -0
- package/dist/ScrollArea/scroll-area.types.js +1 -0
- package/dist/ScrollArea/scroll-area.variants.d.ts +45 -0
- package/dist/ScrollArea/scroll-area.variants.js +27 -0
- package/dist/Sidebar/Sidebar.svelte +30 -0
- package/dist/Sidebar/Sidebar.svelte.d.ts +4 -0
- package/dist/Sidebar/index.d.ts +4 -0
- package/dist/Sidebar/index.js +4 -0
- package/dist/Sidebar/sidebar.types.d.ts +31 -0
- package/dist/Sidebar/sidebar.types.js +1 -0
- package/dist/Sidebar/sidebar.variants.d.ts +69 -0
- package/dist/Sidebar/sidebar.variants.js +23 -0
- package/dist/Spotlight/Spotlight.svelte +31 -0
- package/dist/Spotlight/Spotlight.svelte.d.ts +4 -0
- package/dist/Spotlight/index.d.ts +4 -0
- package/dist/Spotlight/index.js +4 -0
- package/dist/Spotlight/spotlight.types.d.ts +22 -0
- package/dist/Spotlight/spotlight.types.js +1 -0
- package/dist/Spotlight/spotlight.variants.d.ts +39 -0
- package/dist/Spotlight/spotlight.variants.js +8 -0
- package/dist/TagsInput/TagsInput.svelte +100 -0
- package/dist/TagsInput/TagsInput.svelte.d.ts +4 -0
- package/dist/TagsInput/index.d.ts +4 -0
- package/dist/TagsInput/index.js +4 -0
- package/dist/TagsInput/tags-input.types.d.ts +32 -0
- package/dist/TagsInput/tags-input.types.js +1 -0
- package/dist/TagsInput/tags-input.variants.d.ts +45 -0
- package/dist/TagsInput/tags-input.variants.js +22 -0
- package/dist/TreeView/TreeView.svelte +95 -0
- package/dist/TreeView/TreeView.svelte.d.ts +4 -0
- package/dist/TreeView/index.d.ts +4 -0
- package/dist/TreeView/index.js +4 -0
- package/dist/TreeView/tree-view.types.d.ts +68 -0
- package/dist/TreeView/tree-view.types.js +1 -0
- package/dist/TreeView/tree-view.variants.d.ts +69 -0
- package/dist/TreeView/tree-view.variants.js +30 -0
- package/dist/docs/navigation.js +108 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/mcp/svelora-docs.data.json +39 -3
- package/package.json +8 -6
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"packageName": "svelora",
|
|
4
|
-
"packageVersion": "3.0.
|
|
5
|
-
"generatedAt": "2026-06-
|
|
4
|
+
"packageVersion": "3.0.7",
|
|
5
|
+
"generatedAt": "2026-06-25T15:23:58.309Z",
|
|
6
6
|
"slugs": {
|
|
7
7
|
"components": [
|
|
8
8
|
"button",
|
|
@@ -14,21 +14,30 @@
|
|
|
14
14
|
"locale-button",
|
|
15
15
|
"kbd",
|
|
16
16
|
"theme-mode-button",
|
|
17
|
+
"bento-grid",
|
|
17
18
|
"card",
|
|
18
19
|
"container",
|
|
20
|
+
"resizable",
|
|
21
|
+
"scroll-area",
|
|
19
22
|
"separator",
|
|
20
23
|
"accordion",
|
|
24
|
+
"chart",
|
|
21
25
|
"avatar",
|
|
22
26
|
"avatar-group",
|
|
23
27
|
"badge",
|
|
24
28
|
"carousel",
|
|
25
29
|
"chip",
|
|
30
|
+
"list",
|
|
31
|
+
"number-ticker",
|
|
32
|
+
"prose",
|
|
26
33
|
"empty",
|
|
27
34
|
"skeleton",
|
|
28
35
|
"timeline",
|
|
29
36
|
"user",
|
|
30
37
|
"table",
|
|
38
|
+
"tree-view",
|
|
31
39
|
"checkbox",
|
|
40
|
+
"color-picker",
|
|
32
41
|
"checkbox-group",
|
|
33
42
|
"editor",
|
|
34
43
|
"input",
|
|
@@ -38,16 +47,24 @@
|
|
|
38
47
|
"slider",
|
|
39
48
|
"switch",
|
|
40
49
|
"textarea",
|
|
50
|
+
"password-input",
|
|
41
51
|
"file-upload",
|
|
42
52
|
"pin-input",
|
|
53
|
+
"rating",
|
|
54
|
+
"tags-input",
|
|
43
55
|
"form-field",
|
|
44
56
|
"form",
|
|
45
57
|
"alert",
|
|
46
58
|
"banner",
|
|
59
|
+
"chat",
|
|
60
|
+
"marquee",
|
|
47
61
|
"progress",
|
|
48
62
|
"toast",
|
|
63
|
+
"spotlight",
|
|
49
64
|
"breadcrumb",
|
|
50
65
|
"pagination",
|
|
66
|
+
"menu",
|
|
67
|
+
"sidebar",
|
|
51
68
|
"stepper",
|
|
52
69
|
"tabs",
|
|
53
70
|
"collapsible",
|
|
@@ -60,7 +77,8 @@
|
|
|
60
77
|
"slideover",
|
|
61
78
|
"tooltip",
|
|
62
79
|
"calendar",
|
|
63
|
-
"range-calendar"
|
|
80
|
+
"range-calendar",
|
|
81
|
+
"date-range-picker"
|
|
64
82
|
],
|
|
65
83
|
"hooks": [
|
|
66
84
|
"use-media-query",
|
|
@@ -91,21 +109,30 @@
|
|
|
91
109
|
"locale-button": "<script lang=\"ts\">\n import { CodeBlock, LocaleButton } from '$lib/index.js'\n import type { LocaleButtonLocale } from '$lib/index.js'\n\n const locales: LocaleButtonLocale[] = [\n {\n code: 'en',\n label: 'English',\n shortLabel: 'EN',\n description: 'Default content language'\n },\n {\n code: 'th',\n label: 'Thai',\n shortLabel: 'TH',\n description: 'Thai translation'\n },\n {\n code: 'ja',\n label: 'Japanese',\n shortLabel: 'JA',\n description: 'Japanese translation'\n }\n ]\n\n const variants = ['solid', 'outline', 'soft', 'subtle', 'ghost', 'link'] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n const integrationStrategies = [\n {\n title: 'Callback Strategy',\n badge: 'Recommended',\n description:\n 'Use onLocaleChange when your app controls locale changes in code. This works well with Paraglide cookie-based setups and custom i18n stores.',\n bullets: [\n 'Keeps the current pathname unchanged',\n 'Works with setLocale(..., { reload: false })',\n 'Best default for consumer apps'\n ]\n },\n {\n title: 'Href Strategy',\n badge: 'Optional',\n description:\n 'Use getLocaleHref when your app intentionally exposes locale-specific URLs such as /th/docs or /en/docs.',\n bullets: [\n 'Generates per-locale links',\n 'Useful for URL-prefix routing',\n 'Navigation happens through hrefs instead of imperative state'\n ]\n },\n {\n title: 'Custom I18n',\n badge: 'Flexible',\n description:\n 'You can use LocaleButton without Paraglide at all. Pass your own locale state, persistence, and translation runtime.',\n bullets: [\n 'No hard dependency on Paraglide',\n 'Works with stores, cookies, or API-backed preferences',\n 'Good for non-SvelteKit or mixed stacks'\n ]\n }\n ] as const\n\n const localeItemFields = [\n {\n field: 'code',\n required: 'Yes',\n description: 'The locale identifier used by your i18n layer, such as en or th.'\n },\n {\n field: 'label',\n required: 'Yes',\n description: 'The full label rendered in the dropdown menu.'\n },\n {\n field: 'shortLabel',\n required: 'No',\n description: 'Compact text for the trigger or badge, such as EN or TH.'\n },\n {\n field: 'description',\n required: 'No',\n description: 'Secondary helper text shown inside the menu.'\n },\n {\n field: 'href',\n required: 'No',\n description: 'A precomputed locale-specific link if your app uses href navigation.'\n },\n {\n field: 'hreflang',\n required: 'No',\n description: 'Optional hreflang value forwarded to anchor items.'\n },\n {\n field: 'disabled',\n required: 'No',\n description: 'Disables a specific locale item.'\n }\n ] as const\n\n const integrationChecklist = [\n 'Use onLocaleChange when you do not want locale prefixes in the URL.',\n 'Use getLocaleHref only when your routing strategy intentionally includes locale-specific paths.',\n 'Pass locale from your own source of truth so the trigger always reflects the current language.',\n 'Provide shortLabel values when you want compact trigger text such as EN, TH, or JA.',\n 'Use the children snippet when your product needs a fully custom trigger design.'\n ] as const\n\n let locale = $state('en')\n let customLocale = $state('th')\n\n const paraglideSetLocaleExample =\n `<script lang=\"ts\">\n import { LocaleButton, type LocaleButtonLocale } from 'svelora'\n import { setLocale, toLocale } from '$lib/paraglide/runtime'\n \n const locales: LocaleButtonLocale[] = [\n { code: 'en', label: 'English', shortLabel: 'EN' },\n { code: 'th', label: 'Thai', shortLabel: 'TH' }\n ]\n \n let locale = 'en'\n<` +\n `/script>\n\n<LocaleButton\n {locale}\n {locales}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n const target = toLocale(nextLocale)\n if (target) {\n return setLocale(target)\n }\n }}\n/>`\n\n const paraglideCookieExample =\n `<script lang=\"ts\">\n import { LocaleButton, type LocaleButtonLocale } from 'svelora'\n import { setLocale, toLocale } from '$lib/paraglide/runtime'\n \n const locales: LocaleButtonLocale[] = [\n { code: 'en', label: 'English', shortLabel: 'EN' },\n { code: 'th', label: 'Thai', shortLabel: 'TH' }\n ]\n \n let locale = 'en'\n<` +\n `/script>\n\n<LocaleButton\n {locale}\n {locales}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n const target = toLocale(nextLocale)\n if (target) {\n return setLocale(target, { reload: false })\n }\n }}\n/>`\n\n const consumerAppExample =\n `<script lang=\"ts\">\n import { LocaleButton, type LocaleButtonLocale } from 'svelora'\n import { setLocale, toLocale } from '$lib/paraglide/runtime'\n \n const locales: LocaleButtonLocale[] = [\n { code: 'en', label: 'English', shortLabel: 'EN' },\n { code: 'th', label: 'Thai', shortLabel: 'TH' }\n ]\n \n let locale = 'en'\n<` +\n `/script>\n\n<LocaleButton\n {locales}\n {locale}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n const target = toLocale(nextLocale)\n if (target) {\n return setLocale(target, { reload: false })\n }\n }}\n/>`\n\n const hrefStrategyExample =\n `<script lang=\"ts\">\n import { LocaleButton, type LocaleButtonLocale } from 'svelora'\n import { localizeHref } from '$lib/paraglide/runtime'\n \n const locales: LocaleButtonLocale[] = [\n { code: 'en', label: 'English', shortLabel: 'EN' },\n { code: 'th', label: 'Thai', shortLabel: 'TH' }\n ]\n \n let locale = 'en'\n const pathname = '/docs/components/locale-button'\n<` +\n `/script>\n\n<LocaleButton\n {locales}\n {locale}\n getLocaleHref={(nextLocale) => localizeHref(pathname, { locale: nextLocale })}\n/>`\n\n const customI18nExample =\n `<script lang=\"ts\">\n import { LocaleButton, type LocaleButtonLocale } from 'svelora'\n \n const locales: LocaleButtonLocale[] = [\n { code: 'en', label: 'English', shortLabel: 'EN' },\n { code: 'th', label: 'Thai', shortLabel: 'TH' }\n ]\n \n let locale = 'en'\n \n function applyLocale(nextLocale: string) {\n locale = nextLocale\n localStorage.setItem('preferred-locale', nextLocale)\n }\n<` +\n `/script>\n\n<LocaleButton\n {locales}\n {locale}\n onLocaleChange={(nextLocale) => {\n applyLocale(nextLocale)\n }}\n/>`\n\n const localeShapeExample = `import type { LocaleButtonLocale } from 'svelora'\n\nconst locales: LocaleButtonLocale[] = [\n {\n code: 'en',\n label: 'English',\n shortLabel: 'EN',\n description: 'Default content language'\n },\n {\n code: 'th',\n label: 'Thai',\n shortLabel: 'TH',\n description: 'Thai translation',\n href: '/th/docs',\n hreflang: 'th'\n }\n]`\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">LocaleButton</h1>\n <p class=\"text-on-surface-variant\">\n A language switcher button designed to work nicely with Paraglide. Use\n <code class=\"rounded bg-surface-container-highest px-1\">onLocaleChange</code> for\n <code class=\"rounded bg-surface-container-highest px-1\">setLocale()</code> flows\n while keeping the current path unchanged.\n </p>\n </div>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic Usage</h2>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <LocaleButton\n {locales}\n {locale}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n }}\n />\n <span class=\"text-sm text-on-surface-variant\">Current locale: {locale}</span>\n </div>\n </section>\n\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold\">Choose Your Strategy</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1\">LocaleButton</code> is only\n the UI layer. Your app decides how locale changes are applied.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-3\">\n {#each integrationStrategies as strategy (strategy.title)}\n <div class=\"space-y-3 rounded-2xl border border-outline-variant bg-surface-container/40 p-5\">\n <div class=\"flex items-center justify-between gap-3\">\n <h3 class=\"text-base font-semibold\">{strategy.title}</h3>\n <span class=\"rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {strategy.badge}\n </span>\n </div>\n <p class=\"text-sm text-on-surface-variant\">{strategy.description}</p>\n <ul class=\"space-y-2 text-sm text-on-surface-variant\">\n {#each strategy.bullets as bullet (bullet)}\n <li>{bullet}</li>\n {/each}\n </ul>\n </div>\n {/each}\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Paraglide with setLocale</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the selection callback when your app already manages locale changes in code.\n </p>\n <CodeBlock title=\"Paraglide setLocale()\" code={paraglideSetLocaleExample} />\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <LocaleButton\n {locales}\n {locale}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n }}\n />\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Use In Consumer Apps</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1\">LocaleButton</code> is\n exported from the\n <code class=\"rounded bg-surface-container-highest px-1\">svelora</code> package and can\n be used in any app that installs this library.\n </p>\n <p class=\"text-sm text-on-surface-variant\">\n The component does not depend on Paraglide internally. Your app provides\n <code class=\"rounded bg-surface-container-highest px-1\">locale</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">locales</code>, and the\n locale-change logic through\n <code class=\"rounded bg-surface-container-highest px-1\">onLocaleChange</code> or\n <code class=\"rounded bg-surface-container-highest px-1\">getLocaleHref</code>.\n </p>\n <CodeBlock title=\"Consumer app example\" code={consumerAppExample} />\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Keep The Same Path</h2>\n <p class=\"text-sm text-on-surface-variant\">\n With cookie-based Paraglide strategy, you can switch locale without adding\n <code class=\"rounded bg-surface-container-highest px-1\">/th</code> or other locale\n prefixes to the URL.\n </p>\n <CodeBlock title=\"Paraglide without locale prefix\" code={paraglideCookieExample} />\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <LocaleButton\n {locales}\n {locale}\n disableCurrentLocale={false}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n }}\n />\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Use Locale Prefix URLs</h2>\n <p class=\"text-sm text-on-surface-variant\">\n If your product intentionally uses locale-prefixed routes such as\n <code class=\"rounded bg-surface-container-highest px-1\">/th/docs</code>, provide\n <code class=\"rounded bg-surface-container-highest px-1\">getLocaleHref</code>. This\n keeps navigation declarative and lets the button render locale-specific links.\n </p>\n <CodeBlock title=\"Href strategy with localizeHref()\" code={hrefStrategyExample} />\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Use Without Paraglide</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The component works with any locale source of truth. You can connect it to a Svelte\n store, cookies, localStorage, an API-backed user preference, or another i18n runtime.\n </p>\n <CodeBlock title=\"Custom i18n integration\" code={customI18nExample} />\n </section>\n\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold\">Locale Item Shape</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Each locale entry is just data. Start with\n <code class=\"rounded bg-surface-container-highest px-1\">code</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">label</code>, then add\n optional fields only when your app needs them.\n </p>\n <CodeBlock title=\"LocaleButtonLocale[]\" code={localeShapeExample} />\n <div class=\"overflow-hidden rounded-2xl border border-outline-variant\">\n <table class=\"w-full text-left text-sm\">\n <thead class=\"bg-surface-container\">\n <tr>\n <th class=\"px-4 py-3 font-semibold\">Field</th>\n <th class=\"px-4 py-3 font-semibold\">Required</th>\n <th class=\"px-4 py-3 font-semibold\">Purpose</th>\n </tr>\n </thead>\n <tbody>\n {#each localeItemFields as field (field.field)}\n <tr class=\"border-t border-outline-variant/70\">\n <td class=\"px-4 py-3 font-mono text-xs\">{field.field}</td>\n <td class=\"px-4 py-3 text-on-surface-variant\">{field.required}</td>\n <td class=\"px-4 py-3 text-on-surface-variant\">{field.description}</td>\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold\">Integration Checklist</h2>\n <div class=\"rounded-2xl border border-outline-variant bg-surface-container/40 p-5\">\n <ul class=\"space-y-3 text-sm text-on-surface-variant\">\n {#each integrationChecklist as item (item)}\n <li>{item}</li>\n {/each}\n </ul>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each variants as variant (variant)}\n <LocaleButton\n {locales}\n {locale}\n {variant}\n size=\"sm\"\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n }}\n />\n {/each}\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"flex flex-wrap items-end gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <LocaleButton\n {locales}\n {locale}\n {size}\n onLocaleChange={(nextLocale) => {\n locale = nextLocale\n }}\n />\n {/each}\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Trigger</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the\n <code class=\"rounded bg-surface-container-highest px-1\">children</code> snippet to\n fully control the trigger content while keeping the dropdown behavior.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <LocaleButton\n {locales}\n locale={customLocale}\n square={false}\n variant=\"soft\"\n color=\"primary\"\n onLocaleChange={(nextLocale) => {\n customLocale = nextLocale\n }}\n >\n {#snippet children({ currentLocale, open })}\n <span class=\"flex items-center gap-2\">\n <span>{currentLocale?.label ?? 'Language'}</span>\n <span class=\"text-xs text-on-surface-variant\">\n {open ? 'Open' : currentLocale?.code.toUpperCase()}\n </span>\n </span>\n {/snippet}\n </LocaleButton>\n </div>\n </section>\n</div>\n",
|
|
92
110
|
"kbd": "<script lang=\"ts\">\n import { Kbd, useKbd } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const variants = ['solid', 'outline', 'soft', 'subtle'] as const\n const sizes = ['sm', 'md', 'lg'] as const\n\n let shortcutLog = $state<string[]>([])\n let searchOpen = $state(false)\n\n const kbd = useKbd({\n shortcuts: {\n 'ctrl+k': () => {\n searchOpen = !searchOpen\n shortcutLog = [...shortcutLog, 'Ctrl+K → Toggle search'].slice(-5)\n },\n 'ctrl+shift+p': () => {\n shortcutLog = [...shortcutLog, 'Ctrl+Shift+P → Command palette'].slice(-5)\n },\n escape: () => {\n searchOpen = false\n shortcutLog = [...shortcutLog, 'Escape → Close'].slice(-5)\n }\n },\n captureModifiers: true\n })\n\n const modifierKeys = ['meta', 'ctrl', 'alt', 'shift'] as const\n const specialKeys = [\n 'enter',\n 'escape',\n 'tab',\n 'backspace',\n 'delete',\n 'space',\n 'capslock'\n ] as const\n const arrowKeys = ['arrowup', 'arrowdown', 'arrowleft', 'arrowright'] as const\n const navKeys = ['pageup', 'pagedown', 'home', 'end'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Kbd</h1>\n <p class=\"text-on-surface-variant\">\n Keyboard key indicator for displaying shortcuts and key combinations.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n <Kbd value=\"K\" />\n <Kbd value=\"Enter\" />\n <Kbd value=\"Escape\" />\n <Kbd value=\"Tab\" />\n </div>\n </section>\n\n <!-- Modifier Keys -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Modifier Keys</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Platform-aware: Mac shows symbols, others show text labels.\n </p>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n {#each modifierKeys as key (key)}\n <Kbd value={key} />\n {/each}\n </div>\n </section>\n\n <!-- Special Keys -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Special Keys</h2>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n {#each specialKeys as key (key)}\n <Kbd value={key} />\n {/each}\n </div>\n </section>\n\n <!-- Arrow & Navigation Keys -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Arrow & Navigation Keys</h2>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n {#each arrowKeys as key (key)}\n <Kbd value={key} />\n {/each}\n <span class=\"mx-1 text-on-surface-variant\">|</span>\n {#each navKeys as key (key)}\n <Kbd value={key} />\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-1\">\n <span class=\"text-sm text-on-surface-variant\">{size}:</span>\n <Kbd value=\"K\" {size} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n {#each variants as variant (variant)}\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"w-16 text-sm text-on-surface-variant\">{variant}</span>\n {#each colors as color (color)}\n <Kbd value=\"K\" {variant} {color} />\n {/each}\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Children Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Children Slot</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the default snippet for custom key labels.\n </p>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n <Kbd>F1</Kbd>\n <Kbd>F12</Kbd>\n <Kbd>PgUp</Kbd>\n <Kbd>PgDn</Kbd>\n <Kbd>Ins</Kbd>\n <Kbd>Fn</Kbd>\n </div>\n </section>\n\n <!-- Keyboard Shortcuts -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Keyboard Shortcuts</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each [{ label: 'Copy', keys: ['meta', 'C'] }, { label: 'Paste', keys: ['meta', 'V'] }, { label: 'Save', keys: ['meta', 'S'] }, { label: 'Search', keys: ['meta', 'K'] }, { label: 'Undo', keys: ['meta', 'Z'] }, { label: 'Redo', keys: ['meta', 'shift', 'Z'] }] as shortcut (shortcut.label)}\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm\">{shortcut.label}</span>\n <div class=\"flex items-center gap-1\">\n {#each shortcut.keys as key, i (i)}\n {#if i > 0}\n <span class=\"text-on-surface-variant\">+</span>\n {/if}\n <Kbd value={key} />\n {/each}\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n <Kbd value=\"K\" ui={{ base: 'rounded-md' }} />\n <Kbd value=\"meta\" ui={{ base: 'rounded-full px-2' }} />\n <Kbd value=\"Enter\" ui={{ base: 'rounded-lg px-2 shadow-sm' }} variant=\"soft\" />\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Command Palette -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Command Palette Trigger</p>\n <div\n class=\"flex items-center gap-2 rounded-lg border border-outline-variant bg-surface-container p-3\"\n >\n <span class=\"flex-1 text-sm text-on-surface-variant\">Search commands...</span>\n <div class=\"flex items-center gap-1\">\n <Kbd value=\"meta\" size=\"sm\" />\n <Kbd value=\"K\" size=\"sm\" />\n </div>\n </div>\n </div>\n\n <!-- Menu Item -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Menu Item with Shortcut</p>\n <div class=\"max-w-xs rounded-lg border border-outline-variant bg-surface-container\">\n {#each [{ label: 'New File', keys: ['meta', 'N'] }, { label: 'Open File', keys: ['meta', 'O'] }, { label: 'Save', keys: ['meta', 'S'] }] as item (item.label)}\n <div\n class=\"flex items-center justify-between px-3 py-2 hover:bg-surface-container-high\"\n >\n <span class=\"text-sm\">{item.label}</span>\n <div class=\"flex items-center gap-0.5\">\n {#each item.keys as key (key)}\n <Kbd value={key} size=\"sm\" variant=\"soft\" />\n {/each}\n </div>\n </div>\n {/each}\n </div>\n </div>\n\n <!-- Inline Help -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Inline Help Text</p>\n <p class=\"text-sm text-on-surface-variant\">\n Press <Kbd value=\"escape\" size=\"sm\" class=\"mx-0.5\" /> to close the modal, or\n <Kbd value=\"enter\" size=\"sm\" class=\"mx-0.5\" /> to confirm.\n </p>\n </div>\n\n <!-- Navigation Hint -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Navigation Hint</p>\n <div class=\"flex items-center gap-4 text-sm text-on-surface-variant\">\n <span class=\"flex items-center gap-1\">\n <Kbd value=\"arrowup\" size=\"sm\" />\n <Kbd value=\"arrowdown\" size=\"sm\" />\n to navigate\n </span>\n <span class=\"flex items-center gap-1\">\n <Kbd value=\"enter\" size=\"sm\" />\n to select\n </span>\n </div>\n </div>\n </div>\n </section>\n\n <!-- useKbd: Shortcut Listener -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">useKbd — Shortcut Listener</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Try pressing the shortcuts below. The hook listens for keyboard events and fires\n callbacks.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-wrap gap-4 text-sm\">\n <span class=\"flex items-center gap-1\">\n <Kbd value=\"ctrl\" size=\"sm\" /> + <Kbd value=\"K\" size=\"sm\" /> Toggle search\n </span>\n <span class=\"flex items-center gap-1\">\n <Kbd value=\"ctrl\" size=\"sm\" /> + <Kbd value=\"shift\" size=\"sm\" /> +\n <Kbd value=\"P\" size=\"sm\" /> Command palette\n </span>\n <span class=\"flex items-center gap-1\">\n <Kbd value=\"escape\" size=\"sm\" /> Close\n </span>\n </div>\n\n {#if searchOpen}\n <div\n class=\"flex items-center gap-2 rounded-lg border border-primary/50 bg-surface-container p-3\"\n >\n <span class=\"flex-1 text-sm text-on-surface-variant\">\n Search is open! Press <Kbd value=\"escape\" size=\"sm\" class=\"mx-0.5\" /> to close.\n </span>\n </div>\n {/if}\n\n {#if shortcutLog.length > 0}\n <div class=\"space-y-1 rounded-md bg-surface-container p-3\">\n <p class=\"text-xs font-medium text-on-surface-variant\">Recent shortcuts:</p>\n {#each shortcutLog as entry, i (i)}\n <p class=\"font-mono text-xs text-on-surface\">{entry}</p>\n {/each}\n </div>\n {/if}\n </div>\n </section>\n\n <!-- useKbd: Reactive Key State -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">useKbd — Reactive Key State</h2>\n <p class=\"text-sm text-on-surface-variant\">Hold any key to see it tracked in real-time.</p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-wrap gap-2\">\n {#each ['shift', 'ctrl', 'alt', 'meta'] as key (key)}\n <div\n class=\"rounded-lg border px-3 py-2 text-sm transition-colors {kbd.isPressed(\n key\n )\n ? 'border-primary bg-primary-container text-on-primary-container'\n : 'border-outline-variant bg-surface-container text-on-surface-variant'}\"\n >\n <Kbd\n value={key}\n size=\"sm\"\n variant={kbd.isPressed(key) ? 'solid' : 'outline'}\n color={kbd.isPressed(key) ? 'primary' : 'surface'}\n />\n <span class=\"ml-1\">{kbd.isPressed(key) ? 'Pressed' : 'Released'}</span>\n </div>\n {/each}\n </div>\n\n <div class=\"rounded-md bg-surface-container p-3\">\n <p class=\"text-xs font-medium text-on-surface-variant\">Currently pressed keys:</p>\n <p class=\"mt-1 font-mono text-sm text-on-surface\">\n {#if kbd.pressedKeys.size > 0}\n {[...kbd.pressedKeys].join(' + ')}\n {:else}\n <span class=\"text-on-surface-variant\">None</span>\n {/if}\n </p>\n </div>\n </div>\n </section>\n\n <!-- Variants x Colors Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants x Colors</h2>\n <div class=\"overflow-x-auto rounded-lg bg-surface-container-high p-4\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-3 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Variant</th\n >\n {#each colors as color (color)}\n <th\n class=\"px-3 py-3 text-center text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each variants as variant (variant)}\n <tr class=\"border-b border-outline-variant/50\">\n <td class=\"px-3 py-3 text-sm font-medium text-on-surface-variant\"\n >{variant}</td\n >\n {#each colors as color (color)}\n <td class=\"px-3 py-3 text-center\">\n <Kbd value=\"K\" {variant} {color} />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n</div>\n",
|
|
93
111
|
"theme-mode-button": "<script lang=\"ts\">\n import { ThemeModeButton, mode } from '$lib/index.js'\n\n const variants = ['solid', 'outline', 'soft', 'subtle', 'ghost', 'link'] as const\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">ThemeModeButton</h1>\n <p class=\"text-on-surface-variant\">\n A button to switch between light and dark mode. Built on top of the Button component\n and Svelora theme utilities.\n </p>\n </div>\n\n <!-- Basic Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic Usage</h2>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <ThemeModeButton />\n <span class=\"text-sm text-on-surface-variant capitalize\">\n Current mode: {mode.current}\n </span>\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each variants as variant (variant)}\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton {variant} color=\"primary\" />\n <span class=\"text-xs text-on-surface-variant capitalize\">{variant}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-2 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Variant</th\n >\n {#each colors as color (color)}\n <th\n class=\"px-2 py-3 text-center text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each variants as variant (variant)}\n <tr class=\"border-b border-outline-variant/50\">\n <td\n class=\"px-2 py-3 text-sm font-medium text-on-surface-variant capitalize\"\n >{variant}</td\n >\n {#each colors as color (color)}\n <td class=\"px-2 py-3 text-center\">\n <ThemeModeButton {variant} {color} />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"flex flex-wrap items-end gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton {size} />\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Custom Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override the default light/dark icons using the\n <code class=\"rounded bg-surface-container-highest px-1\">lightIcon</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">darkIcon</code> props.\n </p>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton lightIcon=\"lucide:sun-medium\" darkIcon=\"lucide:moon-star\" />\n <span class=\"text-xs text-on-surface-variant\">sun-medium / moon-star</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton lightIcon=\"lucide:sun-dim\" darkIcon=\"lucide:cloud-moon\" />\n <span class=\"text-xs text-on-surface-variant\">sun-dim / cloud-moon</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton lightIcon=\"lucide:sunrise\" darkIcon=\"lucide:sunset\" />\n <span class=\"text-xs text-on-surface-variant\">sunrise / sunset</span>\n </div>\n </div>\n </section>\n\n <!-- Non-square (with label via children) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Content (Children Snippet)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">children</code> snippet\n with\n <code class=\"rounded bg-surface-container-highest px-1\">{'{ isDark }'}</code> to render custom\n content.\n </p>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <ThemeModeButton square={false} variant=\"outline\" color=\"secondary\">\n {#snippet children({ isDark })}\n {isDark ? 'Light Mode' : 'Dark Mode'}\n {/snippet}\n </ThemeModeButton>\n\n <ThemeModeButton square={false} variant=\"soft\" color=\"primary\">\n {#snippet children({ isDark })}\n <span class=\"flex items-center gap-2\">\n {#if isDark}\n ☀️ Switch to Light\n {:else}\n 🌙 Switch to Dark\n {/if}\n </span>\n {/snippet}\n </ThemeModeButton>\n </div>\n </section>\n\n <!-- Disabled & Loading -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled & Loading</h2>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton disabled />\n <span class=\"text-xs text-on-surface-variant\">Disabled</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton loading />\n <span class=\"text-xs text-on-surface-variant\">Loading</span>\n </div>\n </div>\n </section>\n\n <!-- UI Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Prop (Class Overrides)</h2>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton class=\"rounded-full\" variant=\"outline\" color=\"primary\" />\n <span class=\"text-xs text-on-surface-variant\">Pill shape</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton\n variant=\"solid\"\n color=\"tertiary\"\n class=\"shadow-lg shadow-tertiary/30\"\n />\n <span class=\"text-xs text-on-surface-variant\">With shadow</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <ThemeModeButton\n variant=\"solid\"\n color=\"primary\"\n class=\"bg-linear-to-r from-primary to-tertiary hover:from-primary/90 hover:to-tertiary/90\"\n />\n <span class=\"text-xs text-on-surface-variant\">Gradient</span>\n </div>\n </div>\n </section>\n</div>\n",
|
|
112
|
+
"bento-grid": "<script lang=\"ts\">\n import { BentoGrid, BentoCard, Icon } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">BentoGrid</h1>\n <p class=\"text-on-surface-variant\">\n A masonry-like grid layout composed of beautifully animated cards, inspired by Bento boxes.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">BentoGrid</code> as a wrapper and <code class=\"rounded bg-surface-container-highest px-1\">BentoCard</code> for the children. \n Adjust the <code class=\"rounded bg-surface-container-highest px-1\">col-span-*</code> classes on the children to make them span different columns.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 bg-surface-50 dark:bg-surface-900\">\n <BentoGrid class=\"max-w-3xl mx-auto\">\n <BentoCard \n class=\"md:col-span-2 md:row-span-2\" \n name=\"Analytics\" \n icon=\"lucide:line-chart\"\n description=\"Dive deep into your traffic data.\"\n href=\"#\"\n cta=\"View dashboard\"\n >\n {#snippet background()}\n <div class=\"absolute right-0 top-0 opacity-10 blur-xl\">\n <Icon name=\"lucide:bar-chart-3\" size=\"200\" />\n </div>\n {/snippet}\n </BentoCard>\n\n <BentoCard \n name=\"Notifications\" \n icon=\"lucide:bell\"\n description=\"Manage your alert settings.\"\n href=\"#\"\n />\n\n <BentoCard \n name=\"Security\" \n icon=\"lucide:shield-check\"\n description=\"Keep your account safe.\"\n href=\"#\"\n />\n \n <BentoCard \n class=\"md:col-span-3\"\n name=\"Integrations\" \n icon=\"lucide:blocks\"\n description=\"Connect with your favorite tools seamlessly.\"\n href=\"#\"\n cta=\"Browse tools\"\n />\n </BentoGrid>\n </div>\n </section>\n</div>\n",
|
|
94
113
|
"card": "<script lang=\"ts\">\n import { Card, Button, Icon, Avatar } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Card</h1>\n <p class=\"text-on-surface-variant\">\n Container component for grouping related content with optional header and footer.\n </p>\n </div>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <Card variant=\"outline\">\n {#snippet header()}\n <h3 class=\"font-semibold\">Outline (Default)</h3>\n {/snippet}\n <p class=\"text-on-surface-variant\">Card with border and background.</p>\n </Card>\n\n <Card variant=\"soft\">\n {#snippet header()}\n <h3 class=\"font-semibold\">Soft</h3>\n {/snippet}\n <p class=\"text-on-surface-variant\">Card with subtle background.</p>\n </Card>\n\n <Card variant=\"subtle\">\n {#snippet header()}\n <h3 class=\"font-semibold\">Subtle</h3>\n {/snippet}\n <p class=\"text-on-surface-variant\">Card with background and border.</p>\n </Card>\n\n <Card variant=\"solid\">\n {#snippet header()}\n <h3 class=\"font-semibold\">Solid</h3>\n {/snippet}\n <p class=\"opacity-90\">Card with inverted colors.</p>\n </Card>\n </div>\n </section>\n\n <!-- With Header and Footer -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Header & Footer</h2>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <Card>\n {#snippet header()}\n <div class=\"flex items-center justify-between\">\n <h3 class=\"font-semibold\">Card Title</h3>\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n icon=\"lucide:more-horizontal\"\n square\n size=\"sm\"\n />\n </div>\n {/snippet}\n <p class=\"text-on-surface-variant\">\n This is the card body content. You can put any content here.\n </p>\n {#snippet footer()}\n <div class=\"flex justify-end gap-2\">\n <Button variant=\"ghost\" color=\"secondary\" label=\"Cancel\" size=\"sm\" />\n <Button variant=\"solid\" color=\"primary\" label=\"Save\" size=\"sm\" />\n </div>\n {/snippet}\n </Card>\n\n <Card variant=\"soft\">\n {#snippet header()}\n <div class=\"flex items-center gap-3\">\n <Avatar src=\"https://i.pravatar.cc/150?img=5\" alt=\"John Doe\" size=\"md\" />\n <div>\n <h3 class=\"font-semibold\">John Doe</h3>\n <p class=\"text-sm text-on-surface-variant\">Software Engineer</p>\n </div>\n </div>\n {/snippet}\n <p class=\"text-on-surface-variant\">\n Building awesome things with Svelte and TypeScript.\n </p>\n {#snippet footer()}\n <div class=\"flex gap-2\">\n <Button\n variant=\"soft\"\n color=\"primary\"\n leadingIcon=\"lucide:mail\"\n label=\"Contact\"\n size=\"sm\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n leadingIcon=\"lucide:user-plus\"\n label=\"Follow\"\n size=\"sm\"\n />\n </div>\n {/snippet}\n </Card>\n </div>\n </section>\n\n <!-- Body Only -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Body Only</h2>\n <div class=\"grid gap-4 md:grid-cols-3\">\n <Card>\n <div class=\"text-center\">\n <Icon name=\"lucide:folder\" size=\"32\" class=\"mx-auto text-primary\" />\n <h4 class=\"mt-2 font-medium\">Documents</h4>\n <p class=\"text-sm text-on-surface-variant\">24 files</p>\n </div>\n </Card>\n\n <Card variant=\"soft\">\n <div class=\"text-center\">\n <Icon name=\"lucide:image\" size=\"32\" class=\"mx-auto text-success\" />\n <h4 class=\"mt-2 font-medium\">Images</h4>\n <p class=\"text-sm text-on-surface-variant\">128 files</p>\n </div>\n </Card>\n\n <Card variant=\"subtle\">\n <div class=\"text-center\">\n <Icon name=\"lucide:music\" size=\"32\" class=\"mx-auto text-tertiary\" />\n <h4 class=\"mt-2 font-medium\">Music</h4>\n <p class=\"text-sm text-on-surface-variant\">56 files</p>\n </div>\n </Card>\n </div>\n </section>\n\n <!-- Custom Styling -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Styling</h2>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <Card class=\"border-l-4 border-l-primary\">\n {#snippet header()}\n <h3 class=\"font-semibold text-primary\">Accent Border</h3>\n {/snippet}\n <p class=\"text-on-surface-variant\">Card with left accent border.</p>\n </Card>\n\n <Card\n class=\"shadow-lg\"\n ui={{ header: 'bg-primary text-on-primary', body: 'bg-primary-container' }}\n >\n {#snippet header()}\n <h3 class=\"font-semibold\">Custom Slots</h3>\n {/snippet}\n <p class=\"text-on-primary-container\">\n Card with custom header and body styles via ui prop.\n </p>\n </Card>\n </div>\n </section>\n\n <!-- As Different Element -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">As Different Element</h2>\n <Card as=\"article\" variant=\"soft\">\n {#snippet header()}\n <h3 class=\"font-semibold\">Article Card</h3>\n <p class=\"text-sm text-on-surface-variant\">Rendered as <article> element</p>\n {/snippet}\n <p class=\"text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">as</code> prop to render\n as different HTML elements for better semantics.\n </p>\n </Card>\n </section>\n</div>\n",
|
|
95
114
|
"container": "<script lang=\"ts\">\n import { Container } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Container</h1>\n <p class=\"text-on-surface-variant\">\n Layout component that centers and constrains content width with responsive padding.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Container class=\"rounded-lg bg-primary-container/30 py-4 text-center\">\n <p class=\"text-on-primary-container\">\n Content is centered with max-w-7xl and responsive padding.\n </p>\n </Container>\n </div>\n </section>\n\n <!-- Semantic Elements -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Semantic Elements</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">as</code> prop to render as\n different HTML elements.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Container as=\"section\" class=\"rounded-lg bg-secondary-container/30 py-3 text-center\">\n <p class=\"text-sm text-on-secondary-container\">Rendered as <section></p>\n </Container>\n <Container as=\"main\" class=\"rounded-lg bg-tertiary-container/30 py-3 text-center\">\n <p class=\"text-sm text-on-tertiary-container\">Rendered as <main></p>\n </Container>\n <Container as=\"article\" class=\"rounded-lg bg-success-container/30 py-3 text-center\">\n <p class=\"text-sm text-on-success-container\">Rendered as <article></p>\n </Container>\n <Container as=\"nav\" class=\"rounded-lg bg-info-container/30 py-3 text-center\">\n <p class=\"text-sm text-on-info-container\">Rendered as <nav></p>\n </Container>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override the default max-width and padding using the <code\n class=\"rounded bg-surface-container-highest px-1\">ui</code\n > prop.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Container\n ui={{ root: 'max-w-sm' }}\n class=\"rounded-lg bg-warning-container/30 py-3 text-center\"\n >\n <p class=\"text-sm text-on-warning-container\">max-w-sm</p>\n </Container>\n <Container\n ui={{ root: 'max-w-xl' }}\n class=\"rounded-lg bg-primary-container/30 py-3 text-center\"\n >\n <p class=\"text-sm text-on-primary-container\">max-w-xl</p>\n </Container>\n <Container\n ui={{ root: 'max-w-3xl' }}\n class=\"rounded-lg bg-secondary-container/30 py-3 text-center\"\n >\n <p class=\"text-sm text-on-secondary-container\">max-w-3xl</p>\n </Container>\n <Container class=\"rounded-lg bg-tertiary-container/30 py-3 text-center\">\n <p class=\"text-sm text-on-tertiary-container\">max-w-7xl (default)</p>\n </Container>\n </div>\n </section>\n\n <!-- Custom Class -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Class</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Container class=\"rounded-xl border border-outline-variant bg-surface-container p-6\">\n <h3 class=\"mb-2 font-medium\">Card-like Container</h3>\n <p class=\"text-sm text-on-surface-variant\">\n Combine Container with custom classes to create card-like layouts with\n consistent max-width.\n </p>\n </Container>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Page Layout -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Page Header</p>\n <Container\n as=\"header\"\n class=\"rounded-lg border border-outline-variant bg-surface-container py-4\"\n >\n <div class=\"flex items-center justify-between\">\n <span class=\"font-bold text-primary\">Logo</span>\n <div class=\"flex gap-4 text-sm text-on-surface-variant\">\n <span>Home</span>\n <span>About</span>\n <span>Contact</span>\n </div>\n </div>\n </Container>\n </div>\n\n <!-- Content Section -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Content Section</p>\n <Container\n as=\"section\"\n ui={{ root: 'max-w-3xl' }}\n class=\"rounded-lg border border-outline-variant bg-surface-container py-6\"\n >\n <h3 class=\"mb-2 text-lg font-semibold\">Article Title</h3>\n <p class=\"text-sm text-on-surface-variant\">\n A narrow container is ideal for long-form content like blog posts and\n articles, keeping the line length readable and comfortable.\n </p>\n </Container>\n </div>\n\n <!-- Footer -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Footer</p>\n <Container\n as=\"footer\"\n class=\"rounded-lg border border-outline-variant bg-surface-container py-4\"\n >\n <div class=\"flex items-center justify-between text-sm text-on-surface-variant\">\n <span>© 2026 Svelora</span>\n <div class=\"flex gap-4\">\n <span>Privacy</span>\n <span>Terms</span>\n </div>\n </div>\n </Container>\n </div>\n </div>\n </section>\n</div>\n",
|
|
115
|
+
"resizable": "<script lang=\"ts\">\n import { Resizable } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Resizable</h1>\n <p class=\"text-on-surface-variant\">\n A simple container that allows users to resize its dimensions.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Wrap your content in a <code class=\"rounded bg-surface-container-highest px-1\">Resizable</code> component. By default, it can be resized in both directions.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <Resizable direction=\"both\" class=\"min-h-32 min-w-32 h-48 w-48 rounded-lg border border-outline-variant bg-surface overflow-hidden\">\n <div class=\"flex h-full items-center justify-center p-6 text-center text-sm text-on-surface-variant\">\n Drag the bottom-right corner to resize\n </div>\n </Resizable>\n </div>\n </section>\n\n <!-- Horizontal & Vertical -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Horizontal & Vertical</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">direction</code> prop to restrict resizing to either <code class=\"rounded bg-surface-container-highest px-1\">horizontal</code> or <code class=\"rounded bg-surface-container-highest px-1\">vertical</code>.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex gap-4 justify-center flex-wrap\">\n <Resizable direction=\"horizontal\" class=\"min-h-32 min-w-32 h-32 w-48 rounded-lg border border-outline-variant bg-surface overflow-hidden\">\n <div class=\"flex h-full items-center justify-center p-4 font-semibold\">\n Horizontal\n </div>\n </Resizable>\n \n <Resizable direction=\"vertical\" class=\"min-h-32 min-w-32 h-48 w-32 rounded-lg border border-outline-variant bg-surface overflow-hidden\">\n <div class=\"flex h-full items-center justify-center p-4 font-semibold\">\n Vertical\n </div>\n </Resizable>\n </div>\n </section>\n</div>\n",
|
|
116
|
+
"scroll-area": "<script lang=\"ts\">\n import { ScrollArea, Separator } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">ScrollArea</h1>\n <p class=\"text-on-surface-variant\">\n Augments native scroll functionality for custom, cross-browser styling.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Wrap any content in a <code class=\"rounded bg-surface-container-highest px-1\">ScrollArea</code> and provide it with a fixed height.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <ScrollArea class=\"h-72 w-full max-w-sm rounded-lg border border-outline-variant bg-surface p-4\">\n <div class=\"space-y-4\">\n <h4 class=\"text-sm font-semibold\">Svelora Components</h4>\n <Separator />\n {#each Array(50) as _, i}\n <div class=\"text-sm text-on-surface-variant\">Component {i + 1}</div>\n {#if i !== 49}\n <Separator />\n {/if}\n {/each}\n </div>\n </ScrollArea>\n </div>\n </section>\n\n <!-- Horizontal -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Horizontal Scrolling</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1\">orientation=\"horizontal\"</code> or <code class=\"rounded bg-surface-container-highest px-1\">orientation=\"both\"</code> to enable horizontal scrollbars.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <ScrollArea orientation=\"horizontal\" class=\"w-full max-w-lg rounded-lg border border-outline-variant bg-surface p-4 whitespace-nowrap\">\n <div class=\"flex gap-4\">\n {#each Array(10) as _, i}\n <div class=\"h-32 w-32 shrink-0 rounded-md bg-surface-container-highest flex items-center justify-center font-bold text-on-surface-variant\">\n Item {i + 1}\n </div>\n {/each}\n </div>\n </ScrollArea>\n </div>\n </section>\n</div>\n",
|
|
96
117
|
"separator": "<script lang=\"ts\">\n import { Separator, Button } from '$lib/index.js'\n\n const colors = [\n 'surface',\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n const types = ['solid', 'dashed', 'dotted'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Separator</h1>\n <p class=\"text-on-surface-variant\">\n Visual divider for separating content with optional label, icon, or avatar.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p>Content above</p>\n <Separator class=\"my-4\" />\n <p>Content below</p>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <Separator {color} label={color} />\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <Separator {size} color=\"primary\" label={size} />\n {/each}\n </div>\n </section>\n\n <!-- Types -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Types</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each types as type (type)}\n <Separator {type} size=\"sm\" color=\"primary\" label={type} />\n {/each}\n </div>\n </section>\n\n <!-- With Label -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Label</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Separator label=\"OR\" color=\"primary\" />\n <Separator label=\"Continue reading\" color=\"secondary\" size=\"sm\" />\n <Separator label=\"Section break\" color=\"tertiary\" type=\"dashed\" />\n </div>\n </section>\n\n <!-- With Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icon</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Separator icon=\"lucide:star\" color=\"warning\" size=\"sm\" />\n <Separator icon=\"lucide:heart\" color=\"error\" size=\"md\" />\n <Separator icon=\"lucide:zap\" color=\"primary\" size=\"lg\" />\n </div>\n </section>\n\n <!-- With Avatar -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Avatar</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <p class=\"text-xs text-on-surface-variant\">Default avatarSize (2xs from config)</p>\n <Separator\n avatar={{ src: 'https://i.pravatar.cc/150?img=1', alt: 'Default size' }}\n color=\"primary\"\n />\n <p class=\"text-xs text-on-surface-variant\">Avatar with explicit size override (sm)</p>\n <Separator\n avatar={{ src: 'https://i.pravatar.cc/150?img=2', alt: 'Size sm', size: 'sm' }}\n color=\"secondary\"\n />\n <p class=\"text-xs text-on-surface-variant\">Avatar with explicit size override (md)</p>\n <Separator\n avatar={{ src: 'https://i.pravatar.cc/150?img=3', alt: 'Size md', size: 'md' }}\n color=\"tertiary\"\n />\n </div>\n </section>\n\n <!-- With Custom Content -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Content</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Separator color=\"primary\">\n {#snippet content()}\n <Button size=\"sm\" variant=\"soft\" color=\"primary\">Load more</Button>\n {/snippet}\n </Separator>\n <Separator color=\"secondary\">\n {#snippet content()}\n <span\n class=\"rounded-full bg-secondary-container px-3 py-1 text-xs text-on-secondary-container\"\n >\n New messages\n </span>\n {/snippet}\n </Separator>\n </div>\n </section>\n\n <!-- Vertical Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Vertical Orientation</h2>\n <div class=\"flex h-24 items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <span>Left</span>\n <Separator orientation=\"vertical\" />\n <span>Center</span>\n <Separator orientation=\"vertical\" color=\"primary\" />\n <span>Right</span>\n </div>\n <div class=\"flex h-32 items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <span>Item 1</span>\n <Separator orientation=\"vertical\" color=\"primary\" label=\"OR\" size=\"sm\" />\n <span>Item 2</span>\n <Separator orientation=\"vertical\" color=\"secondary\" icon=\"lucide:plus\" size=\"sm\" />\n <span>Item 3</span>\n </div>\n </section>\n\n <!-- Color x Size Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors × Sizes</h2>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-3 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Color</th\n >\n {#each sizes as size (size)}\n <th\n class=\"px-3 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >{size}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each colors as color (color)}\n <tr class=\"border-b border-outline-variant/50\">\n <td\n class=\"px-3 py-3 text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</td\n >\n {#each sizes as size (size)}\n <td class=\"px-3 py-3\">\n <div class=\"w-24\">\n <Separator {color} {size} />\n </div>\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Login Form Divider -->\n <div>\n <p class=\"mb-3 text-sm font-medium text-on-surface-variant\">Login Form</p>\n <div class=\"mx-auto max-w-sm space-y-4 rounded-lg bg-surface p-4\">\n <Button block variant=\"outline\">Continue with Google</Button>\n <Separator label=\"or continue with email\" size=\"sm\" />\n <Button block>Sign in</Button>\n </div>\n </div>\n\n <!-- Card Sections -->\n <div>\n <p class=\"mb-3 text-sm font-medium text-on-surface-variant\">Card Sections</p>\n <div class=\"mx-auto max-w-sm rounded-lg bg-surface p-4\">\n <h3 class=\"font-semibold\">Order Summary</h3>\n <p class=\"text-sm text-on-surface-variant\">2 items</p>\n <Separator class=\"my-3\" />\n <div class=\"flex justify-between text-sm\">\n <span>Subtotal</span>\n <span>$99.00</span>\n </div>\n <div class=\"flex justify-between text-sm\">\n <span>Shipping</span>\n <span>$5.00</span>\n </div>\n <Separator class=\"my-3\" type=\"dashed\" />\n <div class=\"flex justify-between font-semibold\">\n <span>Total</span>\n <span>$104.00</span>\n </div>\n </div>\n </div>\n\n <!-- Timeline -->\n <div>\n <p class=\"mb-3 text-sm font-medium text-on-surface-variant\">Timeline</p>\n <div class=\"mx-auto max-w-sm space-y-2\">\n <div class=\"flex items-center gap-3\">\n <div class=\"size-2 rounded-full bg-success\"></div>\n <span class=\"text-sm\">Order placed</span>\n <span class=\"ml-auto text-xs text-on-surface-variant\">Today</span>\n </div>\n <Separator color=\"success\" size=\"xs\" class=\"ml-1\" />\n <div class=\"flex items-center gap-3\">\n <div class=\"size-2 rounded-full bg-success\"></div>\n <span class=\"text-sm\">Processing</span>\n <span class=\"ml-auto text-xs text-on-surface-variant\">Today</span>\n </div>\n <Separator color=\"primary\" size=\"xs\" type=\"dashed\" class=\"ml-1\" />\n <div class=\"flex items-center gap-3\">\n <div class=\"size-2 rounded-full bg-outline-variant\"></div>\n <span class=\"text-sm text-on-surface-variant\">Shipped</span>\n <span class=\"ml-auto text-xs text-on-surface-variant\">Pending</span>\n </div>\n </div>\n </div>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Separator\n label=\"Custom label style\"\n color=\"primary\"\n ui={{ label: 'font-bold uppercase tracking-wider text-xs' }}\n />\n <Separator\n icon=\"lucide:sparkles\"\n color=\"warning\"\n size=\"md\"\n ui={{ icon: 'animate-pulse' }}\n />\n <Separator\n label=\"Gradient border\"\n ui={{\n border: 'bg-gradient-to-r from-primary via-secondary to-tertiary h-0.5 border-none'\n }}\n />\n </div>\n </section>\n\n <!-- Position -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Position</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">position</code> prop to\n place the label/icon/avatar at the start, center, or end. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">center</code>.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Separator label=\"Start\" position=\"start\" />\n <Separator label=\"Center\" position=\"center\" />\n <Separator label=\"End\" position=\"end\" />\n </div>\n </section>\n</div>\n",
|
|
97
118
|
"accordion": "<script lang=\"ts\">\n import { Accordion, Button, Badge, Icon, Separator } from '$lib/index.js'\n import type { AccordionItem } from '$lib/index.js'\n\n // --- Basic items ---\n const basicItems: AccordionItem[] = [\n {\n label: 'What is Svelte?',\n content:\n 'Svelte is a modern JavaScript compiler that generates highly efficient vanilla JavaScript at build time, resulting in faster runtime performance and smaller bundle sizes.'\n },\n {\n label: 'Why use SvelteKit?',\n content:\n 'SvelteKit is the official framework for building Svelte apps. It provides routing, server-side rendering, code splitting, and other production-ready features out of the box.'\n },\n {\n label: 'How does reactivity work?',\n content:\n 'Svelte uses a compile-time approach to reactivity. Variables declared with let are automatically reactive, and the compiler generates efficient update code.'\n }\n ]\n\n // --- Items with icons ---\n const iconItems: AccordionItem[] = [\n {\n label: 'Account Settings',\n icon: 'lucide:user',\n content:\n 'Manage your profile information, email preferences, and account security settings.'\n },\n {\n label: 'Notifications',\n icon: 'lucide:bell',\n content:\n 'Configure push notifications, email alerts, and in-app notification preferences.'\n },\n {\n label: 'Privacy & Security',\n icon: 'lucide:shield',\n content:\n 'Control your privacy settings, two-factor authentication, and data sharing preferences.'\n }\n ]\n\n // --- Disabled items ---\n const disabledItems: AccordionItem[] = [\n { label: 'Available Feature', content: 'This feature is available for all users.' },\n {\n label: 'Premium Feature',\n content: 'This feature requires a premium subscription.',\n disabled: true\n },\n { label: 'Another Feature', content: 'This feature is also available.' }\n ]\n\n // --- Custom trailing icons ---\n const trailingItems: AccordionItem[] = [\n { label: 'Add Item', content: 'Click to add a new item.', trailingIcon: 'lucide:plus' },\n { label: 'Edit Item', content: 'Click to edit this item.', trailingIcon: 'lucide:pencil' },\n {\n label: 'Delete Item',\n content: 'Click to delete this item.',\n trailingIcon: 'lucide:trash-2'\n }\n ]\n\n // --- Controlled value ---\n let singleValue = $state<string>('0')\n let multipleValue = $state<string[]>(['0', '2'])\n\n // --- Callback demo ---\n let lastChange = $state<string>('')\n\n // --- FAQ items ---\n const faqItems: AccordionItem[] = [\n {\n label: 'How do I reset my password?',\n content:\n 'Click on \"Forgot Password\" on the login page, enter your email address, and follow the instructions sent to your inbox.'\n },\n {\n label: 'Can I change my username?',\n content:\n 'Yes, you can change your username once every 30 days from Account Settings > Profile > Edit Username.'\n },\n {\n label: 'What payment methods are accepted?',\n content:\n 'We accept all major credit cards (Visa, MasterCard, American Express), PayPal, and bank transfers for annual plans.'\n },\n {\n label: 'How do I cancel my subscription?',\n content:\n 'Go to Account Settings > Billing > Cancel Subscription. Your access will continue until the end of your current billing period.'\n },\n {\n label: 'Is my data secure?',\n content:\n 'Yes, we use industry-standard encryption (AES-256) and follow SOC 2 compliance guidelines to protect your data.'\n }\n ]\n\n // --- Slot demo items ---\n const slotItems: AccordionItem[] = [\n {\n label: 'Pro Plan',\n content: 'Access all features with priority support.',\n value: 'pro'\n },\n {\n label: 'Enterprise Plan',\n content: 'Custom solutions with dedicated account manager.',\n value: 'enterprise'\n },\n {\n label: 'Free Plan',\n content: 'Basic features for personal use.',\n value: 'free'\n }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Accordion</h1>\n <p class=\"text-on-surface-variant\">\n A vertically stacked set of interactive headings that reveal or hide associated content.\n Built on top of bits-ui Accordion.\n </p>\n </div>\n\n <!-- ==================== BASIC ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an array of items with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">label</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">content</code> properties.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion items={basicItems} />\n </div>\n </section>\n\n <!-- ==================== SINGLE VS MULTIPLE ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Single vs Multiple</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >type=\"single\"</code\n >\n to allow only one item open at a time, or\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >type=\"multiple\"</code\n >\n for multiple.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Single (default)</p>\n <Accordion type=\"single\" items={basicItems} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Multiple</p>\n <Accordion type=\"multiple\" items={basicItems} />\n </div>\n </div>\n </section>\n\n <!-- ==================== ICONS ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Add leading icons via the <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code\n >\n property on each item.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion type=\"single\" items={iconItems} />\n </div>\n </section>\n\n <!-- ==================== TRAILING ICON ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Trailing Icon</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override the default chevron with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >trailingIcon</code\n >\n globally or per-item.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Global trailing icon</p>\n <Accordion type=\"single\" items={basicItems} trailingIcon=\"lucide:plus\" />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Per-item trailing icons</p>\n <Accordion type=\"single\" items={trailingItems} />\n </div>\n </div>\n </section>\n\n <!-- ==================== DISABLED ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled State</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Disable the entire accordion or individual items.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Entire accordion disabled</p>\n <Accordion type=\"single\" items={basicItems} disabled />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Individual item disabled</p>\n <Accordion type=\"single\" items={disabledItems} />\n </div>\n </div>\n </section>\n\n <!-- ==================== CONTROLLED ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Controlled Value</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Bind the <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >value</code\n >\n prop to control which items are open.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">\n Single mode: <Badge variant=\"soft\" color=\"info\" label=\"value={singleValue}\" />\n </p>\n <div class=\"mb-3 flex gap-2\">\n <Button\n size=\"xs\"\n variant={singleValue === '0' ? 'solid' : 'outline'}\n label=\"Item 1\"\n onclick={() => (singleValue = '0')}\n />\n <Button\n size=\"xs\"\n variant={singleValue === '1' ? 'solid' : 'outline'}\n label=\"Item 2\"\n onclick={() => (singleValue = '1')}\n />\n <Button\n size=\"xs\"\n variant={singleValue === '2' ? 'solid' : 'outline'}\n label=\"Item 3\"\n onclick={() => (singleValue = '2')}\n />\n </div>\n <Accordion type=\"single\" items={basicItems} bind:value={singleValue} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">\n Multiple mode: <Badge\n variant=\"soft\"\n color=\"info\"\n label=\"value=[{multipleValue.join(',')}]\"\n />\n </p>\n <div class=\"mb-3 flex gap-2\">\n <Button\n size=\"xs\"\n variant={multipleValue.includes('0') ? 'solid' : 'outline'}\n label=\"Item 1\"\n onclick={() =>\n (multipleValue = multipleValue.includes('0')\n ? multipleValue.filter((v) => v !== '0')\n : [...multipleValue, '0'])}\n />\n <Button\n size=\"xs\"\n variant={multipleValue.includes('1') ? 'solid' : 'outline'}\n label=\"Item 2\"\n onclick={() =>\n (multipleValue = multipleValue.includes('1')\n ? multipleValue.filter((v) => v !== '1')\n : [...multipleValue, '1'])}\n />\n <Button\n size=\"xs\"\n variant={multipleValue.includes('2') ? 'solid' : 'outline'}\n label=\"Item 3\"\n onclick={() =>\n (multipleValue = multipleValue.includes('2')\n ? multipleValue.filter((v) => v !== '2')\n : [...multipleValue, '2'])}\n />\n </div>\n <Accordion type=\"multiple\" items={basicItems} bind:value={multipleValue} />\n </div>\n </div>\n </section>\n\n <!-- ==================== CALLBACK ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Value Change Callback</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >onValueChange</code\n >\n to react to value changes.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm\">\n Last change: <Badge\n variant=\"soft\"\n color={lastChange ? 'success' : 'surface'}\n label={lastChange || 'None'}\n />\n </p>\n <Accordion\n type=\"single\"\n items={basicItems}\n onValueChange={(v) => (lastChange = v ? `Opened: ${v}` : 'Closed')}\n />\n </div>\n </section>\n\n <!-- ==================== CUSTOM VALUES ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Item Values</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Assign custom <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >value</code\n >\n to items instead of using index.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"single\"\n items={[\n { label: 'Introduction', content: 'Welcome to the guide.', value: 'intro' },\n {\n label: 'Getting Started',\n content: \"Let's begin with the basics.\",\n value: 'start'\n },\n {\n label: 'Advanced Topics',\n content: 'Deep dive into advanced features.',\n value: 'advanced'\n }\n ]}\n value=\"start\"\n />\n </div>\n </section>\n\n <!-- ==================== LOOP ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Keyboard Navigation (Loop)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">loop</code> to\n cycle keyboard focus at boundaries.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm text-on-surface-variant\">\n Use ↑↓ arrow keys to navigate. Focus cycles from last to first (and vice versa).\n </p>\n <Accordion type=\"single\" items={basicItems} loop />\n </div>\n </section>\n\n <!-- ==================== ORIENTATION ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >orientation</code\n >\n for keyboard navigation direction. Use ←→ for horizontal, ↑↓ for vertical.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Vertical (default) - ↑↓ keys</p>\n <Accordion type=\"single\" items={basicItems} orientation=\"vertical\" />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Horizontal - ←→ keys</p>\n <Accordion type=\"single\" items={basicItems} orientation=\"horizontal\" />\n </div>\n </div>\n </section>\n\n <!-- ==================== FORCE MOUNT ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Force Mount</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >forceMount</code\n >\n to keep content in the DOM even when collapsed. Useful for SEO or preserving form state.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Default (unmount on close)</p>\n <p class=\"mb-3 text-xs text-on-surface-variant\">\n Content is removed from DOM when closed\n </p>\n <Accordion type=\"single\" items={basicItems} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Force mount enabled</p>\n <p class=\"mb-3 text-xs text-on-surface-variant\">\n Content stays in DOM (inspect element)\n </p>\n <Accordion type=\"single\" items={basicItems} forceMount />\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- ==================== SLOTS ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use snippets for custom rendering: <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">leading</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">label</code>,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">trailing</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">content</code>,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">body</code>.\n </p>\n\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <!-- Leading slot -->\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom leading</p>\n <Accordion type=\"single\" items={slotItems}>\n {#snippet leading({ open })}\n <div\n class=\"flex size-8 items-center justify-center rounded-full {open\n ? 'bg-primary text-on-primary'\n : 'bg-surface-container-highest'}\"\n >\n <Icon name={open ? 'lucide:check' : 'lucide:star'} size=\"16\" />\n </div>\n {/snippet}\n </Accordion>\n </div>\n\n <!-- Label slot -->\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom label</p>\n <Accordion type=\"single\" items={slotItems}>\n {#snippet label({ item, open })}\n <span class=\"flex items-center gap-2\">\n {item.label}\n {#if item.value === 'pro'}\n <Badge size=\"xs\" variant=\"soft\" color=\"primary\" label=\"Popular\" />\n {/if}\n {#if open}\n <Icon name=\"lucide:eye\" size=\"14\" class=\"text-on-surface-variant\" />\n {/if}\n </span>\n {/snippet}\n </Accordion>\n </div>\n\n <!-- Trailing slot -->\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom trailing</p>\n <Accordion type=\"single\" items={slotItems}>\n {#snippet trailing({ item, open })}\n <div class=\"flex items-center gap-2\">\n {#if item.value === 'enterprise'}\n <Badge\n size=\"xs\"\n variant=\"outline\"\n color=\"warning\"\n label=\"Contact us\"\n />\n {/if}\n <Icon\n name={open ? 'lucide:minus' : 'lucide:plus'}\n size=\"18\"\n class=\"transition-transform\"\n />\n </div>\n {/snippet}\n </Accordion>\n </div>\n\n <!-- Body slot -->\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom body</p>\n <Accordion type=\"single\" items={slotItems}>\n {#snippet body({ item })}\n <div class=\"flex items-start gap-3\">\n <Icon\n name=\"lucide:info\"\n size=\"18\"\n class=\"mt-0.5 shrink-0 text-primary\"\n />\n <div>\n <p class=\"text-sm\">{item.content}</p>\n <Button\n size=\"xs\"\n variant=\"link\"\n label=\"Learn more\"\n class=\"mt-2 h-auto p-0\"\n />\n </div>\n </div>\n {/snippet}\n </Accordion>\n </div>\n </div>\n\n <!-- Content slot (full custom) -->\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom content (full control)</p>\n <Accordion type=\"single\" items={slotItems}>\n {#snippet content({ item })}\n <div class=\"rounded-lg bg-surface-container p-4\">\n <div class=\"flex items-center gap-3\">\n <div\n class=\"flex size-10 items-center justify-center rounded-full bg-primary/10 text-primary\"\n >\n <Icon name=\"lucide:package\" size=\"20\" />\n </div>\n <div class=\"flex-1\">\n <p class=\"font-medium\">{item.label}</p>\n <p class=\"text-sm text-on-surface-variant\">{item.content}</p>\n </div>\n <Button size=\"sm\" variant=\"solid\" label=\"Select\" />\n </div>\n </div>\n {/snippet}\n </Accordion>\n </div>\n </section>\n\n <Separator />\n\n <!-- ==================== UI OVERRIDES ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Prop (Style Overrides)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override slot styles via the <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code\n >\n prop. Available slots: root, item, header, trigger, content, body, label, leadingIcon, trailingIcon.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"single\"\n items={basicItems}\n ui={{\n root: 'divide-y divide-outline',\n item: 'border-none',\n trigger: 'py-4 hover:bg-surface-container rounded-lg px-3 -mx-3',\n label: 'text-primary font-semibold',\n body: 'px-3 text-on-surface-variant'\n }}\n />\n </div>\n </section>\n\n <!-- ==================== ITEM UI OVERRIDES ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Per-Item Style Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Each item can have its own <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">class</code> overrides.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"single\"\n items={[\n { label: 'Normal Item', content: 'Standard styling.' },\n {\n label: 'Highlighted Item',\n content: 'This item has custom styling.',\n class: 'bg-primary/5 rounded-lg',\n ui: { trigger: 'text-primary', label: 'font-bold' }\n },\n {\n label: 'Warning Item',\n content: 'This item indicates a warning.',\n class: 'bg-warning/5 rounded-lg',\n ui: { trigger: 'text-warning', trailingIcon: 'text-warning' }\n },\n { label: 'Another Normal', content: 'Standard styling again.' }\n ]}\n />\n </div>\n </section>\n\n <Separator />\n\n <!-- ==================== REAL WORLD EXAMPLE: FAQ ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Example: FAQ</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"mb-4 flex items-center gap-3\">\n <div class=\"flex size-10 items-center justify-center rounded-full bg-primary/10\">\n <Icon name=\"lucide:help-circle\" size=\"20\" class=\"text-primary\" />\n </div>\n <div>\n <h3 class=\"font-semibold text-on-surface\">Frequently Asked Questions</h3>\n <p class=\"text-sm text-on-surface-variant\">Find answers to common questions</p>\n </div>\n </div>\n <Accordion\n type=\"single\"\n items={faqItems}\n ui={{\n item: 'border-outline-variant/50',\n trigger: 'py-4',\n label: 'text-on-surface',\n body: 'text-on-surface-variant leading-relaxed'\n }}\n />\n </div>\n </section>\n\n <!-- ==================== REAL WORLD EXAMPLE: SETTINGS ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Example: Settings Sections</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"multiple\"\n items={[\n {\n label: 'General Settings',\n icon: 'lucide:settings-2',\n content: 'Configure language, timezone, and display preferences.',\n value: 'general'\n },\n {\n label: 'Notification Preferences',\n icon: 'lucide:bell-ring',\n content: 'Manage email, push, and SMS notification settings.',\n value: 'notifications'\n },\n {\n label: 'Security & Privacy',\n icon: 'lucide:lock',\n content: 'Set up two-factor authentication and manage privacy controls.',\n value: 'security'\n },\n {\n label: 'Billing & Subscription',\n icon: 'lucide:credit-card',\n content: 'View invoices, update payment methods, and manage your plan.',\n value: 'billing'\n }\n ]}\n value={['general']}\n ui={{\n item: 'bg-surface-container rounded-lg mb-2 last:mb-0 border-none overflow-hidden',\n header: 'bg-surface-container-low',\n trigger: 'px-4',\n body: 'px-4 bg-surface-container-lowest'\n }}\n />\n </div>\n </section>\n\n <!-- ==================== REAL WORLD EXAMPLE: SIDEBAR NAV ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Example: Sidebar Navigation</h2>\n <div class=\"max-w-xs rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"multiple\"\n items={[\n {\n label: 'Dashboard',\n icon: 'lucide:layout-dashboard',\n content: '',\n value: 'dashboard'\n },\n {\n label: 'Products',\n icon: 'lucide:package',\n content: '',\n value: 'products'\n },\n {\n label: 'Analytics',\n icon: 'lucide:bar-chart-3',\n content: '',\n value: 'analytics'\n }\n ]}\n value={['products']}\n trailingIcon=\"lucide:chevron-right\"\n ui={{\n root: 'space-y-1',\n item: 'border-none',\n trigger: 'py-2 px-3 rounded-lg hover:bg-surface-container',\n trailingIcon: 'group-data-[state=open]:rotate-90',\n content: 'pl-6'\n }}\n >\n {#snippet body({ item })}\n <div class=\"space-y-1 py-1\">\n {#if item.value === 'products'}\n <a\n href=\"#all\"\n class=\"block rounded-md px-3 py-1.5 text-sm hover:bg-surface-container\"\n >\n All Products\n </a>\n <a\n href=\"#categories\"\n class=\"block rounded-md px-3 py-1.5 text-sm hover:bg-surface-container\"\n >\n Categories\n </a>\n <a\n href=\"#inventory\"\n class=\"block rounded-md px-3 py-1.5 text-sm hover:bg-surface-container\"\n >\n Inventory\n </a>\n {:else if item.value === 'analytics'}\n <a\n href=\"#overview\"\n class=\"block rounded-md px-3 py-1.5 text-sm hover:bg-surface-container\"\n >\n Overview\n </a>\n <a\n href=\"#reports\"\n class=\"block rounded-md px-3 py-1.5 text-sm hover:bg-surface-container\"\n >\n Reports\n </a>\n {:else}\n <a\n href=\"#home\"\n class=\"block rounded-md px-3 py-1.5 text-sm hover:bg-surface-container\"\n >\n Home\n </a>\n {/if}\n </div>\n {/snippet}\n </Accordion>\n </div>\n </section>\n\n <!-- ==================== REAL WORLD EXAMPLE: FEATURE COMPARISON ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Example: Feature Comparison</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"single\"\n items={[\n { label: 'Storage', value: 'storage' },\n { label: 'Users', value: 'users' },\n { label: 'Support', value: 'support' }\n ]}\n ui={{\n item: 'border-outline-variant/30',\n trigger: 'py-3'\n }}\n >\n {#snippet leading({ open })}\n <div\n class=\"flex size-6 items-center justify-center rounded {open\n ? 'bg-primary text-on-primary'\n : 'bg-surface-container-highest'}\"\n >\n <Icon name={open ? 'lucide:minus' : 'lucide:plus'} size=\"14\" />\n </div>\n {/snippet}\n {#snippet trailing()}\n <div class=\"flex gap-8 text-center text-xs\">\n <div class=\"w-16\">\n <p class=\"font-medium\">Free</p>\n </div>\n <div class=\"w-16\">\n <p class=\"font-medium text-primary\">Pro</p>\n </div>\n <div class=\"w-16\">\n <p class=\"font-medium\">Enterprise</p>\n </div>\n </div>\n {/snippet}\n {#snippet body({ item })}\n <div class=\"flex justify-end gap-8 text-center text-sm\">\n {#if item.value === 'storage'}\n <div class=\"w-16\">5 GB</div>\n <div class=\"w-16 font-medium text-primary\">100 GB</div>\n <div class=\"w-16\">Unlimited</div>\n {:else if item.value === 'users'}\n <div class=\"w-16\">1</div>\n <div class=\"w-16 font-medium text-primary\">10</div>\n <div class=\"w-16\">Unlimited</div>\n {:else}\n <div class=\"w-16\">Email</div>\n <div class=\"w-16 font-medium text-primary\">Priority</div>\n <div class=\"w-16\">24/7 Phone</div>\n {/if}\n </div>\n {/snippet}\n </Accordion>\n </div>\n </section>\n\n <!-- ==================== ADVANCED: UNIQUE UI PER ITEM ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Advanced: Unique UI Per Item</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >body</code\n >\n or\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">content</code> snippet\n with conditional rendering to create completely different layouts for each item.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"single\"\n items={[\n { label: 'User Profile', icon: 'lucide:user', value: 'profile' },\n { label: 'Statistics', icon: 'lucide:bar-chart-2', value: 'stats' },\n { label: 'Recent Activity', icon: 'lucide:activity', value: 'activity' },\n { label: 'Quick Actions', icon: 'lucide:zap', value: 'actions' }\n ]}\n ui={{\n item: 'border-outline-variant/30',\n trigger: 'py-3'\n }}\n >\n {#snippet body({ item })}\n {#if item.value === 'profile'}\n <!-- Profile Card UI -->\n <div class=\"flex items-center gap-4 rounded-lg bg-surface-container p-4\">\n <div\n class=\"flex size-16 items-center justify-center rounded-full bg-primary/10\"\n >\n <Icon name=\"lucide:user\" size=\"32\" class=\"text-primary\" />\n </div>\n <div class=\"flex-1\">\n <h4 class=\"font-semibold\">John Doe</h4>\n <p class=\"text-sm text-on-surface-variant\">john.doe@example.com</p>\n <div class=\"mt-2 flex gap-2\">\n <Badge\n size=\"xs\"\n variant=\"soft\"\n color=\"success\"\n label=\"Active\"\n />\n <Badge\n size=\"xs\"\n variant=\"outline\"\n color=\"primary\"\n label=\"Pro Plan\"\n />\n </div>\n </div>\n <Button\n size=\"sm\"\n variant=\"outline\"\n leadingIcon=\"lucide:pencil\"\n label=\"Edit\"\n />\n </div>\n {:else if item.value === 'stats'}\n <!-- Statistics Grid UI -->\n <div class=\"grid grid-cols-3 gap-3\">\n <div class=\"rounded-lg bg-surface-container p-3 text-center\">\n <p class=\"text-2xl font-bold text-primary\">1,234</p>\n <p class=\"text-xs text-on-surface-variant\">Total Views</p>\n </div>\n <div class=\"rounded-lg bg-surface-container p-3 text-center\">\n <p class=\"text-2xl font-bold text-success\">89%</p>\n <p class=\"text-xs text-on-surface-variant\">Success Rate</p>\n </div>\n <div class=\"rounded-lg bg-surface-container p-3 text-center\">\n <p class=\"text-2xl font-bold text-warning\">42</p>\n <p class=\"text-xs text-on-surface-variant\">Pending</p>\n </div>\n </div>\n {:else if item.value === 'activity'}\n <!-- Activity Timeline UI -->\n <div class=\"space-y-3\">\n {#each [{ time: '2 min ago', text: 'Updated profile picture', icon: 'lucide:image', color: 'text-primary' }, { time: '1 hour ago', text: 'Completed task #123', icon: 'lucide:check-circle', color: 'text-success' }, { time: '3 hours ago', text: 'Added new comment', icon: 'lucide:message-circle', color: 'text-info' }] as activity (activity.text)}\n <div class=\"flex items-center gap-3\">\n <div\n class=\"flex size-8 shrink-0 items-center justify-center rounded-full bg-surface-container\"\n >\n <Icon\n name={activity.icon}\n size=\"16\"\n class={activity.color}\n />\n </div>\n <div class=\"flex-1\">\n <p class=\"text-sm\">{activity.text}</p>\n <p class=\"text-xs text-on-surface-variant\">\n {activity.time}\n </p>\n </div>\n </div>\n {/each}\n </div>\n {:else if item.value === 'actions'}\n <!-- Quick Actions Grid UI -->\n <div class=\"grid grid-cols-2 gap-2\">\n <Button\n variant=\"outline\"\n leadingIcon=\"lucide:plus\"\n label=\"New Item\"\n class=\"justify-start\"\n />\n <Button\n variant=\"outline\"\n leadingIcon=\"lucide:upload\"\n label=\"Upload\"\n class=\"justify-start\"\n />\n <Button\n variant=\"outline\"\n leadingIcon=\"lucide:download\"\n label=\"Export\"\n class=\"justify-start\"\n />\n <Button\n variant=\"outline\"\n leadingIcon=\"lucide:share-2\"\n label=\"Share\"\n class=\"justify-start\"\n />\n </div>\n {/if}\n {/snippet}\n </Accordion>\n </div>\n </section>\n\n <!-- ==================== ADVANCED: MIXED CONTENT TYPES ==================== -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Advanced: Mixed Content Types</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Combine different content types like forms, images, code blocks, and interactive\n elements.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Accordion\n type=\"multiple\"\n items={[\n { label: 'Contact Form', icon: 'lucide:mail', value: 'form' },\n { label: 'Image Gallery', icon: 'lucide:image', value: 'gallery' },\n { label: 'Code Example', icon: 'lucide:code', value: 'code' }\n ]}\n value={['form']}\n ui={{\n item: 'bg-surface-container rounded-lg mb-2 last:mb-0 border-none overflow-hidden',\n trigger: 'px-4 py-3',\n body: 'px-4 pb-4'\n }}\n >\n {#snippet body({ item })}\n {#if item.value === 'form'}\n <!-- Form UI -->\n <div class=\"space-y-3\">\n <div class=\"grid gap-3 sm:grid-cols-2\">\n <label class=\"block\">\n <span class=\"mb-1 block text-xs font-medium\">Name</span>\n <input\n type=\"text\"\n placeholder=\"Your name\"\n class=\"w-full rounded-lg border border-outline-variant bg-surface px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n />\n </label>\n <label class=\"block\">\n <span class=\"mb-1 block text-xs font-medium\">Email</span>\n <input\n type=\"email\"\n placeholder=\"your@email.com\"\n class=\"w-full rounded-lg border border-outline-variant bg-surface px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n />\n </label>\n </div>\n <label class=\"block\">\n <span class=\"mb-1 block text-xs font-medium\">Message</span>\n <textarea\n placeholder=\"Your message...\"\n rows=\"3\"\n class=\"w-full rounded-lg border border-outline-variant bg-surface px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n ></textarea>\n </label>\n <Button\n variant=\"solid\"\n label=\"Send Message\"\n leadingIcon=\"lucide:send\"\n />\n </div>\n {:else if item.value === 'gallery'}\n <!-- Image Gallery UI -->\n <div class=\"grid grid-cols-3 gap-2\">\n {#each Array.from({ length: 6 }, (_, i) => i) as i (i)}\n <div\n class=\"flex aspect-square items-center justify-center rounded-lg bg-linear-to-br from-primary/20 to-secondary/20\"\n >\n <Icon\n name=\"lucide:image\"\n size=\"24\"\n class=\"text-on-surface-variant/50\"\n />\n </div>\n {/each}\n </div>\n <p class=\"mt-2 text-center text-xs text-on-surface-variant\">\n Click to view full size\n </p>\n {:else if item.value === 'code'}\n <!-- Code Block UI -->\n <div class=\"overflow-hidden rounded-lg bg-surface-container-highest\">\n <div\n class=\"flex items-center justify-between border-b border-outline-variant/30 px-3 py-2\"\n >\n <span class=\"text-xs text-on-surface-variant\">accordion.svelte</span\n >\n <Button\n size=\"xs\"\n variant=\"ghost\"\n leadingIcon=\"lucide:copy\"\n label=\"Copy\"\n />\n </div>\n <pre class=\"overflow-x-auto p-3 text-xs\"><code\n class=\"text-on-surface-variant\"\n ><Accordion\n type=\"single\"\n items={items}\n bind:value\n>\n {#snippet body({ item })}\n <p>{item.content}</p>\n {/snippet}\n</Accordion></code\n ></pre>\n </div>\n {/if}\n {/snippet}\n </Accordion>\n </div>\n </section>\n</div>\n",
|
|
119
|
+
"chart": "<script lang=\"ts\">\n import { Chart } from '$lib/index.js'\n \n // Sample data for various charts\n const barData = {\n labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],\n datasets: [{\n label: 'Sales ($)',\n data: [12000, 19000, 15000, 22000, 18000, 25000],\n backgroundColor: 'rgba(59, 130, 246, 0.5)',\n borderColor: 'rgb(59, 130, 246)',\n borderWidth: 1\n }]\n }\n\n const lineData = {\n labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],\n datasets: [{\n label: 'Active Users',\n data: [65, 59, 80, 81, 56, 55, 40],\n fill: false,\n borderColor: 'rgb(16, 185, 129)',\n tension: 0.1\n }]\n }\n\n const pieData = {\n labels: ['Desktop', 'Mobile', 'Tablet'],\n datasets: [{\n data: [300, 500, 100],\n backgroundColor: [\n 'rgb(59, 130, 246)',\n 'rgb(16, 185, 129)',\n 'rgb(245, 158, 11)'\n ],\n hoverOffset: 4\n }]\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Chart</h1>\n <p class=\"text-on-surface-variant\">\n A flexible wrapper around <code class=\"rounded bg-surface-container-highest px-1\">chart.js</code> to render beautiful, responsive charts.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass the <code class=\"rounded bg-surface-container-highest px-1\">type</code> prop (e.g. <code class=\"rounded bg-surface-container-highest px-1\">bar</code>, <code class=\"rounded bg-surface-container-highest px-1\">line</code>, <code class=\"rounded bg-surface-container-highest px-1\">pie</code>) and the standard Chart.js <code class=\"rounded bg-surface-container-highest px-1\">data</code> object.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"h-[300px] w-full\">\n <Chart type=\"bar\" data={barData} />\n </div>\n </div>\n </section>\n\n <!-- Line Chart -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Line Chart</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1\">type=\"line\"</code> to render a line chart.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"h-[300px] w-full\">\n <Chart type=\"line\" data={lineData} />\n </div>\n </div>\n </section>\n\n <!-- Pie Chart -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Pie Chart</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1\">type=\"pie\"</code> to render a pie chart. Ensure the parent container has adequate width/height constraints.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"h-[300px] w-full max-w-sm\">\n <Chart type=\"pie\" data={pieData} />\n </div>\n </div>\n </section>\n</div>\n",
|
|
98
120
|
"avatar": "<script lang=\"ts\">\n import { Avatar, AvatarGroup } from '$lib/index.js'\n\n const sizes = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const\n\n const groupAvatars = [\n { src: 'https://i.pravatar.cc/150?img=20', alt: 'User 1' },\n { src: 'https://i.pravatar.cc/150?img=21', alt: 'User 2' },\n { src: 'https://i.pravatar.cc/150?img=22', alt: 'User 3' },\n { src: 'https://i.pravatar.cc/150?img=23', alt: 'User 4' },\n { src: 'https://i.pravatar.cc/150?img=24', alt: 'User 5' },\n { src: 'https://i.pravatar.cc/150?img=25', alt: 'User 6' },\n { src: 'https://i.pravatar.cc/150?img=26', alt: 'User 7' }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Avatar</h1>\n <p class=\"text-on-surface-variant\">\n Display user profile images with fallback to initials, icons, or custom content.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Avatar src=\"https://i.pravatar.cc/150?img=1\" alt=\"John Doe\" />\n <Avatar src=\"https://i.pravatar.cc/150?img=2\" alt=\"Jane Smith\" />\n <Avatar src=\"https://i.pravatar.cc/150?img=3\" alt=\"Bob Wilson\" />\n <Avatar alt=\"No Image\" />\n <Avatar text=\"AB\" />\n <Avatar icon=\"lucide:user\" />\n </div>\n </section>\n\n <!-- Size -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Size</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">size</code> prop to\n control dimensions. Available sizes:\n <code class=\"rounded bg-surface-container-highest px-1\">3xs</code>\n to <code class=\"rounded bg-surface-container-highest px-1\">3xl</code>.\n </p>\n <div class=\"flex flex-wrap items-end gap-4 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <Avatar src=\"https://i.pravatar.cc/150?img=5\" alt=\"User\" {size} />\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Rounded -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Rounded</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">rounded</code> prop to\n control the border radius. Use\n <code class=\"rounded bg-surface-container-highest px-1\">lg</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">md</code>, or\n <code class=\"rounded bg-surface-container-highest px-1\">none</code> for square-ish avatars\n (e.g. organization logos).\n </p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each ['full', 'lg', 'md', 'sm', 'none'] as r (r)}\n <div class=\"flex flex-col items-center gap-2\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=8\"\n alt=\"User\"\n size=\"xl\"\n rounded={r as 'full' | 'lg' | 'md' | 'sm' | 'none'}\n />\n <span class=\"text-xs text-on-surface-variant\">{r}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Fallback Initials -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Fallback Initials</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Auto-generates initials from <code class=\"rounded bg-surface-container-highest px-1\"\n >alt</code\n >\n text when no image. Use\n <code class=\"rounded bg-surface-container-highest px-1\">text</code> to override.\n </p>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Avatar alt=\"John Doe\" size=\"lg\" />\n <Avatar alt=\"Jane Smith\" size=\"lg\" />\n <Avatar alt=\"Bob\" size=\"lg\" />\n <Avatar alt=\"Alice Brown Carter\" size=\"lg\" />\n <Avatar text=\"XY\" size=\"lg\" />\n </div>\n </section>\n\n <!-- Icon Fallback -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Icon Fallback</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">icon</code> prop to display\n an Iconify icon as fallback when no image or text is available.\n </p>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Avatar icon=\"lucide:user\" size=\"lg\" />\n <Avatar icon=\"lucide:users\" size=\"lg\" />\n <Avatar icon=\"lucide:building-2\" size=\"lg\" rounded=\"lg\" />\n <Avatar\n icon=\"lucide:bot\"\n size=\"lg\"\n class=\"bg-primary\"\n ui={{ icon: 'text-on-primary' }}\n />\n </div>\n </section>\n\n <!-- Chip -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Chip</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">chip</code> prop to add\n a status indicator. Pass\n <code class=\"rounded bg-surface-container-highest px-1\">true</code> for default or an object\n to customize.\n </p>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Default</p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <Avatar src=\"https://i.pravatar.cc/150?img=10\" alt=\"User\" size=\"lg\" chip />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Color</p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each ['primary', 'secondary', 'success', 'warning', 'error', 'info', 'surface'] as color (color)}\n <div class=\"flex flex-col items-center gap-2\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=12\"\n alt={color}\n size=\"lg\"\n chip={{ color: color as 'primary' }}\n />\n <span class=\"text-xs text-on-surface-variant\">{color}</span>\n </div>\n {/each}\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Position</p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each ['top-right', 'top-left', 'bottom-right', 'bottom-left'] as pos (pos)}\n <div class=\"flex flex-col items-center gap-2\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=16\"\n alt=\"User\"\n size=\"xl\"\n chip={{ color: 'success', position: pos as 'top-right' }}\n />\n <span class=\"text-xs text-on-surface-variant\">{pos}</span>\n </div>\n {/each}\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">With text</p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=18\"\n alt=\"User\"\n size=\"3xl\"\n chip={{ color: 'error', text: 3, size: '3xl' }}\n />\n <Avatar\n src=\"https://i.pravatar.cc/150?img=19\"\n alt=\"User\"\n size=\"3xl\"\n chip={{ color: 'success', text: '99+', size: '3xl' }}\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Show / Hide</p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=20\"\n alt=\"Visible\"\n size=\"lg\"\n chip={{ color: 'success', show: true }}\n />\n <span class=\"text-xs text-on-surface-variant\">show: true</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=21\"\n alt=\"Hidden\"\n size=\"lg\"\n chip={{ color: 'success', show: false }}\n />\n <span class=\"text-xs text-on-surface-variant\">show: false</span>\n </div>\n </div>\n </section>\n\n <!-- Avatar Group -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Avatar Group</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">AvatarGroup</code> to display\n multiple avatars with overlap.\n </p>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Usage</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup avatars={groupAvatars.slice(0, 5)} />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Max</p>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">max</code> to limit visible avatars.\n Excess is shown as a \"+N\" indicator.\n </p>\n <div class=\"flex flex-col gap-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col gap-1\">\n <span class=\"text-xs text-on-surface-variant\">max=3</span>\n <AvatarGroup avatars={groupAvatars} max={3} />\n </div>\n <div class=\"flex flex-col gap-1\">\n <span class=\"text-xs text-on-surface-variant\">max=5</span>\n <AvatarGroup avatars={groupAvatars} max={5} />\n </div>\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Size</p>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">size</code> to control dimensions\n of all avatars in the group.\n </p>\n <div class=\"flex flex-col gap-4 rounded-lg bg-surface-container-high p-4\">\n {#each ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as s (s)}\n <div class=\"flex flex-col gap-1\">\n <span class=\"text-xs text-on-surface-variant\">{s}</span>\n <AvatarGroup avatars={groupAvatars.slice(0, 4)} size={s as 'md'} />\n </div>\n {/each}\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Rounded</p>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">rounded</code> to control the\n border radius of all avatars.\n </p>\n <div class=\"flex flex-col gap-4 rounded-lg bg-surface-container-high p-4\">\n {#each ['full', 'lg', 'md', 'sm', 'none'] as r (r)}\n <div class=\"flex flex-col gap-1\">\n <span class=\"text-xs text-on-surface-variant\">{r}</span>\n <AvatarGroup\n avatars={groupAvatars.slice(0, 4)}\n size=\"xl\"\n rounded={r as 'full'}\n />\n </div>\n {/each}\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">With initials fallback</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup\n avatars={[\n { alt: 'Alice Johnson' },\n { alt: 'Bob Williams' },\n { alt: 'Carol Davis' },\n { alt: 'David Lee' },\n { alt: 'Emma Wilson' }\n ]}\n max={3}\n size=\"lg\"\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">With chip</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup\n avatars={[\n {\n src: 'https://i.pravatar.cc/150?img=40',\n alt: 'Online',\n chip: { color: 'success' }\n },\n {\n src: 'https://i.pravatar.cc/150?img=41',\n alt: 'Away',\n chip: { color: 'warning' }\n },\n {\n src: 'https://i.pravatar.cc/150?img=42',\n alt: 'Offline',\n chip: { color: 'error' }\n },\n { src: 'https://i.pravatar.cc/150?img=43', alt: 'User 4' },\n { src: 'https://i.pravatar.cc/150?img=44', alt: 'User 5' }\n ]}\n max={4}\n size=\"lg\"\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Custom styling</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup\n avatars={groupAvatars.slice(0, 5)}\n max={3}\n size=\"xl\"\n class=\"gap-1\"\n ui={{ base: 'ring-primary' }}\n />\n </div>\n </section>\n\n <!-- Custom Styling -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Styling</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">class</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">ui</code> to customize individual\n slots.\n </p>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=10\"\n alt=\"User\"\n size=\"xl\"\n class=\"ring-2 ring-primary ring-offset-2 ring-offset-surface\"\n />\n <Avatar\n src=\"https://i.pravatar.cc/150?img=11\"\n alt=\"User\"\n size=\"xl\"\n class=\"ring-2 ring-success\"\n />\n <Avatar alt=\"VIP\" size=\"xl\" class=\"bg-primary\" ui={{ fallback: 'text-on-primary' }} />\n <Avatar\n alt=\"Pro\"\n size=\"xl\"\n class=\"bg-linear-to-br from-primary to-tertiary\"\n ui={{ fallback: 'text-white font-bold' }}\n />\n </div>\n </section>\n\n <!-- Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Examples</h2>\n\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">\n User list with status\n </p>\n <div class=\"flex flex-col gap-3\">\n {#each [{ name: 'Alice Johnson', color: 'success' as const, img: 1 }, { name: 'Bob Williams', color: 'error' as const, img: 2 }, { name: 'Carol Davis', color: 'warning' as const, img: 3 }] as user (user.name)}\n <div class=\"flex items-center gap-3\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img={user.img}\"\n alt={user.name}\n chip={{ color: user.color }}\n />\n <div>\n <p class=\"text-sm font-medium\">{user.name}</p>\n <p class=\"text-xs text-on-surface-variant\">Member</p>\n </div>\n </div>\n {/each}\n </div>\n </div>\n\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Organization logos</p>\n <div class=\"flex flex-wrap items-center gap-4\">\n <Avatar\n icon=\"lucide:building-2\"\n size=\"xl\"\n rounded=\"lg\"\n class=\"bg-primary\"\n ui={{ icon: 'text-on-primary' }}\n />\n <Avatar\n icon=\"lucide:code-2\"\n size=\"xl\"\n rounded=\"md\"\n class=\"bg-secondary\"\n ui={{ icon: 'text-on-secondary' }}\n />\n <Avatar\n icon=\"lucide:globe\"\n size=\"xl\"\n rounded=\"lg\"\n class=\"bg-tertiary\"\n ui={{ icon: 'text-on-tertiary' }}\n />\n </div>\n </div>\n </section>\n</div>\n",
|
|
99
121
|
"avatar-group": "<script lang=\"ts\">\n import { Avatar, AvatarGroup } from '$lib/index.js'\n\n const sizes = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Avatar Group</h1>\n <p class=\"text-on-surface-variant\">\n Display a stack of overlapping avatars with optional overflow indicator.\n </p>\n </div>\n\n <!-- Basic Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic Usage</h2>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup\n avatars={[\n { src: 'https://i.pravatar.cc/150?img=1', alt: 'User 1' },\n { src: 'https://i.pravatar.cc/150?img=2', alt: 'User 2' },\n { src: 'https://i.pravatar.cc/150?img=3', alt: 'User 3' },\n { src: 'https://i.pravatar.cc/150?img=4', alt: 'User 4' }\n ]}\n />\n </div>\n </section>\n\n <!-- Max Limit -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Max Limit</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">max</code> to limit visible avatars.\n Excess shown as \"+N\".\n </p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup\n max={3}\n avatars={[\n { src: 'https://i.pravatar.cc/150?img=1', alt: 'User 1' },\n { src: 'https://i.pravatar.cc/150?img=2', alt: 'User 2' },\n { src: 'https://i.pravatar.cc/150?img=3', alt: 'User 3' },\n { src: 'https://i.pravatar.cc/150?img=4', alt: 'User 4' },\n { src: 'https://i.pravatar.cc/150?img=5', alt: 'User 5' },\n { src: 'https://i.pravatar.cc/150?img=6', alt: 'User 6' }\n ]}\n />\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"flex flex-wrap items-end gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <AvatarGroup\n {size}\n avatars={[\n { src: 'https://i.pravatar.cc/150?img=1', alt: 'User 1' },\n { src: 'https://i.pravatar.cc/150?img=2', alt: 'User 2' },\n { src: 'https://i.pravatar.cc/150?img=3', alt: 'User 3' }\n ]}\n />\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- With Children -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Children</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use slot content for full control over child avatars.\n </p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <AvatarGroup size=\"lg\">\n <Avatar src=\"https://i.pravatar.cc/150?img=10\" alt=\"User A\" />\n <Avatar src=\"https://i.pravatar.cc/150?img=11\" alt=\"User B\" />\n <Avatar alt=\"New\" />\n </AvatarGroup>\n </div>\n </section>\n</div>\n",
|
|
100
122
|
"badge": "<script lang=\"ts\">\n import { Badge } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const variants = ['solid', 'outline', 'soft', 'subtle'] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Badge</h1>\n <p class=\"text-on-surface-variant\">\n Small status indicator for labeling and categorization.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"Badge\" />\n <Badge label=\"New\" color=\"success\" />\n <Badge label=\"Warning\" color=\"warning\" />\n <Badge label=\"Error\" color=\"error\" />\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n {#each variants as variant (variant)}\n <div class=\"flex flex-wrap items-center gap-2\">\n <span class=\"w-16 text-sm text-on-surface-variant\">{variant}</span>\n {#each colors as color (color)}\n <Badge label={color} {variant} {color} />\n {/each}\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <Badge label={size} {size} />\n {/each}\n </div>\n </section>\n\n <!-- With Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Add leading or trailing icons alongside the label.\n </p>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"Star\" leadingIcon=\"lucide:star\" color=\"warning\" />\n <Badge label=\"Check\" leadingIcon=\"lucide:check\" color=\"success\" />\n <Badge label=\"Close\" trailingIcon=\"lucide:x\" color=\"error\" />\n <Badge\n label=\"Info\"\n leadingIcon=\"lucide:info\"\n trailingIcon=\"lucide:chevron-right\"\n color=\"info\"\n />\n </div>\n </section>\n\n <!-- Icon Only -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Icon Only</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the icon prop for square icon-only badges.\n </p>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <Badge icon=\"lucide:star\" color=\"warning\" {size} />\n {/each}\n </div>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge icon=\"lucide:check\" color=\"success\" />\n <Badge icon=\"lucide:x\" color=\"error\" />\n <Badge icon=\"lucide:heart\" color=\"error\" variant=\"soft\" />\n <Badge icon=\"lucide:bell\" color=\"info\" variant=\"outline\" />\n <Badge icon=\"lucide:lock\" color=\"surface\" variant=\"subtle\" />\n </div>\n </section>\n\n <!-- With Avatar -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Avatar</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Display an avatar on the leading side of the badge.\n </p>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"John\" avatar={{ alt: 'John', src: 'https://i.pravatar.cc/32?u=1' }} />\n <Badge\n label=\"Jane\"\n avatar={{ alt: 'Jane', src: 'https://i.pravatar.cc/32?u=2' }}\n color=\"secondary\"\n />\n <Badge\n label=\"Admin\"\n avatar={{ alt: 'Admin' }}\n color=\"tertiary\"\n variant=\"soft\"\n size=\"lg\"\n />\n </div>\n </section>\n\n <!-- Children Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Children Slot</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the default snippet for custom content instead of the label prop.\n </p>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge color=\"success\" variant=\"soft\">\n <span class=\"flex items-center gap-1\">\n <span class=\"size-1.5 rounded-full bg-success\"></span>\n Online\n </span>\n </Badge>\n <Badge color=\"error\" variant=\"soft\">\n <span class=\"flex items-center gap-1\">\n <span class=\"size-1.5 rounded-full bg-error\"></span>\n Offline\n </span>\n </Badge>\n <Badge color=\"warning\" variant=\"outline\">\n <span class=\"font-mono\">v2.0.0-beta</span>\n </Badge>\n </div>\n </section>\n\n <!-- Custom Leading/Trailing Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override leading and trailing content with custom snippets.\n </p>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"Custom Leading\" color=\"tertiary\" variant=\"soft\">\n {#snippet leading()}\n <span class=\"size-2 rounded-full bg-tertiary\"></span>\n {/snippet}\n </Badge>\n <Badge label=\"Custom Trailing\" color=\"info\" variant=\"outline\">\n {#snippet trailing()}\n <span\n class=\"flex size-4 items-center justify-center rounded-full bg-info text-[8px] text-on-info\"\n >\n 3\n </span>\n {/snippet}\n </Badge>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"flex flex-wrap gap-2 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"Rounded Full\" ui={{ base: 'rounded-full px-3' }} />\n <Badge\n label=\"Gradient\"\n ui={{\n base: 'bg-gradient-to-r from-primary to-tertiary text-on-primary rounded-full px-3'\n }}\n />\n <Badge\n label=\"BOLD\"\n ui={{ label: 'font-bold uppercase tracking-wider' }}\n color=\"error\"\n variant=\"outline\"\n />\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Status Labels -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Status Labels</p>\n <div class=\"flex flex-wrap gap-2\">\n <Badge label=\"Active\" color=\"success\" leadingIcon=\"lucide:circle-check\" />\n <Badge label=\"Pending\" color=\"warning\" leadingIcon=\"lucide:clock\" />\n <Badge label=\"Inactive\" color=\"error\" leadingIcon=\"lucide:circle-x\" />\n <Badge label=\"Draft\" color=\"surface\" leadingIcon=\"lucide:file-edit\" />\n </div>\n </div>\n\n <!-- Tags -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Tags</p>\n <div class=\"flex flex-wrap gap-2\">\n <Badge label=\"Svelte\" variant=\"soft\" color=\"tertiary\" />\n <Badge label=\"TypeScript\" variant=\"soft\" color=\"info\" />\n <Badge label=\"Tailwind\" variant=\"soft\" color=\"primary\" />\n <Badge label=\"Vite\" variant=\"soft\" color=\"warning\" />\n </div>\n </div>\n\n <!-- Notification Count -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Counts</p>\n <div class=\"flex flex-wrap gap-3\">\n <Badge label={3} color=\"primary\" square />\n <Badge label={12} color=\"error\" square />\n <Badge label=\"99+\" color=\"info\" size=\"sm\" />\n <Badge label=\"NEW\" color=\"success\" size=\"xs\" />\n </div>\n </div>\n\n <!-- Card with Badge -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">In Context</p>\n <div\n class=\"max-w-sm rounded-lg border border-outline-variant bg-surface-container p-4\"\n >\n <div class=\"mb-2 flex items-center justify-between\">\n <h3 class=\"font-medium\">Feature Request</h3>\n <Badge label=\"Open\" color=\"success\" variant=\"soft\" size=\"sm\" />\n </div>\n <p class=\"mb-3 text-sm text-on-surface-variant\">\n Add dark mode toggle to the settings panel.\n </p>\n <div class=\"flex gap-1.5\">\n <Badge label=\"enhancement\" variant=\"outline\" color=\"tertiary\" size=\"xs\" />\n <Badge label=\"ui\" variant=\"outline\" color=\"info\" size=\"xs\" />\n <Badge label=\"p2\" variant=\"outline\" color=\"warning\" size=\"xs\" />\n </div>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Variants x Colors Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants x Colors</h2>\n <div class=\"overflow-x-auto rounded-lg bg-surface-container-high p-4\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-3 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Variant</th\n >\n {#each colors as color (color)}\n <th\n class=\"px-3 py-3 text-center text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each variants as variant (variant)}\n <tr class=\"border-b border-outline-variant/50\">\n <td class=\"px-3 py-3 text-sm font-medium text-on-surface-variant\"\n >{variant}</td\n >\n {#each colors as color (color)}\n <td class=\"px-3 py-3 text-center\">\n <Badge label={color} {variant} {color} />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n</div>\n",
|
|
101
123
|
"carousel": "<script lang=\"ts\">\n import { Carousel, Button, Icon, Badge, Link } from '$lib/index.js'\n import type { CarouselApi } from '$lib/index.js'\n\n const colors = ['primary', 'secondary', 'tertiary', 'success', 'warning', 'error'] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n const fruits = [\n { id: 1, label: 'Apple', emoji: '🍎', from: 'from-rose-400', to: 'to-rose-600' },\n { id: 2, label: 'Banana', emoji: '🍌', from: 'from-amber-300', to: 'to-yellow-500' },\n { id: 3, label: 'Cherry', emoji: '🍒', from: 'from-pink-400', to: 'to-red-600' },\n { id: 4, label: 'Grape', emoji: '🍇', from: 'from-purple-400', to: 'to-violet-600' },\n { id: 5, label: 'Orange', emoji: '🍊', from: 'from-orange-300', to: 'to-orange-500' },\n { id: 6, label: 'Peach', emoji: '🍑', from: 'from-pink-300', to: 'to-orange-300' },\n { id: 7, label: 'Pear', emoji: '🍐', from: 'from-lime-300', to: 'to-green-500' },\n { id: 8, label: 'Pineapple', emoji: '🍍', from: 'from-yellow-300', to: 'to-amber-500' }\n ]\n\n const photoTitles = [\n 'Sunset Ridge',\n 'Quiet Harbor',\n 'Open Road',\n 'City Lights',\n 'Misty Pines',\n 'Coastal Drift',\n 'Golden Fields',\n 'Riverside Bend',\n 'Alpine Trail',\n 'Bamboo Grove',\n 'Northern Sky',\n 'Stone Path'\n ]\n const photos = Array.from({ length: 12 }, (_, i) => ({\n id: i,\n src: `https://picsum.photos/seed/svelora-carousel-${i}/800/450`,\n alt: `Photo ${i + 1}`,\n title: photoTitles[i]\n }))\n\n const features = [\n { icon: 'lucide:zap', label: 'Tree-shakable plugins', desc: 'Only ship what you use.' },\n { icon: 'lucide:hand', label: 'Touch & mouse drag', desc: 'Native-feeling gestures.' },\n { icon: 'lucide:repeat', label: 'Looping & autoplay', desc: 'With pause-on-hover.' },\n {\n icon: 'lucide:layout-grid',\n label: 'Multi-slide & responsive',\n desc: 'Breakpoint overrides.'\n },\n {\n icon: 'lucide:arrow-down-up',\n label: 'Horizontal & vertical',\n desc: 'Same API, both axes.'\n },\n { icon: 'lucide:sparkles', label: 'Fade & class plugins', desc: 'Custom transitions.' }\n ]\n\n let controlledIndex = $state(0)\n let controlledApi = $state<CarouselApi | undefined>()\n let autoplayApi = $state<CarouselApi | undefined>()\n let autoplayPlaying = $state(true)\n\n let galleryIndex = $state(0)\n let galleryThumbsApi = $state<CarouselApi | undefined>()\n\n let vGalleryIndex = $state(0)\n let vGalleryThumbsApi = $state<CarouselApi | undefined>()\n\n $effect(() => {\n galleryThumbsApi?.goTo(galleryIndex)\n })\n $effect(() => {\n vGalleryThumbsApi?.goTo(vGalleryIndex)\n })\n\n type AutoplayPlugin = { isPlaying: () => boolean; play: () => void; stop: () => void }\n\n function toggleAutoplay() {\n if (!autoplayApi) return\n const plugin = autoplayApi.plugins().autoplay as AutoplayPlugin | undefined\n if (!plugin) return\n if (plugin.isPlaying()) {\n plugin.stop()\n autoplayPlaying = false\n } else {\n plugin.play()\n autoplayPlaying = true\n }\n }\n</script>\n\n<div class=\"space-y-10\">\n <header class=\"space-y-3\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <h1 class=\"text-2xl font-bold\">Carousel</h1>\n <Badge color=\"primary\" variant=\"soft\">Embla v8</Badge>\n </div>\n <p class=\"max-w-3xl text-on-surface-variant\">\n A slideshow component built on top of\n <Link href=\"https://www.embla-carousel.com\" external>Embla Carousel</Link>. Looping,\n autoplay, fade transitions, multiple slides per view, vertical orientation, drag-free\n scrolling and responsive breakpoints — all driven by a single component.\n </p>\n <div class=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {#each features as f (f.label)}\n <div\n class=\"flex items-start gap-3 rounded-lg border border-outline-variant/60 bg-surface-container p-3\"\n >\n <Icon name={f.icon} class=\"mt-0.5 size-5 shrink-0 text-primary\" />\n <div class=\"min-w-0\">\n <p class=\"text-sm font-medium text-on-surface\">{f.label}</p>\n <p class=\"text-xs text-on-surface-variant\">{f.desc}</p>\n </div>\n </div>\n {/each}\n </div>\n </header>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <div class=\"flex items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <p class=\"text-xs text-on-surface-variant\">items + slide snippet</p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel items={fruits}>\n {#snippet slide({ item })}\n <div\n class=\"flex aspect-[2/1 flex-col items-center justify-center rounded-lg bg-linear-to-br {item.from} {item.to} text-white shadow-inner\"\n >\n <div class=\"text-7xl drop-shadow-sm\">{item.emoji}</div>\n <p class=\"mt-2 text-lg font-semibold\">{item.label}</p>\n </div>\n {/snippet}\n </Carousel>\n </div>\n </section>\n\n <!-- Autoplay + Loop -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Loop & autoplay</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>loop</code> + <code>autoplay</code> (pauses on hover by default)\n </p>\n </div>\n <div class=\"space-y-3 rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel\n bind:api={autoplayApi}\n items={photos}\n loop\n autoplay={{ delay: 2500 }}\n ui={{ slide: 'pr-3' }}\n >\n {#snippet slide({ item })}\n <div class=\"relative overflow-hidden rounded-lg\">\n <img\n src={item.src}\n alt={item.alt}\n class=\"aspect-video w-full object-cover\"\n />\n <div\n class=\"absolute inset-x-0 bottom-0 bg-linear-to-t from-black/70 via-black/20 to-transparent p-4\"\n >\n <p class=\"text-sm font-semibold text-white\">{item.title}</p>\n </div>\n </div>\n {/snippet}\n </Carousel>\n <div class=\"flex flex-wrap items-center gap-2 text-sm\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n leadingIcon={autoplayPlaying ? 'lucide:pause' : 'lucide:play'}\n onclick={toggleAutoplay}\n >\n {autoplayPlaying ? 'Pause' : 'Play'}\n </Button>\n <Badge\n color={autoplayPlaying ? 'success' : 'surface'}\n variant=\"soft\"\n leadingIcon={autoplayPlaying ? 'lucide:circle-dot' : 'lucide:circle'}\n >\n {autoplayPlaying ? 'Playing · 2.5s' : 'Paused'}\n </Badge>\n <span class=\"ml-auto text-xs text-on-surface-variant\"\n >Hover the slides to pause</span\n >\n </div>\n </div>\n </section>\n\n <!-- Responsive multi-slide -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Responsive · multiple slides per view</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>ui.slide</code> basis classes + <code>breakpoints</code>\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel\n items={fruits}\n breakpoints={{\n '(min-width: 640px)': { slidesToScroll: 2 },\n '(min-width: 1024px)': { slidesToScroll: 3 }\n }}\n ui={{ slide: 'basis-full sm:basis-1/2 lg:basis-1/3 pr-3' }}\n >\n {#snippet slide({ item })}\n <div\n class=\"flex aspect-square flex-col items-center justify-center rounded-xl bg-linear-to-br {item.from} {item.to} text-white\"\n >\n <div class=\"text-6xl\">{item.emoji}</div>\n <p class=\"mt-2 text-sm font-semibold tracking-wide uppercase\">\n {item.label}\n </p>\n </div>\n {/snippet}\n </Carousel>\n <p class=\"mt-3 text-xs text-on-surface-variant\">\n Resize the window: 1 slide on mobile, 2 on tablet, 3 on desktop.\n </p>\n </div>\n </section>\n\n <!-- Fade -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Fade transition</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>fade</code> + <code>loop</code> + <code>autoplay</code>\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel items={photos} fade loop autoplay={{ delay: 3500 }}>\n {#snippet slide({ item })}\n <img\n src={item.src}\n alt={item.alt}\n class=\"aspect-video w-full rounded-lg object-cover\"\n />\n {/snippet}\n </Carousel>\n </div>\n </section>\n\n <!-- Vertical -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Vertical orientation</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>orientation=\"vertical\"</code> — arrows & dots reposition automatically\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <div class=\"mx-auto h-96 w-full max-w-md\">\n <Carousel items={fruits} orientation=\"vertical\" loop>\n {#snippet slide({ item })}\n <div\n class=\"flex h-full w-full items-center justify-center rounded-lg bg-linear-to-b {item.from} {item.to} text-white\"\n >\n <div class=\"text-center\">\n <div class=\"text-7xl\">{item.emoji}</div>\n <p class=\"mt-3 text-xl font-semibold\">{item.label}</p>\n </div>\n </div>\n {/snippet}\n </Carousel>\n </div>\n </div>\n </section>\n\n <!-- Controlled -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Controlled · bind:index + bind:api</h2>\n <p class=\"text-xs text-on-surface-variant\">Drive from outside, expose Embla API</p>\n </div>\n <div class=\"space-y-4 rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel bind:api={controlledApi} bind:index={controlledIndex} items={fruits} loop>\n {#snippet slide({ item, selected })}\n <div\n class=\"flex aspect-[2/1 flex-col items-center justify-center rounded-lg bg-linear-to-br {item.from} {item.to} text-white transition-transform\"\n class:scale-100={selected}\n class:scale-95={!selected}\n >\n <div class=\"text-7xl\">{item.emoji}</div>\n <p class=\"mt-2 text-lg font-semibold\">{item.label}</p>\n </div>\n {/snippet}\n </Carousel>\n <div class=\"flex flex-wrap items-center gap-2 rounded-lg bg-surface-container-high p-3\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n leadingIcon=\"lucide:chevrons-left\"\n onclick={() => (controlledIndex = 0)}\n >\n First\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n leadingIcon=\"lucide:chevron-left\"\n onclick={() => controlledApi?.goToPrev()}\n >\n Prev\n </Button>\n <div class=\"flex items-center gap-1\">\n {#each fruits as fruit, i (fruit.id)}\n <button\n type=\"button\"\n class=\"size-8 rounded-md text-sm font-medium transition-colors {i ===\n controlledIndex\n ? 'bg-primary text-on-primary'\n : 'bg-surface text-on-surface-variant hover:bg-surface-container-highest'}\"\n onclick={() => (controlledIndex = i)}\n >\n {i + 1}\n </button>\n {/each}\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n trailingIcon=\"lucide:chevron-right\"\n onclick={() => controlledApi?.goToNext()}\n >\n Next\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n trailingIcon=\"lucide:chevrons-right\"\n onclick={() => (controlledIndex = fruits.length - 1)}\n >\n Last\n </Button>\n <span class=\"ml-auto text-sm font-medium text-on-surface\"\n >{fruits[controlledIndex]?.label}\n <span class=\"text-on-surface-variant\"\n >· {controlledIndex + 1} / {fruits.length}</span\n ></span\n >\n </div>\n </div>\n </section>\n\n <!-- Custom snippets -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Custom arrows & dots</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>prevSlot</code>, <code>nextSlot</code>, <code>dot</code> snippets\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel items={photos} loop>\n {#snippet slide({ item })}\n <img\n src={item.src}\n alt={item.alt}\n class=\"aspect-video w-full rounded-lg object-cover\"\n />\n {/snippet}\n {#snippet prevSlot({ canScroll, scroll })}\n <button\n type=\"button\"\n disabled={!canScroll}\n class=\"absolute top-1/2 left-3 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2.5 text-white backdrop-blur-sm transition hover:bg-black/70 disabled:cursor-not-allowed disabled:opacity-30\"\n aria-label=\"Previous\"\n onclick={scroll}\n >\n <Icon name=\"lucide:arrow-left\" class=\"size-5\" />\n </button>\n {/snippet}\n {#snippet nextSlot({ canScroll, scroll })}\n <button\n type=\"button\"\n disabled={!canScroll}\n class=\"absolute top-1/2 right-3 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2.5 text-white backdrop-blur-sm transition hover:bg-black/70 disabled:cursor-not-allowed disabled:opacity-30\"\n aria-label=\"Next\"\n onclick={scroll}\n >\n <Icon name=\"lucide:arrow-right\" class=\"size-5\" />\n </button>\n {/snippet}\n {#snippet dot({ index, selected, select })}\n <button\n type=\"button\"\n class=\"h-1.5 rounded-full transition-all {selected\n ? 'w-10 bg-primary'\n : 'w-5 bg-on-surface/30 hover:bg-on-surface/50'}\"\n aria-label={`Go to slide ${index + 1}`}\n onclick={select}\n ></button>\n {/snippet}\n </Carousel>\n </div>\n </section>\n\n <!-- Gallery · horizontal thumbnails -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Gallery · horizontal thumbnails</h2>\n <p class=\"text-xs text-on-surface-variant\">\n Two carousels synced via <code>bind:index</code> + <code>bind:api</code>\n </p>\n </div>\n <div class=\"space-y-3 rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel bind:index={galleryIndex} items={photos} loop dots={false}>\n {#snippet slide({ item })}\n <div class=\"relative overflow-hidden rounded-lg\">\n <img\n src={item.src}\n alt={item.alt}\n class=\"aspect-video w-full object-cover\"\n />\n <div\n class=\"absolute right-3 bottom-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm\"\n >\n {galleryIndex + 1} / {photos.length}\n </div>\n <div\n class=\"absolute bottom-0 left-0 rounded-tr-lg bg-black/60 px-3 py-1.5 text-sm font-semibold text-white backdrop-blur-sm\"\n >\n {item.title}\n </div>\n </div>\n {/snippet}\n </Carousel>\n <Carousel\n bind:api={galleryThumbsApi}\n items={photos}\n arrows={false}\n dots={false}\n dragFree\n ui={{\n viewport: 'px-1.5 py-1.5',\n slide: 'basis-1/3 sm:basis-1/4 md:basis-1/5 lg:basis-1/6 pr-2'\n }}\n >\n {#snippet slide({ item, index })}\n <button\n type=\"button\"\n class=\"block w-full overflow-hidden rounded-md transition-all {index ===\n galleryIndex\n ? 'ring-2 ring-primary ring-offset-2 ring-offset-surface-container'\n : 'opacity-50 hover:opacity-100'}\"\n aria-label={`View ${item.title}`}\n aria-current={index === galleryIndex ? 'true' : undefined}\n onclick={() => (galleryIndex = index)}\n >\n <img\n src={item.src}\n alt={item.alt}\n class=\"aspect-video w-full object-cover\"\n />\n </button>\n {/snippet}\n </Carousel>\n </div>\n </section>\n\n <!-- Gallery · vertical thumbnails -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Gallery · vertical thumbnails</h2>\n <p class=\"text-xs text-on-surface-variant\">\n Vertical thumb rail synced with main slide\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <div class=\"mx-auto flex max-w-3xl flex-col gap-3 sm:h-96 sm:flex-row\">\n <div class=\"aspect-video w-full sm:aspect-auto sm:flex-1\">\n <Carousel\n bind:index={vGalleryIndex}\n items={photos}\n orientation=\"vertical\"\n loop\n arrows={false}\n dots={false}\n >\n {#snippet slide({ item })}\n <div class=\"relative h-full w-full overflow-hidden rounded-lg\">\n <img\n src={item.src}\n alt={item.alt}\n class=\"h-full w-full object-cover\"\n />\n <div\n class=\"absolute right-3 bottom-3 rounded-full bg-black/60 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm\"\n >\n {vGalleryIndex + 1} / {photos.length}\n </div>\n <div\n class=\"absolute bottom-0 left-0 rounded-tr-lg bg-black/60 px-3 py-1.5 text-sm font-semibold text-white backdrop-blur-sm\"\n >\n {item.title}\n </div>\n </div>\n {/snippet}\n </Carousel>\n </div>\n <div class=\"hidden w-28 sm:block\">\n <Carousel\n bind:api={vGalleryThumbsApi}\n items={photos}\n orientation=\"vertical\"\n slidesToShow={6}\n arrows={false}\n dots={false}\n dragFree\n ui={{ viewport: 'px-1.5 py-1.5', slide: 'pb-2' }}\n >\n {#snippet slide({ item, index })}\n <button\n type=\"button\"\n class=\"block h-full w-full overflow-hidden rounded-md transition-all {index ===\n vGalleryIndex\n ? 'ring-2 ring-primary ring-offset-2 ring-offset-surface-container'\n : 'opacity-50 hover:opacity-100'}\"\n aria-label={`View ${item.title}`}\n aria-current={index === vGalleryIndex ? 'true' : undefined}\n onclick={() => (vGalleryIndex = index)}\n >\n <img\n src={item.src}\n alt={item.alt}\n class=\"h-full w-full object-cover\"\n />\n </button>\n {/snippet}\n </Carousel>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>color</code> drives the active dot & arrow tint\n </p>\n </div>\n <div class=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {#each colors as color (color)}\n <div\n class=\"space-y-2 rounded-xl border border-outline-variant/60 bg-surface-container p-3\"\n >\n <p class=\"text-xs font-medium text-on-surface-variant capitalize\">{color}</p>\n <Carousel items={fruits.slice(0, 4)} {color}>\n {#snippet slide({ item })}\n <div\n class=\"flex aspect-[2/1 items-center justify-center rounded-md bg-linear-to-br {item.from} {item.to} text-5xl text-white\"\n >\n {item.emoji}\n </div>\n {/snippet}\n </Carousel>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>size</code> scales arrows and dot pills\n </p>\n </div>\n <div class=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-3\">\n {#each sizes as size (size)}\n <div\n class=\"space-y-2 rounded-xl border border-outline-variant/60 bg-surface-container p-3\"\n >\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">{size}</p>\n <Carousel items={fruits.slice(0, 4)} {size}>\n {#snippet slide({ item })}\n <div\n class=\"flex aspect-[2/1 items-center justify-center rounded-md bg-linear-to-br {item.from} {item.to} text-5xl text-white\"\n >\n {item.emoji}\n </div>\n {/snippet}\n </Carousel>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Drag-free -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Drag-free content browser</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>dragFree</code> — free-form scroll without snapping\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel\n items={fruits}\n dragFree\n arrows={false}\n dots={false}\n ui={{ slide: 'basis-1/2 sm:basis-1/3 md:basis-1/4 lg:basis-1/5 pr-3' }}\n >\n {#snippet slide({ item })}\n <div\n class=\"flex aspect-square flex-col items-center justify-center rounded-xl bg-linear-to-br {item.from} {item.to} text-white shadow-sm\"\n >\n <div class=\"text-4xl\">{item.emoji}</div>\n <p class=\"mt-1 text-xs font-semibold tracking-wide uppercase\">\n {item.label}\n </p>\n </div>\n {/snippet}\n </Carousel>\n <p class=\"mt-3 text-xs text-on-surface-variant\">\n Try dragging — slides peek and ease to rest, no snapping.\n </p>\n </div>\n </section>\n\n <!-- Minimal -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Minimal · no arrows or dots</h2>\n <p class=\"text-xs text-on-surface-variant\">\n <code>arrows={false}</code> + <code>dots={false}</code>\n </p>\n </div>\n <div class=\"rounded-xl border border-outline-variant/60 bg-surface-container p-4\">\n <Carousel items={photos} arrows={false} dots={false} loop autoplay={{ delay: 4000 }}>\n {#snippet slide({ item })}\n <img\n src={item.src}\n alt={item.alt}\n class=\"aspect-21/9 w-full rounded-lg object-cover\"\n />\n {/snippet}\n </Carousel>\n </div>\n </section>\n</div>\n",
|
|
102
124
|
"chip": "<script lang=\"ts\">\n import { Chip, Avatar, Button } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const\n const positions = ['top-right', 'bottom-right', 'top-left', 'bottom-left'] as const\n\n const statuses = [\n { label: 'Online', color: 'success' },\n { label: 'Away', color: 'warning' },\n { label: 'Busy', color: 'error' },\n { label: 'Offline', color: 'surface' }\n ] as const\n\n let statusIndex = $state(0)\n let showChip = $state(true)\n let notificationCount = $state(3)\n\n function cycleStatus() {\n statusIndex = (statusIndex + 1) % statuses.length\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Chip</h1>\n <p class=\"text-on-surface-variant\">\n Small indicator badge for notifications, status, or counts.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <Chip>\n <div class=\"size-10 rounded-lg bg-surface-container-highest\"></div>\n </Chip>\n <Chip color=\"error\">\n <Avatar src=\"https://i.pravatar.cc/150?img=1\" alt=\"User\" />\n </Chip>\n <Chip color=\"success\" text=\"3\">\n <Button icon=\"lucide:bell\" variant=\"ghost\" />\n </Chip>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <Chip {color}>\n <div class=\"size-10 rounded-lg bg-surface-container-highest\"></div>\n </Chip>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <Chip {size} color=\"primary\">\n <div class=\"size-12 rounded-lg bg-surface-container-highest\"></div>\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Positions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Positions</h2>\n <div class=\"flex flex-wrap items-center gap-8 rounded-lg bg-surface-container-high p-4\">\n {#each positions as position (position)}\n <div class=\"flex flex-col items-center gap-2\">\n <Chip {position} color=\"error\">\n <div class=\"size-12 rounded-lg bg-surface-container-highest\"></div>\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">{position}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- With Text -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Text</h2>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <Chip text=\"5\" size=\"lg\" color=\"error\">\n <Button icon=\"lucide:bell\" variant=\"ghost\" size=\"lg\" />\n </Chip>\n <Chip text=\"99+\" size=\"xl\" color=\"primary\">\n <Button icon=\"lucide:mail\" variant=\"ghost\" size=\"lg\" />\n </Chip>\n <Chip text=\"NEW\" size=\"2xl\" color=\"success\">\n <Button icon=\"lucide:gift\" variant=\"ghost\" size=\"lg\" />\n </Chip>\n </div>\n </section>\n\n <!-- Inset -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Inset</h2>\n <p class=\"text-sm text-on-surface-variant\">Keep the chip inside the component bounds.</p>\n <div class=\"flex flex-wrap items-center gap-8 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <Chip color=\"error\">\n <div class=\"size-12 rounded-lg bg-surface-container-highest\"></div>\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">default</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <Chip color=\"error\" inset>\n <div class=\"size-12 rounded-lg bg-surface-container-highest\"></div>\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">inset</span>\n </div>\n </div>\n </section>\n\n <!-- Standalone -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Standalone</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Render chip without positioning, useful for inline indicators.\n </p>\n <div class=\"flex flex-wrap items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <Chip {color} standalone size=\"md\" />\n {/each}\n </div>\n <div class=\"flex items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n <span>Status:</span>\n <Chip color=\"success\" standalone size=\"sm\" />\n <span class=\"text-sm text-on-surface-variant\">Online</span>\n </div>\n </section>\n\n <!-- Show/Hide -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Show / Hide</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control visibility with <code class=\"rounded bg-surface-container-highest px-1\"\n >bind:show</code\n > for two-way binding.\n </p>\n <div class=\"flex flex-wrap items-center gap-8 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <Chip color=\"error\" show={true}>\n <Button icon=\"lucide:bell\" variant=\"ghost\" />\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">show=true</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <Chip color=\"error\" show={false}>\n <Button icon=\"lucide:bell\" variant=\"ghost\" />\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">show=false</span>\n </div>\n <div class=\"flex flex-col items-center gap-3\">\n <Chip color=\"error\" text={notificationCount} size=\"lg\" bind:show={showChip}>\n <Button\n icon=\"lucide:bell\"\n variant=\"ghost\"\n onclick={() => {\n showChip = !showChip\n }}\n />\n </Chip>\n <span class=\"text-xs text-on-surface-variant\">\n bind:show ({showChip ? 'visible' : 'hidden'})\n </span>\n </div>\n </div>\n </section>\n\n <!-- Status Cycling -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Status Indicator</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Cycle through statuses by clicking the avatar. Combines color, position, and inset.\n </p>\n <div class=\"flex items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <button onclick={cycleStatus} class=\"cursor-pointer\">\n <Chip color={statuses[statusIndex].color} position=\"bottom-right\" inset size=\"sm\">\n <Avatar src=\"https://i.pravatar.cc/150?img=5\" alt=\"User\" size=\"lg\" />\n </Chip>\n </button>\n <div class=\"flex items-center gap-2\">\n <Chip color={statuses[statusIndex].color} standalone size=\"sm\" />\n <span class=\"text-sm\">{statuses[statusIndex].label}</span>\n </div>\n </div>\n </section>\n\n <!-- Colors x Positions Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors × Positions</h2>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-3 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Color</th\n >\n {#each positions as position (position)}\n <th\n class=\"px-3 py-3 text-center text-sm font-medium text-on-surface-variant\"\n >{position}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each colors as color (color)}\n <tr class=\"border-b border-outline-variant/50\">\n <td\n class=\"px-3 py-3 text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</td\n >\n {#each positions as position (position)}\n <td class=\"px-3 py-3\">\n <div class=\"flex justify-center\">\n <Chip {color} {position}>\n <div\n class=\"size-10 rounded-lg bg-surface-container-highest\"\n ></div>\n </Chip>\n </div>\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Notification Bell -->\n <div>\n <p class=\"mb-3 text-sm font-medium text-on-surface-variant\">Notification Bell</p>\n <div class=\"flex items-center gap-4\">\n <Chip text=\"3\" size=\"lg\" color=\"error\">\n <Button icon=\"lucide:bell\" variant=\"ghost\" size=\"lg\" />\n </Chip>\n <Chip text=\"12\" size=\"lg\" color=\"primary\">\n <Button icon=\"lucide:mail\" variant=\"ghost\" size=\"lg\" />\n </Chip>\n <Chip text=\"99+\" size=\"xl\" color=\"warning\">\n <Button icon=\"lucide:message-circle\" variant=\"ghost\" size=\"lg\" />\n </Chip>\n </div>\n </div>\n\n <!-- User Avatar Status -->\n <div>\n <p class=\"mb-3 text-sm font-medium text-on-surface-variant\">User Status</p>\n <div class=\"flex items-center gap-4\">\n <Chip color=\"success\" position=\"bottom-right\" inset size=\"sm\">\n <Avatar src=\"https://i.pravatar.cc/150?img=1\" alt=\"Online user\" size=\"lg\" />\n </Chip>\n <Chip color=\"warning\" position=\"bottom-right\" inset size=\"sm\">\n <Avatar src=\"https://i.pravatar.cc/150?img=2\" alt=\"Away user\" size=\"lg\" />\n </Chip>\n <Chip color=\"surface\" position=\"bottom-right\" inset size=\"sm\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=3\"\n alt=\"Offline user\"\n size=\"lg\"\n />\n </Chip>\n </div>\n </div>\n\n <!-- Navigation with badges -->\n <div>\n <p class=\"mb-3 text-sm font-medium text-on-surface-variant\">Navigation</p>\n <nav class=\"flex items-center gap-2\">\n <Button variant=\"ghost\">Home</Button>\n <Chip text=\"5\" size=\"lg\" color=\"error\" inset>\n <Button variant=\"ghost\">Messages</Button>\n </Chip>\n <Chip text=\"2\" size=\"lg\" color=\"primary\" inset>\n <Button variant=\"ghost\">Notifications</Button>\n </Chip>\n <Button variant=\"ghost\">Settings</Button>\n </nav>\n </div>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <Chip color=\"error\" ui={{ base: 'animate-pulse' }}>\n <Button icon=\"lucide:bell\" variant=\"ghost\" />\n </Chip>\n <Chip color=\"primary\" size=\"2xs\" inset ui={{ base: 'ring-2 ring-primary' }}>\n <Avatar src=\"https://i.pravatar.cc/150?img=4\" alt=\"User\" size=\"2xl\" />\n </Chip>\n </div>\n </section>\n</div>\n",
|
|
125
|
+
"list": "<script lang=\"ts\">\n import { List, ListItem, Avatar } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">List</h1>\n <p class=\"text-on-surface-variant\">\n A versatile component for displaying continuous, vertical indexes of text or images.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">List</code> to wrap multiple <code class=\"rounded bg-surface-container-highest px-1\">ListItem</code> components.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <List class=\"w-full max-w-md bg-surface border border-outline-variant rounded-lg\">\n <ListItem title=\"Account Settings\" description=\"Manage your profile and preferences\" />\n <ListItem title=\"Security\" description=\"Password, 2FA, and sessions\" />\n <ListItem title=\"Notifications\" description=\"Email and push alerts\" />\n </List>\n </div>\n </section>\n\n <!-- Avatars & Actions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Avatars & Actions</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">leading</code> and <code class=\"rounded bg-surface-container-highest px-1\">trailing</code> snippets to add avatars, icons, or buttons.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <List class=\"w-full max-w-md bg-surface border border-outline-variant rounded-lg\">\n <ListItem title=\"Alice\" description=\"Active 2m ago\" href=\"#\">\n {#snippet leading()}\n <Avatar src=\"https://i.pravatar.cc/150?img=1\" alt=\"Alice\" />\n {/snippet}\n </ListItem>\n <ListItem title=\"Bob\" description=\"Offline\" href=\"#\">\n {#snippet leading()}\n <Avatar src=\"https://i.pravatar.cc/150?img=2\" alt=\"Bob\" />\n {/snippet}\n </ListItem>\n </List>\n </div>\n </section>\n</div>\n",
|
|
126
|
+
"number-ticker": "<script lang=\"ts\">\n import { NumberTicker } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">NumberTicker</h1>\n <p class=\"text-on-surface-variant\">\n An animated counter that smoothly transitions from zero to a target number.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Simply provide a <code class=\"rounded bg-surface-container-highest px-1\">value</code>. It will automatically format with commas.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-8 flex flex-col items-center justify-center gap-4\">\n <div class=\"text-4xl font-extrabold text-primary\">\n <NumberTicker value={150000} />\n </div>\n <div class=\"text-on-surface-variant\">Total Users</div>\n </div>\n </section>\n\n <!-- Decimals & Duration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Decimals & Duration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">decimals</code> for floating point numbers and <code class=\"rounded bg-surface-container-highest px-1\">duration</code> to control speed.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-8 flex flex-col items-center justify-center gap-4\">\n <div class=\"text-4xl font-extrabold text-success\">\n $<NumberTicker value={2.5} decimals={2} duration={3000} />M\n </div>\n <div class=\"text-on-surface-variant\">Revenue</div>\n </div>\n </section>\n</div>\n",
|
|
127
|
+
"prose": "<script lang=\"ts\">\n import { Prose } from '$lib/index.js'\n \n const sampleHtml = `\n <h1>Introducing Svelora</h1>\n <p>Svelora is a beautiful, highly customizable UI component library for Svelte 5. It provides a solid foundation for your next web application.</p>\n <h2>Key Features</h2>\n <ul>\n <li>Built with Tailwind CSS</li>\n <li>Fully typesafe</li>\n <li>Accessible and responsive</li>\n </ul>\n <blockquote>The best UI library I have ever used! - <em>A Happy Developer</em></blockquote>\n <pre><code>npm install svelora</code></pre>\n <p>For more information, visit our <a href=\"#\">documentation</a>.</p>\n `\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Prose</h1>\n <p class=\"text-on-surface-variant\">\n A typography wrapper to easily style Markdown or raw HTML content.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage with Raw HTML</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass a string of HTML to the <code class=\"rounded bg-surface-container-highest px-1\">html</code> prop. It will automatically apply beautiful typography styles.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"w-full max-w-2xl bg-surface p-8 border border-outline-variant rounded-lg\">\n <Prose html={sampleHtml} />\n </div>\n </div>\n </section>\n\n <!-- Slot Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage with Svelte Content</h2>\n <p class=\"text-sm text-on-surface-variant\">\n You can also wrap standard Svelte elements inside <code class=\"rounded bg-surface-container-highest px-1\">Prose</code> to apply styles.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"w-full max-w-2xl bg-surface p-8 border border-outline-variant rounded-lg\">\n <Prose size=\"sm\">\n <h2>Smaller Size Prose</h2>\n <p>By using <code class=\"px-1 py-0.5 rounded bg-surface-container\">size=\"sm\"</code>, the typography scales down appropriately.</p>\n <hr />\n <table>\n <thead>\n <tr>\n <th>Component</th>\n <th>Status</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Prose</td>\n <td>Done</td>\n </tr>\n <tr>\n <td>Other</td>\n <td>In Progress</td>\n </tr>\n </tbody>\n </table>\n </Prose>\n </div>\n </div>\n </section>\n</div>\n",
|
|
103
128
|
"empty": "<script lang=\"ts\">\n import { Empty, Separator } from '$lib/index.js'\n import Link from '$lib/Link/Link.svelte'\n\n const variants = ['solid', 'outline', 'soft', 'subtle', 'naked'] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Empty</h1>\n <p class=\"text-on-surface-variant\">\n Display empty state UI when there's no content to show, with support for icons, avatars,\n and action buttons.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"grid gap-4 lg:grid-cols-3\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">\n Icon + title + description\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n icon=\"lucide:inbox\"\n title=\"No messages\"\n description=\"You don't have any messages yet.\"\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Title only</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty icon=\"lucide:file-x\" title=\"No files found\" />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With avatar</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n avatar={{ src: 'https://i.pravatar.cc/150?u=empty', alt: 'User' }}\n title=\"No notifications\"\n description=\"You're all caught up!\"\n />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Variants with Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants with Icon</h2>\n <div class=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {#each variants as variant (variant)}\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant capitalize\">{variant}</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n {variant}\n icon=\"lucide:inbox\"\n title=\"No items\"\n description=\"Nothing to display here.\"\n size=\"sm\"\n />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Variants with Avatar -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants with Avatar</h2>\n <div class=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {#each variants as variant (variant)}\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant capitalize\">{variant}</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n {variant}\n avatar={{\n src: 'https://i.pravatar.cc/150?u=variant-{variant}',\n alt: 'User'\n }}\n title=\"No notifications\"\n description=\"You're all caught up!\"\n size=\"sm\"\n />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-3\">\n {#each sizes as size (size)}\n <div class=\"flex items-start gap-4\">\n <span class=\"w-8 pt-4 text-sm font-medium text-on-surface-variant\">{size}</span>\n <div class=\"flex-1\">\n <Empty\n {size}\n icon=\"lucide:inbox\"\n title=\"Empty state\"\n description=\"This is the {size} size.\"\n />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- With Actions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Actions</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Single action</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n icon=\"lucide:inbox\"\n title=\"No messages\"\n description=\"Start a conversation with your team.\"\n actions={[{ label: 'New Message', leadingIcon: 'lucide:plus' }]}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Multiple actions</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n icon=\"lucide:shopping-cart\"\n title=\"Cart is empty\"\n description=\"Add some items to get started.\"\n actions={[\n { label: 'Browse Products' },\n { label: 'View Wishlist', variant: 'ghost' }\n ]}\n />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom leading</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n variant=\"outline\"\n title=\"Welcome!\"\n description=\"Get started by exploring our components.\"\n >\n {#snippet leading()}\n <div\n class=\"flex size-16 items-center justify-center rounded-full bg-tertiary/10 text-tertiary\"\n >\n <span class=\"text-3xl\">🎉</span>\n </div>\n {/snippet}\n </Empty>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom header</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty variant=\"soft\">\n {#snippet header()}\n <div class=\"flex flex-col items-center gap-3 text-center\">\n <div\n class=\"flex size-14 items-center justify-center rounded-full bg-primary/10\"\n >\n <span class=\"text-2xl text-primary\">✨</span>\n </div>\n <div>\n <p class=\"text-lg font-bold text-primary\">Custom Header</p>\n <p class=\"text-sm text-on-surface-variant\">\n Entirely custom header content\n </p>\n </div>\n </div>\n {/snippet}\n </Empty>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Footer Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Footer</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n variant=\"subtle\"\n icon=\"lucide:help-circle\"\n title=\"Need help?\"\n description=\"Check our documentation or contact support.\"\n >\n {#snippet footer()}\n <p class=\"text-xs text-on-surface-variant\">\n Last updated: March 2026 · <Link href=\"/empty\" class=\"underline\"\n >Documentation</Link\n >\n </p>\n {/snippet}\n </Empty>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n icon=\"lucide:star\"\n title=\"Custom Styles\"\n description=\"With ui slot overrides.\"\n actions={[{ label: 'Action' }]}\n ui={{\n title: 'text-warning font-bold',\n description: 'italic',\n actions: 'mt-2'\n }}\n />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Empty\n icon=\"lucide:heart\"\n title=\"Custom Root\"\n description=\"Dashed border styling.\"\n ui={{\n root: 'border-2 border-dashed border-outline-variant'\n }}\n />\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <!-- Empty Inbox -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Empty Inbox</p>\n <Empty\n variant=\"soft\"\n icon=\"lucide:mail\"\n title=\"Inbox Zero!\"\n description=\"You've read all your messages. Great job staying on top of things!\"\n />\n </div>\n\n <!-- No Search Results -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">No Search Results</p>\n <Empty\n variant=\"outline\"\n icon=\"lucide:search-x\"\n title=\"No results found\"\n description=\"We couldn't find anything matching your search. Try different keywords.\"\n actions={[\n { label: 'Clear Search', leadingIcon: 'lucide:x' },\n { label: 'Browse All', variant: 'outline' }\n ]}\n />\n </div>\n\n <!-- Network Error -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Network Error</p>\n <Empty\n variant=\"subtle\"\n icon=\"lucide:wifi-off\"\n title=\"Connection lost\"\n description=\"Unable to load data. Please check your internet connection and try again.\"\n actions={[{ label: 'Retry', leadingIcon: 'lucide:refresh-cw' }]}\n />\n </div>\n\n <!-- Completed Tasks -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">All Tasks Done</p>\n <Empty\n variant=\"soft\"\n icon=\"lucide:check-circle-2\"\n title=\"All tasks completed!\"\n description=\"You've finished everything on your list. Time to relax or add new tasks.\"\n actions={[\n { label: 'Add Task', leadingIcon: 'lucide:plus' },\n { label: 'View Archive', variant: 'ghost' }\n ]}\n />\n </div>\n\n <!-- Empty Cart -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Empty Shopping Cart</p>\n <Empty\n variant=\"naked\"\n icon=\"lucide:shopping-cart\"\n title=\"Your cart is empty\"\n description=\"Looks like you haven't added anything yet.\"\n actions={[{ label: 'Start Shopping', leadingIcon: 'lucide:shopping-bag' }]}\n />\n </div>\n\n <!-- No Files -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">No Files Uploaded</p>\n <Empty\n variant=\"soft\"\n icon=\"lucide:file-x\"\n title=\"No files uploaded\"\n description=\"Upload your first file to get started with your project.\"\n actions={[\n { label: 'Upload File', leadingIcon: 'lucide:upload' },\n { label: 'Import from URL', variant: 'ghost' }\n ]}\n />\n </div>\n </section>\n</div>\n",
|
|
104
129
|
"skeleton": "<script lang=\"ts\">\n import { Skeleton, Card, Avatar, Button, Separator } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Skeleton</h1>\n <p class=\"text-on-surface-variant\">\n Display placeholder content while data is loading to improve perceived performance.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Skeleton class=\"h-4 w-3/4\" />\n <Skeleton class=\"h-4 w-1/2\" />\n <Skeleton class=\"h-4 w-2/3\" />\n </div>\n </section>\n\n <!-- Shapes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Shapes</h2>\n <div class=\"grid gap-6 sm:grid-cols-2 lg:grid-cols-4\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Text Lines</p>\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <Skeleton class=\"h-4 w-48\" />\n <Skeleton class=\"h-4 w-40\" />\n <Skeleton class=\"h-4 w-44\" />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Circle (Avatar)</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-4\"\n >\n <Skeleton class=\"size-12 rounded-full\" />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Square</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-4\"\n >\n <Skeleton class=\"size-24 rounded-lg\" />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Rectangle (Image)</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-4\"\n >\n <Skeleton class=\"h-24 w-full rounded-lg\" />\n </div>\n </div>\n </div>\n </section>\n\n <!-- As Different Element -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">As Different Element</h2>\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <Skeleton as=\"span\" class=\"inline-block h-4 w-32\" />\n <Skeleton as=\"div\" class=\"h-4 w-48\" />\n <Skeleton as=\"p\" class=\"h-4 w-40\" />\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom background</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Skeleton class=\"h-16 w-full\" ui={{ root: 'bg-primary/15' }} />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom border radius</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Skeleton class=\"h-16 w-full\" ui={{ root: 'rounded-2xl' }} />\n </div>\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <!-- Loading Card Comparison -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Card Loading vs Loaded</p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <div class=\"space-y-3\">\n <div class=\"flex items-center gap-3\">\n <Skeleton class=\"size-12 rounded-full\" />\n <div class=\"flex-1 space-y-2\">\n <Skeleton class=\"h-4 w-1/3\" />\n <Skeleton class=\"h-3 w-1/2\" />\n </div>\n </div>\n <Skeleton class=\"h-4 w-full\" />\n <Skeleton class=\"h-4 w-5/6\" />\n <Skeleton class=\"h-4 w-4/6\" />\n <div class=\"flex gap-2 pt-2\">\n <Skeleton class=\"h-9 w-24\" />\n <Skeleton class=\"h-9 w-20\" />\n </div>\n </div>\n </Card>\n <Card>\n <div class=\"space-y-3\">\n <div class=\"flex items-center gap-3\">\n <Avatar\n src=\"https://i.pravatar.cc/150?img=3\"\n alt=\"Jane Smith\"\n size=\"md\"\n />\n <div>\n <h3 class=\"font-semibold\">Jane Smith</h3>\n <p class=\"text-sm text-on-surface-variant\">Product Designer</p>\n </div>\n </div>\n <p class=\"text-on-surface-variant\">\n Creating beautiful and functional user interfaces with attention to\n detail and user experience.\n </p>\n <div class=\"flex gap-2\">\n <Button variant=\"solid\" color=\"primary\" label=\"Follow\" size=\"sm\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"Message\" size=\"sm\" />\n </div>\n </div>\n </Card>\n </div>\n </div>\n\n <!-- Loading List -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">User List</p>\n <Card>\n <div class=\"space-y-4\">\n {#each Array.from({ length: 4 }, (_, i) => i) as i (i)}\n <div\n class=\"flex items-center gap-3 border-b border-outline-variant pb-4 last:border-0 last:pb-0\"\n >\n <Skeleton class=\"size-10 rounded-full\" />\n <div class=\"flex-1 space-y-2\">\n <Skeleton class=\"h-4 w-1/4\" />\n <Skeleton class=\"h-3 w-3/4\" />\n </div>\n </div>\n {/each}\n </div>\n </Card>\n </div>\n\n <!-- Loading Article -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Article</p>\n <Card as=\"article\">\n <div class=\"space-y-4\">\n <div class=\"space-y-3\">\n <Skeleton class=\"h-8 w-3/4\" />\n <div class=\"flex items-center gap-3\">\n <Skeleton class=\"size-8 rounded-full\" />\n <div class=\"space-y-2\">\n <Skeleton class=\"h-3 w-24\" />\n <Skeleton class=\"h-3 w-32\" />\n </div>\n </div>\n </div>\n <Skeleton class=\"h-48 w-full rounded-lg\" />\n <div class=\"space-y-2\">\n <Skeleton class=\"h-4 w-full\" />\n <Skeleton class=\"h-4 w-full\" />\n <Skeleton class=\"h-4 w-5/6\" />\n <Skeleton class=\"h-4 w-4/6\" />\n </div>\n </div>\n </Card>\n </div>\n\n <!-- Dashboard Widgets -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Dashboard Widgets</p>\n <div class=\"grid gap-4 md:grid-cols-3\">\n {#each Array.from({ length: 3 }, (_, i) => i) as i (i)}\n <Card>\n <div class=\"space-y-3\">\n <div class=\"flex items-center justify-between\">\n <Skeleton class=\"h-4 w-24\" />\n <Skeleton class=\"size-6 rounded\" />\n </div>\n <Skeleton class=\"h-10 w-20\" />\n <Skeleton class=\"h-20 w-full rounded\" />\n </div>\n </Card>\n {/each}\n </div>\n </div>\n\n <!-- Media Grid -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Media Grid</p>\n <div class=\"grid grid-cols-2 gap-4 md:grid-cols-4\">\n {#each Array.from({ length: 4 }, (_, i) => i) as i (i)}\n <div class=\"space-y-2\">\n <Skeleton class=\"aspect-square w-full rounded-lg\" />\n <Skeleton class=\"h-3 w-3/4\" />\n <Skeleton class=\"h-3 w-1/2\" />\n </div>\n {/each}\n </div>\n </div>\n </section>\n</div>\n",
|
|
105
130
|
"timeline": "<script lang=\"ts\">\n import { Timeline, Button, Separator } from '$lib/index.js'\n import type { TimelineItem } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const\n\n // Basic items\n const basicItems: TimelineItem[] = [\n { value: 1, title: 'Step 1', description: 'First step', date: 'Jan 1' },\n { value: 2, title: 'Step 2', description: 'Second step', date: 'Jan 5' },\n { value: 3, title: 'Step 3', description: 'Third step', date: 'Jan 10' }\n ]\n\n // Order tracking\n let orderStep = $state(2)\n const orderItems: TimelineItem[] = [\n {\n value: 1,\n icon: 'lucide:shopping-cart',\n title: 'Order Placed',\n description: 'Your order has been confirmed',\n date: 'Dec 20'\n },\n {\n value: 2,\n icon: 'lucide:package',\n title: 'Processing',\n description: 'Preparing your items',\n date: 'Dec 21'\n },\n {\n value: 3,\n icon: 'lucide:truck',\n title: 'Shipped',\n description: 'On the way to you',\n date: 'Dec 22'\n },\n {\n value: 4,\n icon: 'lucide:home',\n title: 'Delivered',\n description: 'Package delivered',\n date: 'Dec 24'\n }\n ]\n\n // Checkout stepper\n let checkoutStep = $state(2)\n const checkoutItems: TimelineItem[] = [\n { value: 1, icon: 'lucide:shopping-bag', title: 'Cart' },\n { value: 2, icon: 'lucide:map-pin', title: 'Address' },\n { value: 3, icon: 'lucide:credit-card', title: 'Payment' },\n { value: 4, icon: 'lucide:check', title: 'Confirm' }\n ]\n\n // Activity feed\n const activityItems: TimelineItem[] = [\n {\n avatar: { src: 'https://i.pravatar.cc/150?u=john', alt: 'John' },\n title: 'John merged PR #142',\n description: 'feat: add Timeline component',\n date: '5 min ago'\n },\n {\n avatar: { src: 'https://i.pravatar.cc/150?u=sarah', alt: 'Sarah' },\n title: 'Sarah commented',\n description: 'Looks great! Ready for review.',\n date: '15 min ago'\n },\n {\n avatar: { src: 'https://i.pravatar.cc/150?u=mike', alt: 'Mike' },\n title: 'Mike pushed 3 commits',\n description: 'fix: resolve styling issues',\n date: '1 hour ago'\n },\n {\n avatar: { src: 'https://i.pravatar.cc/150?u=emma', alt: 'Emma' },\n title: 'Emma created issue #38',\n description: 'Bug: Timeline not rendering correctly',\n date: '2 hours ago'\n }\n ]\n\n // Per-item ui demo\n const perItemUiItems: TimelineItem[] = [\n {\n value: 1,\n icon: 'lucide:star',\n title: 'Highlighted Step',\n date: 'Special',\n ui: {\n indicator: 'ring-2 ring-warning ring-offset-2 ring-offset-surface-container-high',\n title: 'text-warning font-bold'\n }\n },\n {\n value: 2,\n icon: 'lucide:zap',\n title: 'Normal Step',\n date: 'Default'\n },\n {\n value: 3,\n icon: 'lucide:heart',\n title: 'Custom Separator',\n date: 'Styled',\n ui: { separator: 'bg-gradient-to-b from-error to-primary' }\n }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Timeline</h1>\n <p class=\"text-on-surface-variant\">\n Display a sequence of events with dates, titles, icons or avatars.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"grid gap-6 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Without active state</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline\n items={[\n { title: 'Event 1', date: 'Jan 1' },\n { title: 'Event 2', date: 'Jan 5' },\n { title: 'Event 3', date: 'Jan 10' }\n ]}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With active state</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline items={basicItems} value={2} />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Icons & Avatars -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Icons & Avatars</h2>\n <div class=\"grid gap-6 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With Icons</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline\n items={[\n { value: 1, icon: 'lucide:file-plus', title: 'Created', date: 'Dec 1' },\n { value: 2, icon: 'lucide:edit', title: 'Edited', date: 'Dec 5' },\n {\n value: 3,\n icon: 'lucide:check-circle',\n title: 'Approved',\n date: 'Dec 8'\n }\n ]}\n value={2}\n color=\"success\"\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With Avatars</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline\n items={[\n {\n avatar: { src: 'https://i.pravatar.cc/150?u=a', alt: 'Alice' },\n title: 'Alice started',\n date: '2h ago'\n },\n {\n avatar: { src: 'https://i.pravatar.cc/150?u=b', alt: 'Bob' },\n title: 'Bob reviewed',\n date: '1h ago'\n },\n { avatar: { alt: 'You' }, title: 'You approved', date: 'Just now' }\n ]}\n />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {#each colors as color (color)}\n <div class=\"rounded-lg bg-surface-container-high p-3\">\n <p class=\"mb-2 text-xs font-medium text-on-surface-variant capitalize\">\n {color}\n </p>\n <Timeline\n items={[\n { value: 1, title: 'Done' },\n { value: 2, title: 'Active' },\n { value: 3, title: 'Next' }\n ]}\n value={2}\n {color}\n size=\"sm\"\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"overflow-x-auto\">\n <div\n class=\"flex gap-6 rounded-lg bg-surface-container-high p-4\"\n style=\"min-width: max-content;\"\n >\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <span class=\"text-xs font-medium text-on-surface-variant\">{size}</span>\n <Timeline\n items={[\n { value: 1, icon: 'lucide:check' },\n { value: 2, icon: 'lucide:circle' }\n ]}\n value={1}\n {size}\n />\n </div>\n {/each}\n </div>\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <div class=\"space-y-4\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Horizontal</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline items={basicItems} value={2} orientation=\"horizontal\" />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Horizontal Reversed</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline items={basicItems} value={2} orientation=\"horizontal\" reverse />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Per-item UI Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Per-item UI Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Each item can have its own <code class=\"rounded bg-surface-container-highest px-1\"\n >ui</code\n > prop to override slot classes.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline items={perItemUiItems} value={2} />\n </div>\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <div class=\"grid gap-6 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom Indicator</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline\n items={[\n { value: 1, title: 'Phase 1', date: 'Q1' },\n { value: 2, title: 'Phase 2', date: 'Q2' },\n { value: 3, title: 'Phase 3', date: 'Q3' }\n ]}\n value={2}\n >\n {#snippet indicator({ state, index })}\n <div\n class=\"flex size-8 items-center justify-center rounded-full text-sm font-bold transition-all\"\n class:bg-primary={state !== 'pending'}\n class:text-on-primary={state !== 'pending'}\n class:bg-surface-container-highest={state === 'pending'}\n class:text-on-surface-variant={state === 'pending'}\n class:scale-110={state === 'active'}\n >\n {index + 1}\n </div>\n {/snippet}\n </Timeline>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom Content</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline\n items={[\n {\n value: 'v1',\n icon: 'lucide:package',\n title: 'v1.0.0',\n date: 'Jan 2024'\n },\n {\n value: 'v2',\n icon: 'lucide:sparkles',\n title: 'v2.0.0',\n date: 'Jun 2024'\n },\n {\n value: 'v3',\n icon: 'lucide:rocket',\n title: 'v3.0.0',\n date: 'Coming Soon'\n }\n ]}\n value=\"v2\"\n color=\"info\"\n >\n {#snippet content({ item, state })}\n <div\n class=\"mt-2 rounded-lg border border-outline-variant bg-surface-container p-3\"\n >\n {#if item.value === 'v1'}\n <ul class=\"space-y-1 text-sm text-on-surface-variant\">\n <li>Initial release</li>\n <li>Core components</li>\n </ul>\n {:else if item.value === 'v2'}\n <ul class=\"space-y-1 text-sm text-on-surface-variant\">\n <li>Timeline component</li>\n <li>Performance improvements</li>\n </ul>\n {:else}\n <p class=\"text-sm text-on-surface-variant\">\n {state === 'pending'\n ? 'Exciting features coming!'\n : 'Released!'}\n </p>\n {/if}\n </div>\n {/snippet}\n </Timeline>\n </div>\n </div>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Timeline\n items={[\n { value: 1, icon: 'lucide:star', title: 'Custom Ring', date: 'Styled' },\n { value: 2, icon: 'lucide:zap', title: 'Gradient Line', date: 'Colorful' },\n { value: 3, icon: 'lucide:heart', title: 'Bold Title', date: 'Italic' }\n ]}\n value={2}\n ui={{\n indicator:\n 'ring-2 ring-primary ring-offset-2 ring-offset-surface-container-high',\n separator: 'bg-gradient-to-b from-primary to-tertiary',\n title: 'text-primary font-bold',\n date: 'italic'\n }}\n />\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <!-- Order Tracking -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Order Tracking</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <Timeline items={orderItems} value={orderStep} color=\"success\" />\n <div class=\"mt-4 flex items-center justify-center gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (orderStep = Math.max(1, orderStep - 1))}\n disabled={orderStep === 1}\n >\n Previous\n </Button>\n <span class=\"px-3 text-sm text-on-surface-variant\">Step {orderStep} of 4</span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (orderStep = Math.min(4, orderStep + 1))}\n disabled={orderStep === 4}\n >\n Next\n </Button>\n </div>\n </div>\n </div>\n\n <!-- Checkout Stepper -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Checkout Stepper (Horizontal)</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <Timeline\n items={checkoutItems}\n value={checkoutStep}\n orientation=\"horizontal\"\n size=\"lg\"\n />\n <div class=\"mt-4 flex items-center justify-between\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onclick={() => (checkoutStep = Math.max(1, checkoutStep - 1))}\n disabled={checkoutStep === 1}\n >\n Back\n </Button>\n <Button\n size=\"sm\"\n onclick={() => (checkoutStep = Math.min(4, checkoutStep + 1))}\n disabled={checkoutStep === 4}\n >\n {checkoutStep === 3 ? 'Place Order' : 'Continue'}\n </Button>\n </div>\n </div>\n </div>\n\n <!-- Activity Feed -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Activity Feed</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <Timeline items={activityItems} size=\"lg\" />\n </div>\n </div>\n </section>\n</div>\n",
|
|
106
131
|
"user": "<script lang=\"ts\">\n import { User, Separator } from '$lib/index.js'\n\n const sizes = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">User</h1>\n <p class=\"text-on-surface-variant\">\n Displays user information with avatar, name, and description.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"grid gap-6 lg:grid-cols-3\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With avatar & description</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"John Doe\"\n description=\"Software Engineer\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=john' }}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Avatar with initials</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User name=\"Alex Chen\" description=\"Product Manager\" avatar={{ alt: 'AC' }} />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Name only</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User name=\"Jane Smith\" avatar={{ src: 'https://i.pravatar.cc/128?u=jane' }} />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-10 text-sm font-medium text-on-surface-variant\">{size}</span>\n <User\n name=\"John Doe\"\n description=\"Software Engineer\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=size-{size}' }}\n {size}\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <div class=\"grid gap-6 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Horizontal (default)</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"John Doe\"\n description=\"Software Engineer\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=h' }}\n orientation=\"horizontal\"\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Vertical</p>\n <div class=\"flex justify-center rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"John Doe\"\n description=\"Software Engineer\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=v' }}\n orientation=\"vertical\"\n />\n </div>\n </div>\n </div>\n </section>\n\n <!-- With Chip -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Chip</h2>\n <div class=\"grid gap-6 sm:grid-cols-2 lg:grid-cols-4\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Online</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Alice\"\n description=\"Available\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=online' }}\n chip={{ color: 'success' }}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Busy</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Bob\"\n description=\"In a meeting\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=busy' }}\n chip={{ color: 'error' }}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Away</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Carol\"\n description=\"Be right back\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=away' }}\n chip={{ color: 'warning' }}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Default chip</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Dave\"\n description=\"Offline\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=offline' }}\n chip={true}\n />\n </div>\n </div>\n </div>\n </section>\n\n <!-- As Link -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">As Link</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"John Doe\"\n description=\"View profile →\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=link' }}\n href=\"/user\"\n />\n </div>\n </section>\n\n <!-- Clickable -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Clickable</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Click me\"\n description=\"Has onclick handler\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=click' }}\n onclick={() => alert('Clicked!')}\n />\n </div>\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <div class=\"grid gap-6 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom avatar slot</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User name=\"Custom Avatar\" description=\"With emoji avatar\">\n {#snippet avatarSlot()}\n <div\n class=\"flex size-10 items-center justify-center rounded-full bg-primary text-lg\"\n >\n 🎨\n </div>\n {/snippet}\n </User>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom name & description</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User avatar={{ src: 'https://i.pravatar.cc/128?u=slot' }}>\n {#snippet nameSlot()}\n <p class=\"text-sm font-bold text-primary\">Premium User</p>\n {/snippet}\n {#snippet descriptionSlot()}\n <div class=\"flex items-center gap-1\">\n <span class=\"inline-block size-2 rounded-full bg-success\"></span>\n <span class=\"text-xs text-on-surface-variant\">Active now</span>\n </div>\n {/snippet}\n </User>\n </div>\n </div>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"grid gap-6 lg:grid-cols-3\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom text styles</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Styled Name\"\n description=\"Styled description\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=ui1' }}\n ui={{\n name: 'text-primary font-bold',\n description: 'text-tertiary italic'\n }}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Bordered root</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Bordered\"\n description=\"With root override\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=ui2' }}\n ui={{\n root: 'border border-outline-variant rounded-xl p-3'\n }}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom avatar class</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <User\n name=\"Ring Avatar\"\n description=\"Avatar with ring\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=ui3' }}\n ui={{\n avatar: 'ring-2 ring-primary ring-offset-2 ring-offset-surface-container-high'\n }}\n />\n </div>\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <!-- User List -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">User List</p>\n <div\n class=\"max-w-sm space-y-1 rounded-lg border border-outline-variant bg-surface-container p-2\"\n >\n {#each [{ name: 'Alice Johnson', desc: 'Admin', u: 'alice' }, { name: 'Bob Williams', desc: 'Editor', u: 'bob' }, { name: 'Carol Davis', desc: 'Viewer', u: 'carol' }] as item (item.u)}\n <User\n name={item.name}\n description={item.desc}\n avatar={{\n src: `https://i.pravatar.cc/128?u=${item.u}`,\n alt: item.name\n }}\n onclick={() => alert(item.name)}\n size=\"sm\"\n />\n {/each}\n </div>\n </div>\n\n <!-- Comment Header -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Comment Header</p>\n <div class=\"max-w-md rounded-lg border border-outline-variant bg-surface-container p-4\">\n <User\n name=\"Sarah Chen\"\n description=\"2 hours ago\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=sarah' }}\n size=\"sm\"\n />\n <p class=\"mt-2 text-sm text-on-surface-variant\">\n This looks great! I love the new design direction. The component feels much more\n polished now.\n </p>\n </div>\n </div>\n\n <!-- Profile Card -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Profile Card</p>\n <div class=\"max-w-xs rounded-lg border border-outline-variant bg-surface-container p-6\">\n <User\n name=\"Emily Parker\"\n description=\"Senior Developer at Acme Corp\"\n avatar={{ src: 'https://i.pravatar.cc/128?u=emily' }}\n orientation=\"vertical\"\n size=\"xl\"\n chip={{ color: 'success' }}\n />\n </div>\n </div>\n\n <!-- Team Members -->\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Team Members</p>\n <div class=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {#each [{ name: 'Mike Ross', role: 'Frontend', u: 'mike', color: 'primary' as const }, { name: 'Rachel Zane', role: 'Backend', u: 'rachel', color: 'secondary' as const }, { name: 'Harvey Specter', role: 'Lead', u: 'harvey', color: 'tertiary' as const }] as member (member.u)}\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <User\n name={member.name}\n description={member.role}\n avatar={{ src: `https://i.pravatar.cc/128?u=${member.u}` }}\n chip={{ color: member.color }}\n href=\"/user\"\n />\n </div>\n {/each}\n </div>\n </div>\n </section>\n</div>\n",
|
|
107
132
|
"table": "<script lang=\"ts\">\n import {\n Table,\n Badge,\n Avatar,\n Button,\n Input,\n Icon,\n Pagination,\n DropdownMenu,\n type TableColumn,\n type SortState,\n type TableCellSlotProps,\n type TableFooterSlotProps,\n type DropdownMenuItem\n } from '$lib/index.js'\n\n // ==================== Types ====================\n\n interface User {\n id: string\n name: string\n email: string\n role: string\n status: 'active' | 'inactive' | 'pending'\n avatar: string\n }\n\n interface Product {\n name: string\n category: string\n price: number\n stock: number\n }\n\n interface Department {\n id: string\n name: string\n manager: string\n employees: number\n budget: string\n children?: Department[]\n }\n\n // ==================== Data ====================\n\n const users: User[] = [\n {\n id: '1',\n name: 'Alice Johnson',\n email: 'alice@example.com',\n role: 'Admin',\n status: 'active',\n avatar: 'https://i.pravatar.cc/150?u=alice'\n },\n {\n id: '2',\n name: 'Bob Smith',\n email: 'bob@example.com',\n role: 'User',\n status: 'inactive',\n avatar: 'https://i.pravatar.cc/150?u=bob'\n },\n {\n id: '3',\n name: 'Charlie Brown',\n email: 'charlie@example.com',\n role: 'Editor',\n status: 'active',\n avatar: 'https://i.pravatar.cc/150?u=charlie'\n },\n {\n id: '4',\n name: 'Diana Prince',\n email: 'diana@example.com',\n role: 'Admin',\n status: 'pending',\n avatar: 'https://i.pravatar.cc/150?u=diana'\n },\n {\n id: '5',\n name: 'Eve Wilson',\n email: 'eve@example.com',\n role: 'User',\n status: 'active',\n avatar: 'https://i.pravatar.cc/150?u=eve'\n }\n ]\n\n const manyUsers: User[] = Array.from({ length: 50 }, (_, i) => ({\n id: String(i + 1),\n name: `User ${i + 1}`,\n email: `user${i + 1}@example.com`,\n role: ['Admin', 'User', 'Editor'][i % 3],\n status: (['active', 'inactive', 'pending'] as const)[i % 3],\n avatar: `https://i.pravatar.cc/150?u=user${i + 1}`\n }))\n\n const products: Product[] = [\n { name: 'MacBook Pro', category: 'Electronics', price: 2499, stock: 12 },\n { name: 'iPhone 16', category: 'Electronics', price: 999, stock: 45 },\n { name: 'AirPods Pro', category: 'Accessories', price: 249, stock: 100 },\n { name: 'Magic Keyboard', category: 'Accessories', price: 299, stock: 30 },\n { name: 'Studio Display', category: 'Electronics', price: 1599, stock: 8 }\n ]\n\n const departments: Department[] = [\n {\n id: 'eng',\n name: 'Engineering',\n manager: 'Alice Johnson',\n employees: 42,\n budget: '$2.1M',\n children: [\n {\n id: 'eng-fe',\n name: 'Frontend',\n manager: 'Bob Smith',\n employees: 15,\n budget: '$750K'\n },\n {\n id: 'eng-be',\n name: 'Backend',\n manager: 'Charlie Brown',\n employees: 18,\n budget: '$900K'\n },\n {\n id: 'eng-infra',\n name: 'Infrastructure',\n manager: 'Diana Prince',\n employees: 9,\n budget: '$450K'\n }\n ]\n },\n {\n id: 'design',\n name: 'Design',\n manager: 'Eve Wilson',\n employees: 12,\n budget: '$600K',\n children: [\n {\n id: 'design-ux',\n name: 'UX Research',\n manager: 'Frank Lee',\n employees: 5,\n budget: '$250K'\n },\n {\n id: 'design-ui',\n name: 'UI Design',\n manager: 'Grace Kim',\n employees: 7,\n budget: '$350K'\n }\n ]\n },\n {\n id: 'marketing',\n name: 'Marketing',\n manager: 'Henry Zhang',\n employees: 8,\n budget: '$400K'\n }\n ]\n\n // Flatten departments with parent-child for expandable\n const flatDepartments = $derived.by(() => {\n const result: (Department & { level: number; parentId?: string })[] = []\n for (const dept of departments) {\n result.push({ ...dept, level: 0 })\n }\n return result\n })\n\n const statusColor: Record<string, 'success' | 'error' | 'warning'> = {\n active: 'success',\n inactive: 'error',\n pending: 'warning'\n }\n\n const roleColor: Record<string, 'primary' | 'secondary' | 'tertiary'> = {\n Admin: 'primary',\n User: 'secondary',\n Editor: 'tertiary'\n }\n\n // ==================== Rich Columns ====================\n const richColumns: TableColumn<User>[] = [\n { key: 'id', label: '#' },\n { key: 'name', label: 'Name', cell: userCell },\n { key: 'email', label: 'Email' },\n { key: 'role', label: 'Role', cell: roleCell },\n { key: 'status', label: 'Status', cell: statusCell }\n ]\n\n // ==================== Sorting ====================\n let sorting: SortState = $state([{ key: 'name', direction: 'asc' }])\n\n const sortColumns: TableColumn<User>[] = [\n { key: 'id', label: '#' },\n { key: 'name', label: 'Name', sortable: true, cell: userCell },\n { key: 'email', label: 'Email' },\n { key: 'role', label: 'Role', sortable: true, cell: roleCell },\n { key: 'status', label: 'Status', sortable: true, cell: statusCell }\n ]\n\n // ==================== Row Actions ====================\n function getRowActions(row: User): DropdownMenuItem[] {\n return [\n {\n label: 'View profile',\n icon: 'lucide:user',\n onSelect: () => alert(`View ${row.name}`)\n },\n {\n label: 'Send email',\n icon: 'lucide:mail',\n onSelect: () => alert(`Email ${row.email}`)\n },\n { type: 'separator' },\n { label: 'Edit', icon: 'lucide:pencil', onSelect: () => alert(`Edit ${row.name}`) },\n {\n label: 'Delete',\n icon: 'lucide:trash-2',\n color: 'error',\n onSelect: () => alert(`Delete ${row.name}`)\n }\n ]\n }\n\n const actionColumns: TableColumn<User>[] = [\n { key: 'id', label: '#' },\n { key: 'name', label: 'Name', cell: userCell },\n { key: 'email', label: 'Email' },\n { key: 'role', label: 'Role', cell: roleCell },\n { key: 'status', label: 'Status', cell: statusCell },\n { key: 'avatar', label: '', cell: actionsCell, align: 'right', width: 60 }\n ]\n\n // ==================== Column Pinning ====================\n const pinColumns: TableColumn<User>[] = [\n { key: 'id', label: '#', width: 60 },\n { key: 'name', label: 'Name', cell: userCell, width: 200 },\n { key: 'email', label: 'Email', width: 220 },\n { key: 'role', label: 'Role', cell: roleCell, width: 120 },\n { key: 'status', label: 'Status', cell: statusCell, width: 120 },\n { key: 'avatar', label: '', cell: actionsCell, align: 'right', width: 80 }\n ]\n\n // ==================== Row Pinning ====================\n let pinnedRows: (string | number)[] = $state(['1', '3'])\n\n const rowPinColumns: TableColumn<User>[] = [\n { key: 'id', label: '#' },\n { key: 'name', label: 'Name', cell: userCell },\n { key: 'email', label: 'Email' },\n { key: 'role', label: 'Role', cell: roleCell },\n { key: 'status', label: 'Status', cell: statusCell },\n { key: 'avatar', label: '', cell: pinActionCell, align: 'right', width: 60 }\n ]\n\n // ==================== Expandable Rows ====================\n let expandedRows: (string | number)[] = $state([])\n let expandedDepts: (string | number)[] = $state([])\n\n const deptColumns: TableColumn<Department>[] = [\n { key: 'id', label: '', cell: deptExpandCell, width: 40 },\n { key: 'name', label: 'Department', cell: deptNameCell },\n { key: 'manager', label: 'Manager' },\n { key: 'employees', label: 'Employees', align: 'right' },\n { key: 'budget', label: 'Budget', align: 'right' }\n ]\n\n // ==================== Row Selection ====================\n let selectedRows: User[] = $state([])\n\n // ==================== Column Visibility ====================\n let columnVisibility: Record<string, boolean> = $state({})\n\n const visibilityItems = [\n { value: 'id', label: '#' },\n { value: 'name', label: 'Name' },\n { value: 'email', label: 'Email' },\n { value: 'role', label: 'Role' },\n { value: 'status', label: 'Status' }\n ]\n\n function toggleColumnVisibility(col: string) {\n columnVisibility = {\n ...columnVisibility,\n [col]: columnVisibility[col] === false\n }\n }\n\n // ==================== Pagination ====================\n let paginationPage = $state(1)\n const pageSize = 10\n const tablePage = $derived(paginationPage - 1)\n\n // ==================== Row Pinning + Pagination ====================\n let pinPagPage = $state(1)\n const pinPagTablePage = $derived(pinPagPage - 1)\n let pinPagPinnedRows: (string | number)[] = $state(['1', '5', '12'])\n\n const rowPinPagColumns: TableColumn<User>[] = [\n { key: 'id', label: '#' },\n { key: 'name', label: 'Name', cell: userCell },\n { key: 'email', label: 'Email' },\n { key: 'role', label: 'Role', cell: roleCell },\n { key: 'status', label: 'Status', cell: statusCell },\n { key: 'avatar', label: '', cell: pinPagActionCell, align: 'right', width: 60 }\n ]\n\n // ==================== Selection + Pagination ====================\n let selPagPage = $state(1)\n const selPagTablePage = $derived(selPagPage - 1)\n let selPagSelected: User[] = $state([])\n\n // ==================== Global Filter ====================\n let globalFilter = $state('')\n\n // ==================== Loading (Replace data) ====================\n let isLoadingReplace = $state(false)\n function simulateLoadingReplace() {\n isLoadingReplace = true\n setTimeout(() => (isLoadingReplace = false), 2000)\n }\n\n // ==================== Loading (Overlay) ====================\n let isLoadingOverlay = $state(false)\n function simulateLoadingOverlay() {\n isLoadingOverlay = true\n setTimeout(() => (isLoadingOverlay = false), 2000)\n }\n\n // ==================== Row Click ====================\n let selectedUser: User | undefined = $state(undefined)\n\n // ==================== Column Resizing ====================\n let columnSizing: Record<string, number> = $state({})\n\n const resizableColumns: TableColumn<User>[] = [\n { key: 'id', label: '#', width: 60 },\n { key: 'name', label: 'Name', cell: userCell, resizable: true, width: 200, minWidth: 120 },\n { key: 'email', label: 'Email', resizable: true, width: 220, minWidth: 150 },\n { key: 'role', label: 'Role', cell: roleCell, resizable: true, width: 120, minWidth: 80 },\n { key: 'status', label: 'Status', cell: statusCell, width: 120 }\n ]\n\n // ==================== Hover / Contextmenu ====================\n let hoveredUser: User | null = $state(null)\n let contextUser: { user: User; x: number; y: number } | null = $state(null)\n\n // ==================== Product columns ====================\n const productColumns: TableColumn<Product>[] = [\n { key: 'name', label: 'Product' },\n { key: 'category', label: 'Category', cell: categoryCell },\n { key: 'price', label: 'Price', cell: priceCell, align: 'right' },\n { key: 'stock', label: 'Stock', cell: stockCell, align: 'right' }\n ]\n</script>\n\n<div class=\"space-y-16\">\n <div>\n <h1 class=\"text-3xl font-bold text-on-surface\">Table</h1>\n <p class=\"mt-2 text-on-surface-variant\">Custom implementation — no external dependency.</p>\n </div>\n\n <!-- ==================== BASIC ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Basic</h2>\n <p class=\"text-sm text-on-surface-variant\">Auto-generates columns from data keys.</p>\n <Table\n data={[\n { name: 'Alice', email: 'alice@example.com', role: 'Admin' },\n { name: 'Bob', email: 'bob@example.com', role: 'User' },\n { name: 'Charlie', email: 'charlie@example.com', role: 'Editor' }\n ]}\n />\n </section>\n\n <!-- ==================== RICH COLUMNS ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Rich Columns</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Custom cell snippets with Avatar, Badge components.\n </p>\n <Table data={users} columns={richColumns} rowKey=\"id\" />\n </section>\n\n <!-- ==================== SORTING (default asc on name) ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Sorting</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Default sort on <strong>Name (asc)</strong>. Click headers to toggle.\n </p>\n <Table data={users} columns={sortColumns} bind:sorting rowKey=\"id\" />\n {#if sorting.length > 0}\n <p class=\"text-xs text-on-surface-variant\">\n Sorting: <strong>{sorting[0].key}</strong> ({sorting[0].direction})\n </p>\n {/if}\n </section>\n\n <!-- ==================== GLOBAL FILTER ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Global Filter</h2>\n <p class=\"text-sm text-on-surface-variant\">Filter across all columns.</p>\n <Input\n placeholder=\"Search users...\"\n bind:value={globalFilter}\n leadingIcon=\"lucide:search\"\n variant=\"outline\"\n class=\"max-w-sm\"\n />\n <Table data={users} columns={richColumns} bind:globalFilter rowKey=\"id\" />\n </section>\n\n <!-- ==================== ROW ACTIONS ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Row Actions</h2>\n <p class=\"text-sm text-on-surface-variant\">\n DropdownMenu in the last column for per-row actions.\n </p>\n <Table data={users} columns={actionColumns} rowKey=\"id\" />\n </section>\n\n <!-- ==================== COLUMN PINNING ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Column Pinning</h2>\n <p class=\"text-sm text-on-surface-variant\">\n \"#\" pinned left, \"Actions\" pinned right. Scroll horizontally.\n </p>\n <div class=\"max-w-lg\">\n <Table\n data={users}\n columns={pinColumns}\n columnPinning={{ left: ['id'], right: ['avatar'] }}\n rowKey=\"id\"\n />\n </div>\n </section>\n\n <!-- ==================== ROW PINNING ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Row Pinning</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pin rows to the top. Alice and Charlie are pinned by default. Click the pin icon to\n toggle.\n </p>\n <Table data={users} columns={rowPinColumns} bind:pinnedRows rowKey=\"id\" />\n {#if pinnedRows.length > 0}\n <p class=\"text-xs text-on-surface-variant\">\n Pinned: <strong>{pinnedRows.join(', ')}</strong>\n </p>\n {/if}\n </section>\n\n <!-- ==================== ROW PINNING + PAGINATION ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Row Pinning + Pagination</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pinned rows (#1, #5, #12) stay at the top regardless of the current page.\n </p>\n <Table\n data={manyUsers}\n columns={rowPinPagColumns}\n bind:pinnedRows={pinPagPinnedRows}\n page={pinPagTablePage}\n {pageSize}\n rowKey=\"id\"\n />\n <div class=\"flex items-center justify-between\">\n {#if pinPagPinnedRows.length > 0}\n <Badge label=\"{pinPagPinnedRows.length} pinned\" variant=\"soft\" color=\"primary\" />\n {:else}\n <span class=\"text-sm text-on-surface-variant/50\">No pinned rows</span>\n {/if}\n <Pagination\n bind:page={pinPagPage}\n total={manyUsers.length}\n itemsPerPage={pageSize}\n showEdges\n size=\"sm\"\n />\n </div>\n </section>\n\n <!-- ==================== EXPANDABLE ROWS ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Expandable Rows</h2>\n <p class=\"text-sm text-on-surface-variant\">Click the chevron to expand row details.</p>\n <Table\n data={users}\n columns={[\n { key: 'id', label: '', cell: expandCell, width: 40 },\n ...richColumns.slice(1)\n ]}\n bind:expandedRows\n rowKey=\"id\"\n >\n {#snippet expandedSlot({ row })}\n <div class=\"flex items-start gap-4 p-3\">\n <Avatar src={row.avatar} alt={row.name} size=\"lg\" />\n <div class=\"space-y-2\">\n <div>\n <p class=\"font-semibold text-on-surface\">{row.name}</p>\n <p class=\"text-sm text-on-surface-variant\">{row.email}</p>\n </div>\n <div class=\"flex gap-2\">\n <Badge\n label={row.role}\n variant=\"soft\"\n color={roleColor[row.role] ?? 'secondary'}\n />\n <Badge\n label={row.status}\n variant=\"subtle\"\n color={statusColor[row.status] ?? 'surface'}\n />\n </div>\n </div>\n </div>\n {/snippet}\n </Table>\n </section>\n\n <!-- ==================== EXPANDABLE: PARENT-CHILD ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Parent-Child Rows</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Expand departments to see sub-departments as a nested table.\n </p>\n <Table\n data={flatDepartments}\n columns={deptColumns}\n bind:expandedRows={expandedDepts}\n rowKey=\"id\"\n >\n {#snippet expandedSlot({ row })}\n {#if row.children && row.children.length > 0}\n <Table\n data={row.children}\n columns={[\n { key: 'name', label: 'Sub-department' },\n { key: 'manager', label: 'Manager' },\n { key: 'employees', label: 'Employees', align: 'right' },\n { key: 'budget', label: 'Budget', align: 'right' }\n ]}\n rowKey=\"id\"\n ui={{ root: 'border-0 rounded-none shadow-none' }}\n />\n {:else}\n <p class=\"py-2 text-sm text-on-surface-variant\">No sub-departments</p>\n {/if}\n {/snippet}\n </Table>\n </section>\n\n <!-- ==================== ROW SELECTION ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Row Selection</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Built-in checkbox column with select-all support.\n </p>\n <Table\n data={users}\n columns={richColumns}\n selection=\"multiple\"\n bind:selectedRows\n rowKey=\"id\"\n />\n {#if selectedRows.length > 0}\n <div class=\"flex items-center gap-2\">\n <Badge label=\"{selectedRows.length} selected\" variant=\"soft\" color=\"primary\" />\n <span class=\"text-sm text-on-surface-variant\"\n >{selectedRows.map((u) => u.name).join(', ')}</span\n >\n </div>\n {:else}\n <p class=\"text-sm text-on-surface-variant/50\">No rows selected</p>\n {/if}\n </section>\n\n <!-- ==================== SELECTION + PAGINATION ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Selection + Pagination</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Selections persist across pages. Select some rows, then navigate.\n </p>\n <Table\n data={manyUsers}\n columns={richColumns}\n selection=\"multiple\"\n bind:selectedRows={selPagSelected}\n page={selPagTablePage}\n {pageSize}\n rowKey=\"id\"\n />\n <div class=\"flex items-center justify-between\">\n {#if selPagSelected.length > 0}\n <Badge\n label=\"{selPagSelected.length} selected across pages\"\n variant=\"soft\"\n color=\"primary\"\n />\n {:else}\n <span class=\"text-sm text-on-surface-variant/50\">No rows selected</span>\n {/if}\n <Pagination\n bind:page={selPagPage}\n total={manyUsers.length}\n itemsPerPage={pageSize}\n showEdges\n size=\"sm\"\n />\n </div>\n </section>\n\n <!-- ==================== COLUMN VISIBILITY ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Column Visibility</h2>\n <p class=\"text-sm text-on-surface-variant\">Toggle columns on/off.</p>\n <div class=\"flex flex-wrap gap-1.5\">\n {#each visibilityItems as item (item.value)}\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-1 text-xs transition-colors {columnVisibility[\n item.value\n ] === false\n ? 'bg-surface-container text-on-surface-variant line-through'\n : 'bg-primary/10 text-primary'}\"\n onclick={() => toggleColumnVisibility(item.value)}\n >\n {item.label}\n </button>\n {/each}\n </div>\n <Table data={users} columns={richColumns} bind:columnVisibility rowKey=\"id\" />\n </section>\n\n <!-- ==================== PAGINATION ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Pagination</h2>\n <Table data={manyUsers} columns={richColumns} page={tablePage} {pageSize} rowKey=\"id\" />\n <div class=\"flex items-center justify-between\">\n <p class=\"text-sm text-on-surface-variant\">\n {tablePage * pageSize + 1}–{Math.min((tablePage + 1) * pageSize, manyUsers.length)} of\n {manyUsers.length}\n </p>\n <Pagination\n bind:page={paginationPage}\n total={manyUsers.length}\n itemsPerPage={pageSize}\n showEdges\n size=\"sm\"\n />\n </div>\n </section>\n\n <!-- ==================== ROW CLICK ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Row Click</h2>\n <Table\n data={users}\n columns={richColumns}\n onRowClick={(row) => (selectedUser = row)}\n rowKey=\"id\"\n />\n {#if selectedUser}\n <div\n class=\"flex items-center gap-3 rounded-xl border border-primary/20 bg-primary-container/15 p-3\"\n >\n <Avatar src={selectedUser.avatar} alt={selectedUser.name} size=\"sm\" />\n <div>\n <p class=\"text-sm font-medium text-on-surface\">{selectedUser.name}</p>\n <p class=\"text-xs text-on-surface-variant\">{selectedUser.email}</p>\n </div>\n <Badge\n label={selectedUser.role}\n variant=\"soft\"\n color={roleColor[selectedUser.role] ?? 'secondary'}\n class=\"ml-auto\"\n />\n </div>\n {/if}\n </section>\n\n <!-- ==================== ROW HOVER ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Row Hover</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Hover over rows to see the callback in action.\n </p>\n <Table\n data={users}\n columns={richColumns}\n rowKey=\"id\"\n onRowHover={(row) => (hoveredUser = row)}\n />\n {#if hoveredUser}\n <div\n class=\"flex items-center gap-3 rounded-xl border border-outline-variant/30 bg-surface-container p-3\"\n >\n <Avatar src={hoveredUser.avatar} alt={hoveredUser.name} size=\"xs\" />\n <span class=\"text-sm text-on-surface\"\n >Hovering: <strong>{hoveredUser.name}</strong></span\n >\n <Badge\n label={hoveredUser.status}\n variant=\"subtle\"\n color={statusColor[hoveredUser.status] ?? 'surface'}\n />\n </div>\n {:else}\n <p class=\"text-sm text-on-surface-variant/50\">Hover over a row</p>\n {/if}\n </section>\n\n <!-- ==================== CONTEXT MENU ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Context Menu (Right-Click)</h2>\n <p class=\"text-sm text-on-surface-variant\">Right-click a row to trigger the callback.</p>\n <Table\n data={users}\n columns={richColumns}\n rowKey=\"id\"\n onRowContextmenu={(row, _index, e) => {\n e.preventDefault()\n contextUser = { user: row, x: e.clientX, y: e.clientY }\n setTimeout(() => (contextUser = null), 2000)\n }}\n />\n {#if contextUser}\n <div\n class=\"flex items-center gap-3 rounded-xl border border-outline-variant/30 bg-surface-container p-3\"\n >\n <Icon name=\"lucide:mouse-pointer\" class=\"size-4 text-on-surface-variant\" />\n <span class=\"text-sm text-on-surface\">\n Right-clicked: <strong>{contextUser.user.name}</strong>\n at ({contextUser.x}, {contextUser.y})\n </span>\n </div>\n {/if}\n </section>\n\n <!-- ==================== BODY SLOTS ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Body Top / Bottom Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Insert custom rows before and after data rows.\n </p>\n <Table data={products} columns={productColumns}>\n {#snippet bodyTopSlot()}\n <tr>\n <td\n colspan={4}\n class=\"border-b border-primary/10 bg-primary/5 px-4 py-2 text-xs font-medium text-primary\"\n >\n <Icon name=\"lucide:info\" class=\"-mt-0.5 mr-1 inline-block size-3.5\" />\n Prices updated March 2026. All items in stock.\n </td>\n </tr>\n {/snippet}\n {#snippet bodyBottomSlot()}\n <tr>\n <td\n colspan={4}\n class=\"border-t border-outline-variant/30 bg-surface-container-lowest px-4 py-2 text-xs text-on-surface-variant\"\n >\n <Icon\n name=\"lucide:arrow-right\"\n class=\"-mt-0.5 mr-1 inline-block size-3.5\"\n />\n Showing {products.length} of {products.length} products\n </td>\n </tr>\n {/snippet}\n </Table>\n </section>\n\n <!-- ==================== POLYMORPHIC (as prop) ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Polymorphic Root (as prop)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Render as <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n ><section></code\n >\n instead of\n <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n ><div></code\n >.\n </p>\n <Table\n as=\"section\"\n data={products.slice(0, 3)}\n columns={productColumns}\n caption=\"Q1 2026 Product Summary\"\n />\n </section>\n\n <!-- ==================== STRIPED ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Striped</h2>\n <Table data={products} columns={productColumns} striped />\n </section>\n\n <!-- ==================== COLUMN RESIZING ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Column Resizing</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Drag the right edge of Name, Email, or Role headers to resize.\n </p>\n <Table data={users} columns={resizableColumns} bind:columnSizing rowKey=\"id\" />\n {#if Object.keys(columnSizing).length > 0}\n <p class=\"text-xs text-on-surface-variant\">\n Sizes: {Object.entries(columnSizing)\n .map(([k, v]) => `${k}: ${Math.round(v)}px`)\n .join(', ')}\n </p>\n {/if}\n </section>\n\n <!-- ==================== STICKY HEADER ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Sticky Header</h2>\n <Table\n data={[...users, ...users, ...users]}\n columns={richColumns}\n sticky=\"header\"\n class=\"max-h-72\"\n />\n </section>\n\n <!-- ==================== LOADING (REPLACE DATA) ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Loading — Replace Data</h2>\n <p class=\"text-sm text-on-surface-variant\">Data is hidden during loading.</p>\n <Button onclick={simulateLoadingReplace} size=\"sm\" variant=\"outline\" color=\"surface\">\n Simulate Loading (2s)\n </Button>\n <Table\n data={isLoadingReplace ? [] : users}\n columns={richColumns}\n loading={isLoadingReplace}\n rowKey=\"id\"\n >\n {#snippet loadingSlot()}\n <div class=\"flex items-center justify-center gap-2\">\n <Icon name=\"lucide:loader-2\" class=\"size-5 animate-spin text-primary\" />\n <span class=\"text-sm text-on-surface-variant\">Loading data...</span>\n </div>\n {/snippet}\n </Table>\n </section>\n\n <!-- ==================== LOADING (OVERLAY) ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Loading — Overlay</h2>\n <p class=\"text-sm text-on-surface-variant\">Data stays visible with a frosted overlay.</p>\n <Button onclick={simulateLoadingOverlay} size=\"sm\" variant=\"outline\" color=\"surface\">\n Simulate Loading (2s)\n </Button>\n <Table data={users} columns={richColumns} loading={isLoadingOverlay} rowKey=\"id\" />\n </section>\n\n <!-- ==================== LOADING ANIMATIONS ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Loading Animations</h2>\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2\">\n {#each ['carousel', 'carousel-inverse', 'swing', 'elastic'] as anim (anim)}\n <div class=\"space-y-1\">\n <p class=\"text-xs font-medium text-on-surface-variant\">{anim}</p>\n <Table\n data={products.slice(0, 2)}\n columns={productColumns}\n loading\n loadingAnimation={anim as\n | 'carousel'\n | 'carousel-inverse'\n | 'swing'\n | 'elastic'}\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- ==================== EMPTY ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Empty State</h2>\n <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2\">\n <div class=\"space-y-1\">\n <p class=\"text-xs font-medium text-on-surface-variant\">Default</p>\n <Table data={[]} columns={richColumns} />\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-xs font-medium text-on-surface-variant\">Custom</p>\n <Table data={[]} columns={richColumns}>\n {#snippet emptySlot()}\n <div class=\"flex flex-col items-center gap-2\">\n <Icon name=\"lucide:inbox\" class=\"size-8 text-on-surface-variant/40\" />\n <p class=\"text-sm text-on-surface-variant\">No users found</p>\n </div>\n {/snippet}\n </Table>\n </div>\n </div>\n </section>\n\n <!-- ==================== CUSTOM UI OVERRIDES ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Custom UI Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >ui</code\n > prop to override slot classes.\n </p>\n <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div class=\"space-y-1\">\n <p class=\"text-xs font-medium text-on-surface-variant\">\n Bordered rows + colored header\n </p>\n <Table\n data={products}\n columns={productColumns}\n ui={{\n root: 'border-2 border-primary/20',\n thead: 'bg-primary/5',\n th: 'text-primary',\n tr: 'border-b border-primary/10'\n }}\n />\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-xs font-medium text-on-surface-variant\">Compact + no border</p>\n <Table\n data={products}\n columns={productColumns}\n ui={{\n root: 'border-0 rounded-none',\n th: 'py-2 px-3 text-[10px]',\n td: 'py-2 px-3 text-xs'\n }}\n />\n </div>\n </div>\n </section>\n\n <!-- ==================== CUSTOM HEADER SLOT ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Custom Header Slot</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the global <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >headerSlot</code\n > to customize all column headers.\n </p>\n <Table data={users.slice(0, 3)} columns={richColumns} rowKey=\"id\">\n {#snippet headerSlot({ column })}\n <div class=\"flex items-center gap-1.5\">\n <Icon\n name={column.key === 'name'\n ? 'lucide:user'\n : column.key === 'email'\n ? 'lucide:mail'\n : column.key === 'role'\n ? 'lucide:shield'\n : column.key === 'status'\n ? 'lucide:activity'\n : 'lucide:hash'}\n class=\"size-3.5 text-primary\"\n />\n <span>{column.label ?? column.key}</span>\n </div>\n {/snippet}\n </Table>\n </section>\n\n <!-- ==================== CUSTOM CELL SLOT ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Custom Cell Slot</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the global <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >cellSlot</code\n > to customize all cells at once.\n </p>\n <Table\n data={[\n { name: 'Alice', score: 95, grade: 'A' },\n { name: 'Bob', score: 72, grade: 'B' },\n { name: 'Charlie', score: 58, grade: 'C' }\n ]}\n >\n {#snippet cellSlot({ column, value })}\n {#if column.key === 'score'}\n <div class=\"flex items-center gap-2\">\n <div\n class=\"h-1.5 w-20 overflow-hidden rounded-full bg-surface-container-highest\"\n >\n <div\n class=\"h-full rounded-full transition-all {Number(value) >= 80\n ? 'bg-success'\n : Number(value) >= 60\n ? 'bg-warning'\n : 'bg-error'}\"\n style=\"width: {value}%\"\n ></div>\n </div>\n <span class=\"text-xs font-medium\">{value}</span>\n </div>\n {:else if column.key === 'grade'}\n <Badge\n label={String(value)}\n variant=\"soft\"\n color={value === 'A' ? 'success' : value === 'B' ? 'primary' : 'warning'}\n />\n {:else}\n <span class=\"font-medium\">{value}</span>\n {/if}\n {/snippet}\n </Table>\n </section>\n\n <!-- ==================== FOOTER SLOT ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Footer (Aggregation)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use per-column <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >footer</code\n > snippet for totals/aggregation.\n </p>\n <Table\n data={products}\n columns={[\n { key: 'name', label: 'Product', footer: footerLabel },\n { key: 'category', label: 'Category', cell: categoryCell },\n {\n key: 'price',\n label: 'Price',\n cell: priceCell,\n align: 'right',\n footer: footerTotal\n },\n { key: 'stock', label: 'Stock', cell: stockCell, align: 'right', footer: footerSum }\n ]}\n />\n </section>\n\n <!-- ==================== CAPTION ==================== -->\n <section class=\"space-y-4\">\n <h2 class=\"text-xl font-semibold text-on-surface\">Caption (Accessibility)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >caption</code\n >\n prop renders a sr-only caption for screen readers. Or use\n <code class=\"rounded-md bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >captionSlot</code\n > for visible captions.\n </p>\n <Table\n data={products.slice(0, 3)}\n columns={productColumns}\n caption=\"Product inventory as of March 2026\"\n >\n {#snippet captionSlot()}\n <div\n class=\"not-sr-only border-b border-outline-variant/30 px-4 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >\n <Icon name=\"lucide:table-2\" class=\"-mt-0.5 mr-1.5 inline-block size-4\" />\n Product inventory — March 2026\n </div>\n {/snippet}\n </Table>\n </section>\n</div>\n\n<!-- ==================== SNIPPET DEFINITIONS ==================== -->\n\n{#snippet userCell(props: TableCellSlotProps<User>)}\n <div class=\"flex items-center gap-3\">\n <Avatar src={props.row.avatar} alt={props.row.name} size=\"xs\" />\n <span class=\"font-medium\">{props.row.name}</span>\n </div>\n{/snippet}\n\n{#snippet roleCell(props: TableCellSlotProps<User>)}\n <Badge label={props.row.role} variant=\"soft\" color={roleColor[props.row.role] ?? 'secondary'} />\n{/snippet}\n\n{#snippet statusCell(props: TableCellSlotProps<User>)}\n <Badge\n label={props.row.status}\n variant=\"subtle\"\n color={statusColor[props.row.status] ?? 'surface'}\n />\n{/snippet}\n\n{#snippet categoryCell(props: TableCellSlotProps<Product>)}\n <Badge\n label={props.row.category}\n variant=\"soft\"\n color={props.row.category === 'Electronics' ? 'primary' : 'tertiary'}\n />\n{/snippet}\n\n{#snippet priceCell(props: TableCellSlotProps<Product>)}\n <span class=\"font-medium\">${props.row.price.toLocaleString()}</span>\n{/snippet}\n\n{#snippet stockCell(props: TableCellSlotProps<Product>)}\n <Badge\n label={String(props.row.stock)}\n variant=\"subtle\"\n color={props.row.stock < 15 ? 'error' : props.row.stock < 50 ? 'warning' : 'success'}\n />\n{/snippet}\n\n{#snippet actionsCell(props: TableCellSlotProps<User>)}\n <DropdownMenu items={getRowActions(props.row)}>\n <Button\n variant=\"ghost\"\n color=\"surface\"\n size=\"xs\"\n icon=\"lucide:ellipsis\"\n aria-label=\"Row actions\"\n />\n </DropdownMenu>\n{/snippet}\n\n{#snippet expandCell(props: TableCellSlotProps<User>)}\n {@const rowId = props.row.id}\n {@const isExpanded = expandedRows.includes(rowId)}\n <Button\n variant=\"ghost\"\n color=\"surface\"\n size=\"xs\"\n icon=\"lucide:chevron-right\"\n class=\"transition-transform duration-200 {isExpanded ? 'rotate-90' : ''}\"\n aria-label={isExpanded ? 'Collapse row' : 'Expand row'}\n aria-expanded={isExpanded}\n onclick={() => {\n if (isExpanded) {\n expandedRows = expandedRows.filter((k) => k !== rowId)\n } else {\n expandedRows = [...expandedRows, rowId]\n }\n }}\n />\n{/snippet}\n\n{#snippet deptExpandCell(props: TableCellSlotProps<Department>)}\n {@const deptId = props.row.id}\n {@const hasChildren = props.row.children && props.row.children.length > 0}\n {@const isExpanded = expandedDepts.includes(deptId)}\n {#if hasChildren}\n <Button\n variant=\"ghost\"\n color=\"surface\"\n size=\"xs\"\n icon=\"lucide:chevron-right\"\n class=\"transition-transform duration-200 {isExpanded ? 'rotate-90' : ''}\"\n aria-label={isExpanded ? 'Collapse' : 'Expand'}\n aria-expanded={isExpanded}\n onclick={() => {\n if (isExpanded) {\n expandedDepts = expandedDepts.filter((k) => k !== deptId)\n } else {\n expandedDepts = [...expandedDepts, deptId]\n }\n }}\n />\n {/if}\n{/snippet}\n\n{#snippet deptNameCell(props: TableCellSlotProps<Department>)}\n <div class=\"flex items-center gap-2\">\n <Icon name=\"lucide:building-2\" class=\"size-4 text-on-surface-variant\" />\n <span class=\"font-medium\">{props.row.name}</span>\n {#if props.row.children}\n <Badge\n label=\"{props.row.children.length} teams\"\n variant=\"soft\"\n color=\"tertiary\"\n size=\"xs\"\n />\n {/if}\n </div>\n{/snippet}\n\n{#snippet footerLabel(props: TableFooterSlotProps<Product>)}\n <span class=\"font-semibold text-on-surface\">Total ({props.rows.length} items)</span>\n{/snippet}\n\n{#snippet footerTotal(props: TableFooterSlotProps<Product>)}\n <span class=\"font-semibold text-on-surface\"\n >${props.rows.reduce((sum, r) => sum + r.price, 0).toLocaleString()}</span\n >\n{/snippet}\n\n{#snippet footerSum(props: TableFooterSlotProps<Product>)}\n <span class=\"font-semibold text-on-surface\"\n >{props.rows.reduce((sum, r) => sum + r.stock, 0)}</span\n >\n{/snippet}\n\n{#snippet pinActionCell(props: TableCellSlotProps<User>)}\n {@const isPinned = pinnedRows.includes(props.row.id)}\n <Button\n variant=\"ghost\"\n color={isPinned ? 'primary' : 'surface'}\n size=\"xs\"\n icon={isPinned ? 'lucide:pin-off' : 'lucide:pin'}\n aria-label={isPinned ? 'Unpin row' : 'Pin row'}\n onclick={() => {\n if (isPinned) {\n pinnedRows = pinnedRows.filter((k) => k !== props.row.id)\n } else {\n pinnedRows = [...pinnedRows, props.row.id]\n }\n }}\n />\n{/snippet}\n\n{#snippet pinPagActionCell(props: TableCellSlotProps<User>)}\n {@const isPinned = pinPagPinnedRows.includes(props.row.id)}\n <Button\n variant=\"ghost\"\n color={isPinned ? 'primary' : 'surface'}\n size=\"xs\"\n icon={isPinned ? 'lucide:pin-off' : 'lucide:pin'}\n aria-label={isPinned ? 'Unpin row' : 'Pin row'}\n onclick={() => {\n if (isPinned) {\n pinPagPinnedRows = pinPagPinnedRows.filter((k) => k !== props.row.id)\n } else {\n pinPagPinnedRows = [...pinPagPinnedRows, props.row.id]\n }\n }}\n />\n{/snippet}\n",
|
|
133
|
+
"tree-view": "<script lang=\"ts\">\n import { TreeView } from '$lib/index.js'\n import type { TreeItem } from '$lib/index.js'\n \n const treeData: TreeItem[] = [\n {\n id: '1',\n label: 'src',\n icon: 'lucide:folder',\n children: [\n {\n id: '1-1',\n label: 'lib',\n icon: 'lucide:folder',\n children: [\n { id: '1-1-1', label: 'index.ts', icon: 'lucide:file-code' },\n { id: '1-1-2', label: 'utils.ts', icon: 'lucide:file-code' }\n ]\n },\n {\n id: '1-2',\n label: 'routes',\n icon: 'lucide:folder',\n children: [\n { id: '1-2-1', label: '+page.svelte', icon: 'lucide:file' },\n { id: '1-2-2', label: '+layout.svelte', icon: 'lucide:file' }\n ]\n }\n ]\n },\n {\n id: '2',\n label: 'package.json',\n icon: 'lucide:file-json'\n }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">TreeView</h1>\n <p class=\"text-on-surface-variant\">\n A component for displaying hierarchical data in a collapsible tree structure.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an array of <code class=\"rounded bg-surface-container-highest px-1\">TreeItem</code> objects to the <code class=\"rounded bg-surface-container-highest px-1\">items</code> prop. Each item must have an <code class=\"rounded bg-surface-container-highest px-1\">id</code> and <code class=\"rounded bg-surface-container-highest px-1\">label</code>.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"w-full max-w-sm p-4 bg-surface rounded-lg border border-outline-variant\">\n <TreeView items={treeData} />\n </div>\n </div>\n </section>\n</div>\n",
|
|
108
134
|
"checkbox": "<script lang=\"ts\">\n import { Checkbox, FormField, Separator } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n let bindChecked = $state(false)\n let indeterminate = $state(true)\n\n let allChecked = $state(false)\n let items = $state([\n { label: 'Emails', checked: false },\n { label: 'Push notifications', checked: false },\n { label: 'SMS alerts', checked: false }\n ])\n\n function syncSelectAll() {\n const all = items.every((i) => i.checked)\n const none = items.every((i) => !i.checked)\n allChecked = all\n indeterminate = !all && !none\n }\n\n function toggleAll(checked: boolean) {\n allChecked = checked\n indeterminate = false\n items = items.map((i) => ({ ...i, checked }))\n }\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">Checkbox</h1>\n\n <!-- Basic Usage -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n A checkbox component for boolean and indeterminate states.\n </p>\n <div class=\"flex flex-wrap gap-6\">\n <Checkbox />\n <Checkbox checked={true} />\n </div>\n </section>\n\n <!-- Two-way Binding -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Two-way Binding</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bind:checked</code\n > for reactive two-way data binding.\n </p>\n <div class=\"flex flex-wrap items-center gap-6\">\n <Checkbox bind:checked={bindChecked} label=\"Toggle me\" />\n <p class=\"text-sm text-on-surface-variant\">\n Checked: <span class=\"font-mono text-on-surface\">{bindChecked}</span>\n </p>\n </div>\n </section>\n\n <!-- Label & Description -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Label & Description</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">label</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >description</code\n > to add text next to the checkbox.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Checkbox label=\"Accept terms\" />\n <Checkbox\n label=\"Marketing emails\"\n description=\"Receive emails about new products and features.\"\n />\n <Checkbox\n label=\"Push notifications\"\n description=\"Get notified when someone mentions you.\"\n />\n </div>\n </section>\n\n <!-- Indeterminate -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Indeterminate</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bind:indeterminate</code\n > for tri-state behavior, useful for \"select all\" patterns.\n </p>\n <div class=\"flex flex-col gap-3\">\n <Checkbox\n checked={allChecked}\n {indeterminate}\n label=\"Select all\"\n onCheckedChange={toggleAll}\n />\n <div class=\"ms-6 flex flex-col gap-2\">\n {#each items as item, i (item.label)}\n <Checkbox\n bind:checked={items[i].checked}\n label={item.label}\n onCheckedChange={() => syncSelectAll()}\n />\n {/each}\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code\n > to control the checked background color.\n </p>\n <div class=\"flex flex-wrap gap-6\">\n {#each colors as color (color)}\n <Checkbox {color} checked={true} label={color} />\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Sizes</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code> to\n control the dimensions.\n </p>\n <div class=\"flex flex-wrap items-center gap-6\">\n {#each sizes as size (size)}\n <Checkbox {size} checked={true} label={size} />\n {/each}\n </div>\n </section>\n\n <!-- Variant -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Variant</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >variant=\"card\"</code\n > to display the checkbox inside a bordered card. The border highlights when checked.\n </p>\n <div class=\"flex max-w-sm flex-col gap-3\">\n <Checkbox\n variant=\"card\"\n label=\"Email notifications\"\n description=\"Receive alerts in your inbox.\"\n />\n <Checkbox\n variant=\"card\"\n checked={true}\n label=\"Push notifications\"\n description=\"Alerts on your device.\"\n color=\"primary\"\n />\n <Checkbox\n variant=\"card\"\n checked={true}\n label=\"SMS alerts\"\n description=\"Text messages for critical updates.\"\n color=\"success\"\n />\n <Checkbox\n variant=\"card\"\n disabled\n label=\"Fax (unavailable)\"\n description=\"This option is currently disabled.\"\n />\n </div>\n </section>\n\n <!-- Indicator -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Indicator Position</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >indicator</code\n >\n to control where the checkbox appears:\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">start</code>,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">end</code>, or\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">hidden</code>.\n </p>\n <div class=\"flex max-w-sm flex-col gap-4\">\n <Checkbox\n indicator=\"start\"\n label=\"Start (default)\"\n description=\"Checkbox on the left.\"\n />\n <Checkbox\n indicator=\"end\"\n label=\"End\"\n description=\"Checkbox on the right.\"\n checked={true}\n />\n <Checkbox\n indicator=\"hidden\"\n label=\"Hidden\"\n description=\"Checkbox hidden, label only.\"\n checked={true}\n color=\"secondary\"\n />\n </div>\n <div class=\"flex max-w-sm flex-col gap-3\">\n <p class=\"text-xs text-on-surface-variant\">Combined with card variant</p>\n <Checkbox\n variant=\"card\"\n indicator=\"end\"\n label=\"Opt in to beta features\"\n description=\"Get early access to new features.\"\n checked={true}\n color=\"tertiary\"\n />\n </div>\n </section>\n\n <!-- Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code>\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >indeterminateIcon</code\n > to customize the icons.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Checkbox icon=\"lucide:heart\" checked={true} label=\"Favorite\" color=\"error\" />\n <Checkbox icon=\"lucide:star\" checked={true} label=\"Starred\" color=\"warning\" />\n <Checkbox icon=\"lucide:thumbs-up\" checked={true} label=\"Liked\" color=\"success\" />\n </div>\n </section>\n\n <!-- Loading -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >loading</code\n > to show a spinner and disable interaction.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Checkbox loading label=\"Syncing...\" />\n <Checkbox loading checked={true} label=\"Saving preferences...\" color=\"success\" />\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Disabled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >disabled</code\n > to prevent interaction.\n </p>\n <div class=\"flex flex-wrap gap-6\">\n <Checkbox disabled label=\"Disabled (off)\" />\n <Checkbox disabled checked={true} label=\"Disabled (on)\" />\n </div>\n </section>\n\n <!-- Required -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Required</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >required</code\n > to show an asterisk next to the label.\n </p>\n <Checkbox required label=\"Accept terms and conditions\" />\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Custom Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >labelSlot</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >descriptionSlot</code\n > for fully custom label content.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Checkbox checked={true} color=\"success\">\n {#snippet labelSlot()}\n <span class=\"flex items-center gap-1.5 text-sm font-medium text-on-surface\">\n <span class=\"inline-block size-2 rounded-full bg-success\"></span>\n System online\n </span>\n {/snippet}\n {#snippet descriptionSlot()}\n <span class=\"text-xs text-on-surface-variant\">\n All services are running normally. Last checked <strong>just now</strong>.\n </span>\n {/snippet}\n </Checkbox>\n\n <Checkbox>\n {#snippet labelSlot()}\n <span class=\"flex items-center gap-2 text-sm font-medium text-on-surface\">\n Beta features\n <span\n class=\"rounded bg-tertiary-container px-1.5 py-0.5 text-[10px] font-bold text-on-tertiary-container\"\n >BETA</span\n >\n </span>\n {/snippet}\n </Checkbox>\n </div>\n </section>\n\n <!-- FormField Integration -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">FormField Integration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When used inside a <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FormField</code\n >, the Checkbox automatically inherits size and error state.\n </p>\n <div class=\"max-w-sm space-y-4\">\n <FormField label=\"Preferences\" description=\"Choose your notification preferences.\">\n <Checkbox label=\"Email notifications\" />\n </FormField>\n <FormField label=\"Agreement\" error=\"You must accept the terms.\">\n <Checkbox label=\"I accept the terms of service\" />\n </FormField>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Real World Examples</h2>\n\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Notification preferences (list)</p>\n <div\n class=\"max-w-sm space-y-3 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <Checkbox\n checked={true}\n label=\"Email digest\"\n description=\"Daily summary of activity.\"\n color=\"primary\"\n />\n <Checkbox\n checked={true}\n label=\"Push notifications\"\n description=\"Alerts on your device.\"\n color=\"primary\"\n />\n <Checkbox\n label=\"SMS alerts\"\n description=\"Text messages for critical updates.\"\n color=\"primary\"\n />\n <Checkbox\n loading\n label=\"Marketing emails\"\n description=\"Updating preference...\"\n color=\"primary\"\n />\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Plan selection (card variant)</p>\n <div class=\"max-w-sm space-y-3\">\n <Checkbox\n variant=\"card\"\n indicator=\"end\"\n checked={true}\n color=\"primary\"\n label=\"Starter\"\n description=\"$9/mo · Up to 3 projects\"\n />\n <Checkbox\n variant=\"card\"\n indicator=\"end\"\n color=\"primary\"\n label=\"Pro\"\n description=\"$29/mo · Unlimited projects\"\n />\n <Checkbox\n variant=\"card\"\n indicator=\"end\"\n color=\"primary\"\n label=\"Enterprise\"\n description=\"Custom pricing · SSO + advanced security\"\n />\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Permissions (hidden indicator)</p>\n <div\n class=\"max-w-sm space-y-2 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <Checkbox indicator=\"hidden\" checked={true} color=\"success\">\n {#snippet labelSlot()}\n <span\n class=\"flex w-full items-center justify-between text-sm text-on-surface\"\n >\n Read\n <span\n class=\"rounded bg-success-container px-1.5 py-0.5 text-[10px] font-semibold text-on-success-container\"\n >Granted</span\n >\n </span>\n {/snippet}\n </Checkbox>\n <Checkbox indicator=\"hidden\" checked={true} color=\"success\">\n {#snippet labelSlot()}\n <span\n class=\"flex w-full items-center justify-between text-sm text-on-surface\"\n >\n Write\n <span\n class=\"rounded bg-success-container px-1.5 py-0.5 text-[10px] font-semibold text-on-success-container\"\n >Granted</span\n >\n </span>\n {/snippet}\n </Checkbox>\n <Checkbox indicator=\"hidden\">\n {#snippet labelSlot()}\n <span\n class=\"flex w-full items-center justify-between text-sm text-on-surface-variant\"\n >\n Admin\n <span\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-[10px] font-semibold text-on-surface-variant\"\n >Denied</span\n >\n </span>\n {/snippet}\n </Checkbox>\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
135
|
+
"color-picker": "<script lang=\"ts\">\n import { ColorPicker } from '$lib/index.js'\n \n let color1 = $state('#3b82f6')\n let color2 = $state('#f43f5e')\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">ColorPicker</h1>\n <p class=\"text-on-surface-variant\">\n A flexible component for selecting colors via swatches, hex input, or native color picker.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Bind a hex string variable to the <code class=\"rounded bg-surface-container-highest px-1\">value</code> prop using <code class=\"rounded bg-surface-container-highest px-1\">bind:value</code>.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <ColorPicker bind:value={color1} />\n <div class=\"text-sm text-on-surface-variant\">\n Selected Color: <span class=\"font-mono font-bold\" style=\"color: {color1};\">{color1}</span>\n </div>\n </div>\n </section>\n\n <!-- Disabled State -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">disabled</code> prop to prevent interaction.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <ColorPicker bind:value={color2} disabled />\n </div>\n </section>\n</div>\n",
|
|
109
136
|
"checkbox-group": "<script lang=\"ts\">\n import { CheckboxGroup } from '$lib/index.js'\n import type { CheckboxGroupItem } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n const basicItems: CheckboxGroupItem[] = [\n { value: 'svelte', label: 'Svelte' },\n { value: 'react', label: 'React' },\n { value: 'vue', label: 'Vue' }\n ]\n\n const itemsWithDesc: CheckboxGroupItem[] = [\n {\n value: 'email',\n label: 'Email',\n description: 'Receive notifications via email'\n },\n {\n value: 'sms',\n label: 'SMS',\n description: 'Receive notifications via text message'\n },\n {\n value: 'push',\n label: 'Push',\n description: 'Receive browser push notifications'\n }\n ]\n\n const itemsWithDisabled: CheckboxGroupItem[] = [\n { value: 'read', label: 'Read' },\n { value: 'write', label: 'Write' },\n { value: 'delete', label: 'Delete', disabled: true },\n { value: 'admin', label: 'Admin', disabled: true }\n ]\n\n let basicValue = $state(['svelte'])\n let notifValue = $state(['email'])\n let cardValue = $state(['email'])\n let horizontalValue = $state<string[]>([])\n let permissionValue = $state(['read'])\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">CheckboxGroup</h1>\n <p class=\"text-on-surface-variant\">\n Group of checkboxes for selecting multiple values from a list.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CheckboxGroup items={basicItems} bind:value={basicValue} legend=\"Frameworks\" />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n Selected: {basicValue.join(', ') || 'none'}\n </p>\n </div>\n </section>\n\n <!-- With Description -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Description</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CheckboxGroup\n items={itemsWithDesc}\n bind:value={notifValue}\n legend=\"Notifications\"\n required\n />\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">List (default)</p>\n <CheckboxGroup items={basicItems} value={['svelte']} legend=\"Pick frameworks\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Card</p>\n <CheckboxGroup\n items={itemsWithDesc}\n bind:value={cardValue}\n variant=\"card\"\n legend=\"Notifications\"\n />\n </div>\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <div>\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">Vertical (default)</p>\n <CheckboxGroup items={basicItems} value={['svelte', 'vue']} />\n </div>\n <div>\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">Horizontal</p>\n <CheckboxGroup\n items={basicItems}\n bind:value={horizontalValue}\n orientation=\"horizontal\"\n />\n </div>\n <div>\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">Horizontal · Card</p>\n <CheckboxGroup\n items={basicItems}\n value={['react']}\n variant=\"card\"\n orientation=\"horizontal\"\n />\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <CheckboxGroup items={[{ value: color, label: color }]} value={[color]} {color} />\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-6 text-xs text-on-surface-variant\">{size}</span>\n <CheckboxGroup\n items={basicItems}\n value={['svelte']}\n {size}\n orientation=\"horizontal\"\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Indicator Position -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Indicator Position</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-3\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Start (default)</p>\n <CheckboxGroup items={itemsWithDesc} value={['email']} indicator=\"start\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">End</p>\n <CheckboxGroup items={itemsWithDesc} value={['email']} indicator=\"end\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Hidden</p>\n <CheckboxGroup items={itemsWithDesc} value={['email']} indicator=\"hidden\" />\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Entire group disabled</p>\n <CheckboxGroup items={basicItems} value={['svelte']} disabled />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Per-item disabled</p>\n <CheckboxGroup\n items={itemsWithDisabled}\n bind:value={permissionValue}\n legend=\"Permissions\"\n />\n </div>\n </div>\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CheckboxGroup items={itemsWithDesc} value={['email', 'push']}>\n {#snippet legendSlot()}\n <span class=\"mb-1 block text-sm font-semibold text-primary\">\n Notification channels\n </span>\n {/snippet}\n {#snippet labelSlot({ item })}\n <span class=\"ms-2 text-sm font-semibold text-on-surface\">{item.label}</span>\n {/snippet}\n {#snippet descriptionSlot({ item })}\n <span class=\"ms-2 text-xs text-on-surface-variant italic\"\n >{item.description}</span\n >\n {/snippet}\n </CheckboxGroup>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Settings form -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Settings Form</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container p-4\"\n >\n <CheckboxGroup\n items={[\n {\n value: 'marketing',\n label: 'Marketing emails',\n description: 'Promotions and offers'\n },\n {\n value: 'updates',\n label: 'Product updates',\n description: 'New features and improvements'\n },\n {\n value: 'security',\n label: 'Security alerts',\n description: 'Login and activity alerts'\n }\n ]}\n value={['security']}\n legend=\"Email preferences\"\n variant=\"card\"\n color=\"primary\"\n />\n </div>\n </div>\n\n <!-- Filter tags -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Filter Tags</p>\n <CheckboxGroup\n items={[\n { value: 'design', label: 'Design' },\n { value: 'engineering', label: 'Engineering' },\n { value: 'product', label: 'Product' },\n { value: 'marketing', label: 'Marketing' }\n ]}\n value={['design', 'engineering']}\n orientation=\"horizontal\"\n color=\"tertiary\"\n size=\"sm\"\n />\n </div>\n\n <!-- Permission checklist -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Permission Checklist</p>\n <CheckboxGroup\n items={[\n { value: 'read', label: 'Read', description: 'View resources' },\n {\n value: 'write',\n label: 'Write',\n description: 'Create and edit resources'\n },\n { value: 'delete', label: 'Delete', description: 'Remove resources' },\n {\n value: 'admin',\n label: 'Admin',\n description: 'Full access',\n disabled: true\n }\n ]}\n value={['read', 'write']}\n legend=\"API permissions\"\n variant=\"card\"\n color=\"error\"\n indicator=\"end\"\n />\n </div>\n </div>\n </section>\n</div>\n",
|
|
110
137
|
"editor": "<script lang=\"ts\">\n import { Editor } from '$lib/Editor/index.js'\n import type { EditorApi, EditorJSON, MentionItem } from '$lib/Editor/index.js'\n import { Button, Badge, Separator, Card, Icon, Form, FormField, Input } from '$lib/index.js'\n\n let basicHtml = $state('<p>Start writing here…</p>')\n\n let jsonValue = $state<EditorJSON>({\n type: 'doc',\n content: [\n {\n type: 'paragraph',\n content: [{ type: 'text', text: 'This document is stored as JSON.' }]\n }\n ]\n })\n\n let customToolbarValue = $state('<p>Only <strong>bold</strong> and <em>italic</em>.</p>')\n\n let api = $state<EditorApi>()\n let apiValue = $state('<p>Drive me from outside.</p>')\n\n let limitedValue = $state('<p>Type here…</p>')\n\n let bubbleValue = $state('<p>Select any text to see the floating menu.</p>')\n\n // ----- Phase 2 demos -----\n\n let markdownValue = $state('# Hello\\n\\nThis editor outputs **Markdown**.')\n\n let imageValue = $state('<p>Click the image button to upload.</p>')\n async function fakeUploadImage(file: File): Promise<string> {\n // Demo: convert to data URL. Real apps would upload to a backend.\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result))\n reader.onerror = () => reject(new Error('FileReader failed'))\n reader.readAsDataURL(file)\n })\n }\n\n let tableValue = $state('<p>Click the table button → pick dimensions → insert.</p>')\n\n let slashValue = $state('<p>Type / to open the command menu…</p>')\n let youtubeValue = $state(\n '<p>Click the YouTube button or use / → YouTube to embed a video.</p>'\n )\n let dragHandleValue = $state(\n '<h2>Drag me!</h2><p>Hover any block to see the drag handle appear on the left. Drag to reorder.</p><ul><li>First item</li><li>Second item</li><li>Third item</li></ul>'\n )\n\n let mentionValue = $state('<p>Type @ to mention someone…</p>')\n const teamMembers: MentionItem[] = [\n { id: 'alice', label: 'Alice Nguyen' },\n { id: 'bob', label: 'Bob Tran' },\n { id: 'charlie', label: 'Charlie Le' },\n { id: 'diana', label: 'Diana Pham' },\n { id: 'evan', label: 'Evan Vo' }\n ]\n async function searchMembers(query: string): Promise<MentionItem[]> {\n await new Promise((r) => setTimeout(r, 50))\n const q = query.toLowerCase()\n return teamMembers.filter((m) => m.label.toLowerCase().includes(q))\n }\n\n const articleState = $state({ title: '', body: '<p></p>' })\n let articleSubmitted = $state<string | null>(null)\n function validateArticle(values: object) {\n const v = values as { title: string; body: string }\n const errors: { name: string; message: string }[] = []\n if (!v.title.trim()) errors.push({ name: 'title', message: 'Title is required' })\n if (v.body === '<p></p>' || !v.body.trim())\n errors.push({ name: 'body', message: 'Body cannot be empty' })\n return errors\n }\n function submitArticle(e: { data: unknown }) {\n articleSubmitted = JSON.stringify(e.data, null, 2)\n }\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n let serializedJson = $derived(JSON.stringify(jsonValue, null, 2))\n\n const importExample = \"import { Editor } from 'svelora/editor'\"\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Editor</h1>\n <p class=\"text-on-surface-variant\">\n Rich-text WYSIWYG editor built on <strong>Tiptap v3</strong> + ProseMirror. Imported via\n the sub-export\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >svelora/editor</code\n >\n — only adds Tiptap to your bundle when actually used.\n </p>\n <div class=\"flex flex-wrap gap-2 pt-1\">\n <Badge variant=\"soft\" color=\"info\" label=\"Phase 1 (v1.8)\" />\n <Badge variant=\"soft\" color=\"surface\" label=\"HTML or JSON output\" />\n <Badge variant=\"soft\" color=\"surface\" label=\"Config-driven toolbar\" />\n </div>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Minimal usage — default toolbar, HTML output, bindable\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">value</code>.\n </p>\n <Editor bind:value={basicHtml} placeholder=\"Write something…\" />\n <details class=\"text-sm\">\n <summary class=\"cursor-pointer text-on-surface-variant hover:text-on-surface\"\n >Show raw HTML</summary\n >\n <pre\n class=\"mt-2 overflow-x-auto rounded bg-surface-container-highest p-3 text-xs\">{basicHtml}</pre>\n </details>\n </section>\n\n <!-- JSON output -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">JSON output</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >output=\"json\"</code\n >\n to receive a structured Tiptap document — ideal for database storage and downstream transformations.\n </p>\n <Editor bind:value={jsonValue} output=\"json\" placeholder=\"JSON-backed editor…\" />\n <details class=\"text-sm\">\n <summary class=\"cursor-pointer text-on-surface-variant hover:text-on-surface\"\n >Show serialized JSON</summary\n >\n <pre\n class=\"mt-2 max-h-80 overflow-auto rounded bg-surface-container-highest p-3 text-xs\">{serializedJson}</pre>\n </details>\n </section>\n\n <!-- Custom toolbar -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom toolbar config</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an array of action ids + <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">'|'</code\n > separators to control exactly which buttons appear.\n </p>\n <Editor bind:value={customToolbarValue} toolbar={['bold', 'italic', '|', 'undo', 'redo']} />\n </section>\n\n <!-- Bubble menu only -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Bubble menu (no top toolbar)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >toolbar={'{false}'}</code\n >\n +\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bubbleMenu</code\n >\n shows a floating menu only when text is selected — minimal chrome.\n </p>\n <Editor bind:value={bubbleValue} toolbar={false} bubbleMenu />\n </section>\n\n <!-- Character count + maxLength -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Character limit</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">maxLength</code\n >\n blocks input beyond the limit and shows a counter in the footer.\n </p>\n <Editor bind:value={limitedValue} maxLength={140} placeholder=\"Tweet-length post…\" />\n </section>\n\n <!-- Read-only / disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Read-only & disabled</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <p class=\"mb-2 text-sm font-medium\">readonly</p>\n <Editor\n value=\"<h3>Read-only</h3><p>Content renders but typing is blocked.</p>\"\n readonly\n />\n </div>\n <div>\n <p class=\"mb-2 text-sm font-medium\">disabled</p>\n <Editor value=\"<h3>Disabled</h3><p>Faded + non-interactive.</p>\" disabled />\n </div>\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4\">\n {#each ['sm', 'md', 'lg'] as const as size (size)}\n <div>\n <p class=\"mb-1 text-xs font-medium tracking-wide uppercase\">{size}</p>\n <Editor\n {size}\n value={`<p>Size: <strong>${size}</strong></p>`}\n toolbar={['bold', 'italic', '|', 'h1', 'h2']}\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Colors (focus ring) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Focus ring colors</h2>\n <p class=\"text-sm text-on-surface-variant\">Click into each editor to see the focus ring.</p>\n <div class=\"grid gap-3 md:grid-cols-2\">\n {#each colors as color (color)}\n <div>\n <p class=\"mb-1 text-xs font-medium capitalize\">{color}</p>\n <Editor\n {color}\n value={`<p>Focus me — <strong>${color}</strong> ring.</p>`}\n toolbar={['bold', 'italic', '|', 'undo', 'redo']}\n />\n </div>\n {/each}\n </div>\n </section>\n\n <Separator />\n\n <!-- Imperative API -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Imperative API — bind:api</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Drive the editor from outside via <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">bind:api</code\n >. Useful for custom toolbars, form submission, or external controls.\n </p>\n <Editor bind:api bind:value={apiValue} placeholder=\"Drive me from outside…\" />\n <div class=\"flex flex-wrap items-center gap-2\">\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:bold\"\n label=\"Bold\"\n onclick={() => api?.run('bold')}\n />\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:italic\"\n label=\"Italic\"\n onclick={() => api?.run('italic')}\n />\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:heading-1\"\n label=\"H1\"\n onclick={() => api?.run('h1')}\n />\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:link\"\n label=\"Link\"\n onclick={() => api?.run('link')}\n />\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:undo-2\"\n label=\"Undo\"\n disabled={!api?.state.can.undo}\n onclick={() => api?.run('undo')}\n />\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:redo-2\"\n label=\"Redo\"\n disabled={!api?.state.can.redo}\n onclick={() => api?.run('redo')}\n />\n <Button\n size=\"xs\"\n color=\"error\"\n variant=\"outline\"\n leadingIcon=\"lucide:trash-2\"\n label=\"Clear\"\n onclick={() => api?.clear()}\n />\n <Button\n size=\"xs\"\n color=\"primary\"\n variant=\"solid\"\n leadingIcon=\"lucide:plus\"\n label=\"Insert sample\"\n onclick={() => api?.insert(' — <em>inserted</em>')}\n />\n </div>\n <div class=\"flex flex-wrap items-center gap-2 pt-2 text-xs text-on-surface-variant\">\n <span>State:</span>\n <Badge size=\"xs\" variant=\"soft\" color={api?.state.active.bold ? 'primary' : 'surface'}\n >bold {api?.state.active.bold ?? false}</Badge\n >\n <Badge size=\"xs\" variant=\"soft\" color={api?.state.active.italic ? 'primary' : 'surface'}\n >italic {api?.state.active.italic ?? false}</Badge\n >\n <Badge size=\"xs\" variant=\"soft\" color={api?.state.isFocused ? 'success' : 'surface'}\n >focused {api?.state.isFocused ?? false}</Badge\n >\n <Badge size=\"xs\" variant=\"soft\" color={api?.state.isEmpty ? 'warning' : 'surface'}\n >empty {api?.state.isEmpty ?? false}</Badge\n >\n <Badge size=\"xs\" variant=\"soft\" color=\"info\">chars: {api?.state.charCount ?? 0}</Badge>\n <Badge size=\"xs\" variant=\"soft\" color=\"info\">words: {api?.state.wordCount ?? 0}</Badge>\n </div>\n </section>\n\n <Separator />\n\n <!-- Phase 2: Markdown output -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Markdown output (Phase 2)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >output=\"markdown\"</code\n >\n to bind a Markdown string. Powered by the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >tiptap-markdown</code\n > extension — paste markdown is also recognized.\n </p>\n <Editor\n bind:value={markdownValue}\n output=\"markdown\"\n placeholder=\"Type or paste markdown…\"\n />\n <details class=\"text-sm\">\n <summary class=\"cursor-pointer text-on-surface-variant hover:text-on-surface\"\n >Show raw markdown</summary\n >\n <pre\n class=\"mt-2 overflow-x-auto rounded bg-surface-container-highest p-3 text-xs\">{markdownValue}</pre>\n </details>\n </section>\n\n <!-- Phase 2: Image upload -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Image upload (Phase 2)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">image</code>\n toolbar action opens a file picker → calls\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >onImageUpload(file)</code\n >\n → inserts the returned URL. Demo here converts to a data URL client-side.\n </p>\n <Editor\n bind:value={imageValue}\n image\n onImageUpload={fakeUploadImage}\n toolbar={[\n 'bold',\n 'italic',\n '|',\n 'h1',\n 'h2',\n '|',\n 'image',\n '|',\n 'bulletList',\n 'orderedList',\n '|',\n 'undo',\n 'redo'\n ]}\n />\n </section>\n\n <!-- Phase 2: Tables -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Tables (Phase 2)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Enable with <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >tables</code\n >\n and add the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">table</code> toolbar\n action. Click → hover the grid → pick dimensions → click to insert.\n </p>\n <Editor\n bind:value={tableValue}\n tables\n toolbar={['bold', 'italic', '|', 'h2', 'h3', '|', 'table', '|', 'undo', 'redo']}\n />\n </section>\n\n <!-- Phase 2: Mentions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Mentions (Phase 2)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Provide <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >onMention(query)</code\n >\n to enable @-style suggestions. Type\n <kbd\n class=\"rounded border border-outline-variant bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >@</kbd\n >\n inside the editor — a popup will open with matching items. Arrow keys to navigate, Enter to\n select.\n </p>\n <Editor bind:value={mentionValue} onMention={searchMembers} placeholder=\"Try typing @al…\" />\n </section>\n\n <Separator />\n\n <!-- Phase 3: Slash commands -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Slash commands (Phase 3)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">slash</code\n >\n to enable. Inside the editor, type\n <kbd\n class=\"rounded border border-outline-variant bg-surface-container-high px-1.5 py-0.5 text-xs\"\n >/</kbd\n >\n to open the command menu — fuzzy filter by typing, Arrow keys to navigate, Enter to run. Pass\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >slashCommands</code\n > to customize.\n </p>\n <Editor bind:value={slashValue} slash image tables youtube />\n </section>\n\n <!-- Phase 3: YouTube embed -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">YouTube embeds (Phase 3)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">youtube</code>\n enables the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">youtube</code> toolbar\n action which prompts for a URL and inserts a responsive embed.\n </p>\n <Editor\n bind:value={youtubeValue}\n youtube\n toolbar={['bold', 'italic', '|', 'h2', 'h3', '|', 'youtube', '|', 'undo', 'redo']}\n />\n </section>\n\n <!-- Phase 3: Drag handle -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Drag handle (Phase 3)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >dragHandle</code\n >\n shows a draggable handle on the left of each block on hover. Drag to reorder paragraphs, headings,\n lists, etc.\n </p>\n <Editor bind:value={dragHandleValue} dragHandle />\n </section>\n\n <Separator />\n\n <!-- Phase 2: Form integration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Form integration (Phase 2)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Wrap in <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n ><FormField></code\n > to get error highlighting, label association, and per-field validation events.\n </p>\n <Card class=\"space-y-3 p-4\">\n <Form state={articleState} validate={validateArticle} onsubmit={submitArticle}>\n <div class=\"space-y-4\">\n <FormField name=\"title\" label=\"Title\" required>\n <Input bind:value={articleState.title} placeholder=\"Article title…\" />\n </FormField>\n <FormField name=\"body\" label=\"Body\" required>\n <Editor\n bind:value={articleState.body}\n placeholder=\"Write your article…\"\n toolbar={[\n 'bold',\n 'italic',\n '|',\n 'h2',\n 'h3',\n '|',\n 'bulletList',\n 'orderedList',\n 'blockquote',\n '|',\n 'link',\n '|',\n 'undo',\n 'redo'\n ]}\n />\n </FormField>\n <div class=\"flex justify-end\">\n <Button type=\"submit\" color=\"primary\" size=\"sm\" label=\"Submit article\" />\n </div>\n </div>\n </Form>\n {#if articleSubmitted}\n <pre\n class=\"mt-2 max-h-60 overflow-auto rounded bg-surface-container-highest p-3 text-xs\">{articleSubmitted}</pre>\n {/if}\n </Card>\n </section>\n\n <Separator />\n\n <!-- Real-world: comment box -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real-world — Comment box</h2>\n <Card class=\"space-y-3 p-4\">\n <Editor\n bind:value={limitedValue}\n placeholder=\"Add a thoughtful comment…\"\n maxLength={280}\n toolbar={['bold', 'italic', 'code', '|', 'bulletList', 'orderedList', '|', 'link']}\n bubbleMenu\n stickyToolbar\n />\n <div class=\"flex items-center justify-end gap-2\">\n <Button variant=\"ghost\" size=\"sm\" label=\"Cancel\" />\n <Button color=\"primary\" size=\"sm\" leadingIcon=\"lucide:send\" label=\"Post comment\" />\n </div>\n </Card>\n </section>\n\n <!-- Note about sub-export -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Import path</h2>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4 text-sm\">\n <p class=\"mb-2 flex items-center gap-2 font-medium\">\n <Icon name=\"lucide:package\" size=\"18\" class=\"text-primary\" />\n Editor is opt-in via a separate entry point\n </p>\n <pre class=\"mt-2 overflow-x-auto rounded bg-surface-container-highest p-3 text-xs\"><code\n >{importExample}</code\n ></pre>\n <p class=\"mt-3 text-on-surface-variant\">\n When your app doesn't reach this import path, Tiptap (~120 KB gzipped) is\n tree-shaken out of the production bundle.\n </p>\n </div>\n </section>\n</div>\n",
|
|
111
138
|
"input": "<script lang=\"ts\">\n import { Input, FormField, FieldGroup, Separator } from '$lib/index.js'\n\n const variants = ['outline', 'soft', 'subtle', 'ghost', 'none'] as const\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n let bindValue = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">Input</h1>\n\n <!-- Basic Usage -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n A form input component with variants, colors, icons, and integration with FormField and\n FieldGroup.\n </p>\n <div class=\"max-w-sm\">\n <Input placeholder=\"Enter text...\" />\n </div>\n </section>\n\n <!-- Two-way Binding -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Two-way Binding</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bind:value</code\n > for reactive two-way data binding.\n </p>\n <div class=\"max-w-sm space-y-3\">\n <Input\n bind:value={bindValue}\n leadingIcon=\"lucide:pencil\"\n placeholder=\"Type something...\"\n />\n <p class=\"text-sm text-on-surface-variant\">\n Value: <span class=\"font-mono text-on-surface\">{bindValue || '(empty)'}</span>\n </p>\n <p class=\"text-sm text-on-surface-variant\">\n Length: <span class=\"font-mono text-on-surface\">{bindValue.length}</span>\n </p>\n </div>\n </section>\n\n <!-- Variants × Colors -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Variants × Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >variant</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code> to control\n appearance.\n </p>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full border-collapse\">\n <thead>\n <tr>\n <th class=\"px-3 py-2 text-left text-xs font-medium text-on-surface-variant\"\n ></th>\n {#each colors as color (color)}\n <th\n class=\"px-3 py-2 text-left text-xs font-medium text-on-surface-variant capitalize\"\n >{color}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each variants as variant (variant)}\n <tr>\n <td\n class=\"px-3 py-2 text-xs font-medium text-on-surface-variant capitalize\"\n >{variant}</td\n >\n {#each colors as color (color)}\n <td class=\"px-3 py-2\">\n <Input {variant} {color} placeholder={color} />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Size -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Size</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code> to\n control the dimensions and text size.\n </p>\n <div class=\"flex flex-wrap items-end gap-4\">\n {#each sizes as size (size)}\n <div class=\"w-48\">\n <Input {size} placeholder=\"{size} size\" />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >leadingIcon</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >trailingIcon</code\n > to add icons.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Input leadingIcon=\"lucide:search\" placeholder=\"Search...\" />\n </div>\n <div class=\"w-64\">\n <Input trailingIcon=\"lucide:eye\" placeholder=\"Password\" type=\"password\" />\n </div>\n <div class=\"w-64\">\n <Input\n leadingIcon=\"lucide:mail\"\n trailingIcon=\"lucide:check\"\n placeholder=\"Email\"\n type=\"email\"\n />\n </div>\n </div>\n </section>\n\n <!-- Icon (with trailing) -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Icon (with trailing)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code>\n with\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">trailing</code> to\n position it.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Input icon=\"lucide:user\" placeholder=\"Leading icon\" />\n </div>\n <div class=\"w-64\">\n <Input icon=\"lucide:user\" trailing placeholder=\"Trailing icon\" />\n </div>\n </div>\n </section>\n\n <!-- Avatar -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Avatar</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >avatar</code\n > to display an avatar before the input.\n </p>\n <div class=\"w-64\">\n <Input\n avatar={{ src: 'https://i.pravatar.cc/120?img=1', alt: 'User' }}\n placeholder=\"With avatar\"\n />\n </div>\n </section>\n\n <!-- Loading -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >loading</code\n > to show a loading spinner.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Input loading placeholder=\"Loading (leading)...\" />\n </div>\n <div class=\"w-64\">\n <Input loading trailing placeholder=\"Loading (trailing)...\" />\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Disabled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >disabled</code\n > to prevent interaction.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Input disabled placeholder=\"Disabled\" />\n </div>\n <div class=\"w-64\">\n <Input disabled value=\"Disabled with value\" />\n </div>\n </div>\n </section>\n\n <!-- Highlight -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Highlight</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >highlight</code\n > to emphasize the ring color like a focus state.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n {#each colors as color (color)}\n <div class=\"w-48\">\n <Input highlight {color} placeholder={color} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- FormField Integration -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">FormField Integration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When used inside a <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FormField</code\n >, the Input automatically inherits size, error state, and accessibility attributes.\n </p>\n <div class=\"max-w-sm space-y-4\">\n <FormField\n label=\"Email\"\n description=\"We'll use this to send you notifications.\"\n required\n >\n <Input leadingIcon=\"lucide:mail\" placeholder=\"Enter your email\" type=\"email\" />\n </FormField>\n\n <FormField\n label=\"Password\"\n help=\"Must be at least 8 characters.\"\n error=\"Password is too short.\"\n >\n <Input\n trailingIcon=\"lucide:eye\"\n placeholder=\"Enter your password\"\n type=\"password\"\n />\n </FormField>\n </div>\n </section>\n\n <!-- FieldGroup Integration -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">FieldGroup Integration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When used inside a <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FieldGroup</code\n >, inputs are visually connected.\n </p>\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">horizontal (default)</p>\n <FieldGroup orientation=\"horizontal\">\n <Input placeholder=\"First name\" />\n <Input placeholder=\"Last name\" />\n <Input placeholder=\"Email\" />\n </FieldGroup>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">vertical</p>\n <div class=\"max-w-sm\">\n <FieldGroup orientation=\"vertical\">\n <Input placeholder=\"Street address\" />\n <Input placeholder=\"City\" />\n <Input placeholder=\"Zip code\" />\n </FieldGroup>\n </div>\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Real World Examples</h2>\n\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Search bar</p>\n <div class=\"max-w-md\">\n <FieldGroup>\n <Input leadingIcon=\"lucide:search\" placeholder=\"Search products...\" />\n <Input placeholder=\"Location\" leadingIcon=\"lucide:map-pin\" />\n </FieldGroup>\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Login form</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <FormField label=\"Email\" required>\n <Input\n leadingIcon=\"lucide:mail\"\n placeholder=\"john@example.com\"\n type=\"email\"\n />\n </FormField>\n\n <FormField label=\"Password\" required help=\"Must be at least 8 characters.\">\n <Input\n leadingIcon=\"lucide:lock\"\n placeholder=\"Enter your password\"\n type=\"password\"\n />\n </FormField>\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Validation states</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <FormField label=\"Username\" error=\"Username is already taken.\">\n <Input value=\"admin\" color=\"error\" leadingIcon=\"lucide:user\" />\n </FormField>\n\n <FormField label=\"Email\" help=\"Looks good!\">\n <Input\n type=\"email\"\n value=\"valid@example.com\"\n color=\"success\"\n leadingIcon=\"lucide:mail\"\n />\n </FormField>\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
@@ -115,16 +142,24 @@
|
|
|
115
142
|
"slider": "<script lang=\"ts\">\n import { Slider, FormField } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n let basicValue = $state(40)\n let rangeValue = $state([20, 80])\n let stepValue = $state(50)\n let tooltipValue = $state(65)\n let tooltipRangeValue = $state([30, 70])\n let verticalValue = $state(60)\n let verticalRangeValue = $state([25, 75])\n let formValue = $state(50)\n let formFieldValue = $state(40)\n let formFieldErrorValue = $state(15)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Slider</h1>\n <p class=\"text-on-surface-variant\">\n Accessible range input built on bits-ui. Supports single thumb, range, tooltip, and all\n orientations.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <Slider bind:value={basicValue} />\n <p class=\"text-sm text-on-surface-variant\">Value: {basicValue}</p>\n </div>\n </section>\n\n <!-- Range -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Range (Multiple Thumbs)</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <Slider bind:value={rangeValue} />\n <p class=\"text-sm text-on-surface-variant\">Value: [{rangeValue.join(', ')}]</p>\n </div>\n </section>\n\n <!-- Step -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Step</h2>\n <div class=\"grid grid-cols-1 gap-6 rounded-lg bg-surface-container-high p-6 sm:grid-cols-2\">\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">step=10</p>\n <Slider bind:value={stepValue} step={10} />\n <p class=\"text-sm text-on-surface-variant\">Value: {stepValue}</p>\n </div>\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">\n Discrete steps: [0, 25, 50, 75, 100]\n </p>\n <Slider value={25} step={[0, 25, 50, 75, 100]} />\n </div>\n </div>\n </section>\n\n <!-- Tooltip -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Tooltip</h2>\n <div class=\"grid grid-cols-1 gap-8 rounded-lg bg-surface-container-high p-6 sm:grid-cols-2\">\n <div class=\"space-y-3 pt-6\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Single</p>\n <Slider bind:value={tooltipValue} tooltip />\n </div>\n <div class=\"space-y-3 pt-6\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Range</p>\n <Slider bind:value={tooltipRangeValue} tooltip />\n </div>\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <div class=\"flex items-start gap-12\">\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Horizontal (default)</p>\n <div class=\"w-64\">\n <Slider bind:value={verticalValue} />\n </div>\n </div>\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Vertical</p>\n <div class=\"h-40\">\n <Slider bind:value={verticalValue} orientation=\"vertical\" />\n </div>\n </div>\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Vertical Range</p>\n <div class=\"h-40\">\n <Slider bind:value={verticalRangeValue} orientation=\"vertical\" />\n </div>\n </div>\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Vertical + Tooltip</p>\n <div class=\"h-40 ps-6\">\n <Slider bind:value={verticalValue} orientation=\"vertical\" tooltip />\n </div>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n {#each colors as color (color)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-20 text-sm text-on-surface-variant\">{color}</span>\n <div class=\"flex-1\">\n <Slider {color} value={55} />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-5 rounded-lg bg-surface-container-high p-6\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-6 text-xs text-on-surface-variant\">{size}</span>\n <div class=\"flex-1\">\n <Slider {size} value={60} />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <div class=\"grid grid-cols-1 gap-6 rounded-lg bg-surface-container-high p-6 sm:grid-cols-2\">\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Single</p>\n <Slider value={40} disabled />\n </div>\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Range</p>\n <Slider value={[25, 75]} disabled />\n </div>\n </div>\n </section>\n\n <!-- Min / Max -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Min / Max</h2>\n <div class=\"grid grid-cols-1 gap-6 rounded-lg bg-surface-container-high p-6 sm:grid-cols-2\">\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">min=20, max=80</p>\n <Slider min={20} max={80} value={50} tooltip />\n </div>\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium text-on-surface-variant\">min=-50, max=50</p>\n <Slider min={-50} max={50} value={0} tooltip />\n </div>\n </div>\n </section>\n\n <!-- Form Integration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Form Integration</h2>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <form\n class=\"max-w-sm space-y-4\"\n onsubmit={(e) => {\n e.preventDefault()\n alert(`volume = ${formValue}`)\n }}\n >\n <div class=\"space-y-2\">\n <label class=\"block text-sm font-medium text-on-surface\" for=\"vol-out\">\n Volume: {formValue}\n </label>\n <Slider name=\"volume\" bind:value={formValue} color=\"success\" />\n </div>\n <button\n type=\"submit\"\n class=\"rounded-md bg-primary px-4 py-2 text-sm font-medium text-on-primary\"\n >\n Submit\n </button>\n </form>\n </div>\n </section>\n\n <!-- FormField Integration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">FormField Integration</h2>\n <div class=\"max-w-sm space-y-4 rounded-lg bg-surface-container-high p-6\">\n <FormField\n label=\"Volume\"\n description=\"Adjust the playback volume.\"\n hint=\"{formFieldValue}%\"\n >\n <Slider bind:value={formFieldValue} class=\"mt-1\" />\n </FormField>\n\n <FormField\n label=\"Brightness\"\n required\n help=\"Recommended between 20–80 for eye comfort.\"\n >\n <Slider bind:value={formFieldValue} color=\"warning\" class=\"mt-1\" />\n </FormField>\n\n <FormField\n label=\"Quality\"\n error={formFieldErrorValue < 20 ? 'Value must be at least 20.' : undefined}\n >\n <Slider bind:value={formFieldErrorValue} class=\"mt-1\" />\n </FormField>\n </div>\n </section>\n\n <!-- Custom ui -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom UI Slots</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">\n Thick track + large rounded thumb\n </p>\n <Slider\n value={55}\n color=\"tertiary\"\n ui={{\n track: 'h-4 rounded-md',\n range: 'rounded-md',\n thumb: 'size-6 rounded-md shadow-md'\n }}\n />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom tooltip style</p>\n <Slider\n value={70}\n tooltip\n color=\"info\"\n ui={{ tooltip: 'bg-info text-on-info rounded-full px-2' }}\n class=\"pt-6\"\n />\n </div>\n </div>\n </section>\n</div>\n",
|
|
116
143
|
"switch": "<script lang=\"ts\">\n import { Switch, FormField, Separator } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n let bindChecked = $state(false)\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">Switch</h1>\n\n <!-- Basic Usage -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">A toggle switch component for binary states.</p>\n <Switch />\n </section>\n\n <!-- Two-way Binding -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Two-way Binding</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bind:checked</code\n > for reactive two-way data binding.\n </p>\n <div class=\"flex flex-wrap items-center gap-6\">\n <Switch bind:checked={bindChecked} label=\"Toggle me\" />\n <p class=\"text-sm text-on-surface-variant\">\n Checked: <span class=\"font-mono text-on-surface\">{bindChecked}</span>\n </p>\n </div>\n </section>\n\n <!-- Label & Description -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Label & Description</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">label</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >description</code\n > to add text next to the switch.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Switch label=\"Notifications\" />\n <Switch label=\"Dark mode\" description=\"Enable dark mode for the application.\" />\n <Switch\n label=\"Marketing emails\"\n description=\"Receive emails about new products and features.\"\n />\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code\n > to control the checked background color.\n </p>\n <div class=\"flex flex-wrap gap-6\">\n {#each colors as color (color)}\n <Switch {color} checked={true} label={color} />\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Sizes</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code> to\n control the dimensions.\n </p>\n <div class=\"flex flex-wrap items-center gap-6\">\n {#each sizes as size (size)}\n <Switch {size} checked={true} label={size} />\n {/each}\n </div>\n </section>\n\n <!-- Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >checkedIcon</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >uncheckedIcon</code\n > to display icons inside the thumb.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Switch\n checkedIcon=\"lucide:check\"\n uncheckedIcon=\"lucide:x\"\n label=\"With check / x icons\"\n />\n <Switch\n checkedIcon=\"lucide:sun\"\n uncheckedIcon=\"lucide:moon\"\n label=\"Theme toggle\"\n description=\"Switch between light and dark mode.\"\n />\n <Switch checkedIcon=\"lucide:volume-2\" uncheckedIcon=\"lucide:volume-x\" label=\"Sound\" />\n <Switch\n checkedIcon=\"lucide:wifi\"\n uncheckedIcon=\"lucide:wifi-off\"\n label=\"Wi-Fi\"\n color=\"success\"\n />\n </div>\n </section>\n\n <!-- Loading -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >loading</code\n > to show a spinner inside the thumb and disable interaction.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Switch loading label=\"Syncing...\" />\n <Switch loading checked={true} label=\"Saving preferences...\" color=\"success\" />\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Disabled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >disabled</code\n > to prevent interaction.\n </p>\n <div class=\"flex flex-wrap gap-6\">\n <Switch disabled label=\"Disabled (off)\" />\n <Switch disabled checked={true} label=\"Disabled (on)\" />\n </div>\n </section>\n\n <!-- Required -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Required</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >required</code\n > to show an asterisk next to the label.\n </p>\n <Switch required label=\"Accept terms and conditions\" />\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Custom Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >labelSlot</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >descriptionSlot</code\n > for fully custom label content.\n </p>\n <div class=\"flex flex-col gap-4\">\n <Switch checked={true} color=\"success\">\n {#snippet labelSlot()}\n <span class=\"flex items-center gap-1.5 text-sm font-medium text-on-surface\">\n <span class=\"inline-block size-2 rounded-full bg-success\"></span>\n System online\n </span>\n {/snippet}\n {#snippet descriptionSlot()}\n <span class=\"text-xs text-on-surface-variant\">\n All services are running normally. Last checked <strong>just now</strong>.\n </span>\n {/snippet}\n </Switch>\n\n <Switch>\n {#snippet labelSlot()}\n <span class=\"flex items-center gap-2 text-sm font-medium text-on-surface\">\n Beta features\n <span\n class=\"rounded bg-tertiary-container px-1.5 py-0.5 text-[10px] font-bold text-on-tertiary-container\"\n >BETA</span\n >\n </span>\n {/snippet}\n </Switch>\n </div>\n </section>\n\n <!-- FormField Integration -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">FormField Integration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When used inside a <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FormField</code\n >, the Switch automatically inherits size and error state.\n </p>\n <div class=\"max-w-sm space-y-4\">\n <FormField label=\"Notifications\" description=\"Choose how you want to be notified.\">\n <Switch label=\"Push notifications\" />\n </FormField>\n <FormField label=\"Agreement\" error=\"You must accept the terms.\">\n <Switch label=\"I accept the terms of service\" />\n </FormField>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Real World Examples</h2>\n\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Notification preferences</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <Switch\n checked={true}\n label=\"Push notifications\"\n description=\"Receive alerts on your device.\"\n color=\"primary\"\n />\n <Switch\n checked={true}\n label=\"Email digest\"\n description=\"Daily summary of activity.\"\n color=\"primary\"\n />\n <Switch\n label=\"SMS alerts\"\n description=\"Text messages for critical updates.\"\n color=\"primary\"\n />\n <Switch\n loading\n label=\"Marketing emails\"\n description=\"Updating preference...\"\n color=\"primary\"\n />\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Privacy settings</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <Switch\n checked={true}\n checkedIcon=\"lucide:eye\"\n uncheckedIcon=\"lucide:eye-off\"\n label=\"Profile visibility\"\n description=\"Make your profile public.\"\n color=\"secondary\"\n />\n <Switch\n checkedIcon=\"lucide:share-2\"\n uncheckedIcon=\"lucide:share-2\"\n label=\"Data sharing\"\n description=\"Share usage data to improve the product.\"\n color=\"secondary\"\n />\n <Switch\n checked={true}\n checkedIcon=\"lucide:shield-check\"\n uncheckedIcon=\"lucide:shield-off\"\n label=\"Two-factor auth\"\n description=\"Add an extra layer of security.\"\n color=\"success\"\n disabled\n />\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
117
144
|
"textarea": "<script lang=\"ts\">\n import { Textarea, FormField, FieldGroup, Separator } from '$lib/index.js'\n\n const variants = ['outline', 'soft', 'subtle', 'ghost', 'none'] as const\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n let bindValue = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">Textarea</h1>\n\n <!-- Basic Usage -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n A multi-line text input component with variants, colors, icons, autoresize, and\n integration with FormField and FieldGroup.\n </p>\n <div class=\"max-w-sm\">\n <Textarea placeholder=\"Enter text...\" />\n </div>\n </section>\n\n <!-- Two-way Binding -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Two-way Binding</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bind:value</code\n > for reactive two-way data binding.\n </p>\n <div class=\"max-w-sm space-y-3\">\n <Textarea\n bind:value={bindValue}\n leadingIcon=\"lucide:pencil\"\n placeholder=\"Type something...\"\n />\n <p class=\"text-sm text-on-surface-variant\">\n Value: <span class=\"font-mono text-on-surface\">{bindValue || '(empty)'}</span>\n </p>\n <p class=\"text-sm text-on-surface-variant\">\n Length: <span class=\"font-mono text-on-surface\">{bindValue.length}</span>\n </p>\n </div>\n </section>\n\n <!-- Variants × Colors -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Variants × Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >variant</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code> to control\n appearance.\n </p>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full border-collapse\">\n <thead>\n <tr>\n <th class=\"px-3 py-2 text-left text-xs font-medium text-on-surface-variant\"\n ></th>\n {#each colors as color (color)}\n <th\n class=\"px-3 py-2 text-left text-xs font-medium text-on-surface-variant capitalize\"\n >{color}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each variants as variant (variant)}\n <tr>\n <td\n class=\"px-3 py-2 text-xs font-medium text-on-surface-variant capitalize\"\n >{variant}</td\n >\n {#each colors as color (color)}\n <td class=\"px-3 py-2\">\n <Textarea {variant} {color} placeholder={color} rows={2} />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Size -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Size</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code> to\n control the dimensions and text size.\n </p>\n <div class=\"flex flex-wrap items-end gap-4\">\n {#each sizes as size (size)}\n <div class=\"w-48\">\n <Textarea {size} placeholder=\"{size} size\" rows={2} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Rows -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Rows</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">rows</code> to\n set the number of visible text lines.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Textarea rows={2} placeholder=\"2 rows\" />\n </div>\n <div class=\"w-64\">\n <Textarea rows={4} placeholder=\"4 rows\" />\n </div>\n <div class=\"w-64\">\n <Textarea rows={6} placeholder=\"6 rows\" />\n </div>\n </div>\n </section>\n\n <!-- Autoresize -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Autoresize</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >autoresize</code\n >\n to automatically adjust height based on content. Combine with\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">maxrows</code> to\n limit growth.\n </p>\n <div class=\"space-y-4\">\n <div class=\"max-w-sm\">\n <p class=\"mb-2 text-xs text-on-surface-variant\">Unlimited autoresize</p>\n <Textarea autoresize placeholder=\"Type to grow...\" rows={1} />\n </div>\n <div class=\"max-w-sm\">\n <p class=\"mb-2 text-xs text-on-surface-variant\">Autoresize with maxrows=5</p>\n <Textarea autoresize maxrows={5} placeholder=\"Grows up to 5 rows...\" rows={1} />\n </div>\n </div>\n </section>\n\n <!-- Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >leadingIcon</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >trailingIcon</code\n > to add icons.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Textarea leadingIcon=\"lucide:message-square\" placeholder=\"Comment...\" rows={2} />\n </div>\n <div class=\"w-64\">\n <Textarea trailingIcon=\"lucide:send\" placeholder=\"Message...\" rows={2} />\n </div>\n <div class=\"w-64\">\n <Textarea\n leadingIcon=\"lucide:file-text\"\n trailingIcon=\"lucide:check\"\n placeholder=\"Description...\"\n rows={2}\n />\n </div>\n </div>\n </section>\n\n <!-- Icon (with trailing) -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Icon (with trailing)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code>\n with\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">trailing</code> to\n position it.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Textarea icon=\"lucide:pencil\" placeholder=\"Leading icon\" rows={2} />\n </div>\n <div class=\"w-64\">\n <Textarea icon=\"lucide:pencil\" trailing placeholder=\"Trailing icon\" rows={2} />\n </div>\n </div>\n </section>\n\n <!-- Loading -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >loading</code\n > to show a loading spinner.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Textarea loading placeholder=\"Loading (leading)...\" rows={2} />\n </div>\n <div class=\"w-64\">\n <Textarea loading trailing placeholder=\"Loading (trailing)...\" rows={2} />\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Disabled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >disabled</code\n > to prevent interaction.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n <div class=\"w-64\">\n <Textarea disabled placeholder=\"Disabled\" rows={2} />\n </div>\n <div class=\"w-64\">\n <Textarea disabled value=\"Disabled with value\" rows={2} />\n </div>\n </div>\n </section>\n\n <!-- Highlight -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Highlight</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >highlight</code\n > to emphasize the ring color like a focus state.\n </p>\n <div class=\"flex flex-wrap gap-4\">\n {#each colors as color (color)}\n <div class=\"w-48\">\n <Textarea highlight {color} placeholder={color} rows={2} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- FormField Integration -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">FormField Integration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When used inside a <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FormField</code\n >, the Textarea automatically inherits size, error state, and accessibility attributes.\n </p>\n <div class=\"max-w-sm space-y-4\">\n <FormField\n label=\"Description\"\n description=\"Provide a detailed description of the issue.\"\n required\n >\n <Textarea leadingIcon=\"lucide:file-text\" placeholder=\"Describe the issue...\" />\n </FormField>\n\n <FormField\n label=\"Notes\"\n help=\"Optional additional notes.\"\n error=\"Notes cannot be empty.\"\n >\n <Textarea placeholder=\"Enter notes...\" />\n </FormField>\n </div>\n </section>\n\n <!-- FieldGroup Integration -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">FieldGroup Integration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When used inside a <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FieldGroup</code\n >, textareas are visually connected.\n </p>\n <div class=\"max-w-sm\">\n <FieldGroup orientation=\"vertical\">\n <Textarea placeholder=\"Title\" rows={1} />\n <Textarea placeholder=\"Description\" rows={3} />\n <Textarea placeholder=\"Additional notes\" rows={2} />\n </FieldGroup>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Real World Examples</h2>\n\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Feedback form</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <FormField label=\"Subject\" required>\n <Textarea placeholder=\"Brief summary...\" rows={1} autoresize />\n </FormField>\n\n <FormField label=\"Details\" required help=\"Be as specific as possible.\">\n <Textarea\n placeholder=\"Describe your feedback in detail...\"\n autoresize\n maxrows={8}\n />\n </FormField>\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Chat message input</p>\n <div class=\"max-w-md\">\n <Textarea\n leadingIcon=\"lucide:message-square\"\n trailingIcon=\"lucide:send\"\n placeholder=\"Type a message...\"\n autoresize\n maxrows={5}\n rows={1}\n />\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Code snippet input</p>\n <div class=\"max-w-md\">\n <Textarea\n variant=\"soft\"\n color=\"surface\"\n placeholder=\"Paste your code here...\"\n rows={6}\n ui={{ base: 'font-mono text-xs' }}\n />\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
145
|
+
"password-input": "<script lang=\"ts\">\n import { PasswordInput } from '$lib/index.js'\n \n let pwd1 = $state('')\n let pwd2 = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">PasswordInput</h1>\n <p class=\"text-on-surface-variant\">\n A secure input for passwords with a built-in visibility toggle and strength meter.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Behaves like a regular <code class=\"rounded bg-surface-container-highest px-1\">Input</code> but adds an eye icon to toggle visibility.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 max-w-sm\">\n <PasswordInput bind:value={pwd1} placeholder=\"Enter your password\" />\n </div>\n </section>\n\n <!-- Strength Meter -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Strength Meter</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1\">showStrength={`{true}`}</code> to display a color-coded strength indicator below the input.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 max-w-sm\">\n <PasswordInput bind:value={pwd2} showStrength={true} placeholder=\"Create a strong password\" />\n </div>\n </section>\n</div>\n",
|
|
118
146
|
"file-upload": "<script lang=\"ts\">\n import { FileUpload, Button, Form, FormField } from '$lib/index.js'\n import type { FileUploadRejection, FormApi } from '$lib/index.js'\n import { z } from 'zod'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n let basicFiles = $state<File[]>([])\n let multipleFiles = $state<File[]>([])\n let gridSingleFile = $state<File[]>([])\n let gridMultipleFiles = $state<File[]>([])\n let buttonFiles = $state<File[]>([])\n let actionsFiles = $state<File[]>([])\n let highlightFiles = $state<File[]>([])\n let noDropzoneFiles = $state<File[]>([])\n let noPreviewFiles = $state<File[]>([])\n let imageFiles = $state<File[]>([])\n\n let maxSizeFiles = $state<File[]>([])\n let maxFilesFiles = $state<File[]>([])\n let validationFiles = $state<File[]>([])\n let rejections = $state<FileUploadRejection[]>([])\n let combinedRejections = $state<FileUploadRejection[]>([])\n\n function reasonLabel(reason: FileUploadRejection['reason']): string {\n if (reason === 'maxSize') return 'too large'\n if (reason === 'maxFiles') return 'too many'\n return 'wrong type'\n }\n\n const fileUploadSchema = z.object({\n avatar: z.array(z.instanceof(File)).min(1, 'Avatar is required'),\n gallery: z.array(z.instanceof(File)).min(2, 'Pick at least 2 images')\n })\n\n let fileUploadFormState = $state<{ avatar: File[]; gallery: File[] }>({\n avatar: [],\n gallery: []\n })\n let fileUploadFormApi = $state<FormApi<unknown>>()\n let fileUploadSubmitted = $state<string | null>(null)\n\n function handleFileUploadSubmit(event: { data: unknown }) {\n const data = event.data as { avatar: File[]; gallery: File[] }\n fileUploadSubmitted = JSON.stringify(\n {\n avatar: data.avatar.map((f) => f.name),\n gallery: data.gallery.map((f) => f.name)\n },\n null,\n 2\n )\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">FileUpload</h1>\n <p class=\"text-on-surface-variant\">\n Upload files via drag-and-drop or file dialog with preview and removal.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload bind:value={basicFiles} />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n Selected: {basicFiles.length ? basicFiles.map((f) => f.name).join(', ') : 'none'}\n </p>\n </div>\n </section>\n\n <!-- Multiple Files -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Multiple Files</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload\n bind:value={multipleFiles}\n multiple\n label=\"Drop multiple files here\"\n description=\"Upload as many files as you need\"\n />\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Area (default)</p>\n <FileUpload label=\"Drop files here\" description=\"Supports drag & drop\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Button</p>\n <FileUpload variant=\"button\" label=\"Upload file\" bind:value={buttonFiles} />\n {#if buttonFiles.length > 0}\n <p class=\"text-sm text-on-surface-variant\">\n {buttonFiles.map((f) => f.name).join(', ')}\n </p>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- Layout -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Layout</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">List (default)</p>\n <FileUpload\n bind:value={multipleFiles}\n multiple\n layout=\"list\"\n label=\"Upload files\"\n description=\"Files shown as list\"\n />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Grid · Multiple</p>\n <FileUpload\n bind:value={gridMultipleFiles}\n multiple\n layout=\"grid\"\n label=\"Upload images\"\n description=\"Files shown as grid thumbnails\"\n accept=\"image/*\"\n />\n </div>\n </div>\n </section>\n\n <!-- Grid Single File -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Grid · Single File Preview</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"max-w-xs\">\n <FileUpload\n bind:value={gridSingleFile}\n layout=\"grid\"\n label=\"Upload image\"\n description=\"Preview fills the area\"\n accept=\"image/*\"\n ui={{ base: 'min-h-48' }}\n />\n </div>\n </div>\n </section>\n\n <!-- Dropzone vs No Dropzone -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Dropzone</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With dropzone (default)</p>\n <FileUpload\n label=\"Drag & drop or click\"\n description=\"Drag files anywhere on this area\"\n />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">No dropzone · click only</p>\n <FileUpload\n bind:value={noDropzoneFiles}\n dropzone={false}\n label=\"Click to upload\"\n description=\"No drag-and-drop\"\n />\n </div>\n </div>\n </section>\n\n <!-- Custom Actions Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Actions</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload\n bind:value={actionsFiles}\n multiple\n interactive={false}\n label=\"Drag files here\"\n description=\"or click the button below\"\n >\n {#snippet actionsSlot({ open })}\n <Button\n size=\"sm\"\n variant=\"outline\"\n color=\"primary\"\n leadingIcon=\"lucide:folder-open\"\n label=\"Browse files\"\n onclick={open}\n />\n {/snippet}\n </FileUpload>\n </div>\n </section>\n\n <!-- Highlight -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Highlight</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Normal</p>\n <FileUpload bind:value={basicFiles} label=\"No highlight\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Highlighted</p>\n <FileUpload bind:value={highlightFiles} highlight label=\"Highlighted border\" />\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <FileUpload\n {color}\n highlight\n label={color}\n description=\"Drop files or click to browse\"\n ui={{ wrapper: 'py-2' }}\n />\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex items-start gap-4\">\n <span class=\"w-6 pt-3 text-xs text-on-surface-variant\">{size}</span>\n <FileUpload\n {size}\n label=\"Upload file\"\n description=\"Drag & drop or click\"\n class=\"flex-1\"\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Disabled & Loading -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">States</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Disabled</p>\n <FileUpload disabled label=\"Disabled upload\" description=\"Cannot interact\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Loading</p>\n <FileUpload loading label=\"Processing files…\" description=\"Please wait\" />\n </div>\n </div>\n </section>\n\n <!-- No Preview -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Preview Disabled</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload\n bind:value={noPreviewFiles}\n multiple\n preview={false}\n label=\"Upload without preview\"\n description=\"Files are selected but not listed\"\n />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n {noPreviewFiles.length} file(s) selected\n </p>\n </div>\n </section>\n\n <!-- Accept Filter -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Accept Filter</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Images only</p>\n <FileUpload\n bind:value={imageFiles}\n multiple\n accept=\"image/*\"\n color=\"tertiary\"\n icon=\"lucide:image\"\n label=\"Drop images here\"\n description=\"Only image files accepted\"\n />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">PDF only</p>\n <FileUpload\n accept=\".pdf\"\n color=\"error\"\n icon=\"lucide:file-text\"\n label=\"Drop PDF here\"\n description=\"Only .pdf files accepted\"\n />\n </div>\n </div>\n </section>\n\n <!-- Image Preview -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Image Preview</h2>\n <div class=\"grid grid-cols-1 gap-4 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Preview enabled (default)</p>\n <FileUpload\n bind:value={imageFiles}\n multiple\n accept=\"image/*\"\n icon=\"lucide:image\"\n label=\"Drop images here\"\n description=\"Click the zoom icon to preview\"\n />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Preview disabled</p>\n <FileUpload\n multiple\n accept=\"image/*\"\n icon=\"lucide:image\"\n label=\"Drop images here\"\n description=\"No preview button shown\"\n imagePreview={false}\n />\n </div>\n </div>\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload multiple bind:value={basicFiles} color=\"success\">\n {#snippet leadingSlot()}\n <div\n class=\"flex size-12 items-center justify-center rounded-full bg-success-container text-on-success-container\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n class=\"size-6\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12\"\n />\n </svg>\n </div>\n {/snippet}\n {#snippet labelSlot()}\n <span class=\"mt-2 block text-sm font-semibold text-success\">\n Drag & drop your files\n </span>\n {/snippet}\n {#snippet descriptionSlot()}\n <span class=\"mt-1 block text-xs text-on-surface-variant\">\n PNG, JPG, GIF up to 10MB each\n </span>\n {/snippet}\n </FileUpload>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- Avatar Upload -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Avatar Upload</p>\n <div class=\"max-w-xs\">\n <FileUpload\n layout=\"grid\"\n accept=\"image/*\"\n icon=\"lucide:user-round\"\n label=\"Upload photo\"\n description=\"PNG or JPG up to 2MB\"\n color=\"primary\"\n ui={{ base: 'min-h-40 rounded-full', wrapper: 'py-4' }}\n />\n </div>\n </div>\n\n <!-- Document Upload -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Document Upload</p>\n <div\n class=\"max-w-lg space-y-4 rounded-lg border border-outline-variant bg-surface-container p-4\"\n >\n <div>\n <p class=\"mb-1 text-sm font-medium text-on-surface\">Resume *</p>\n <FileUpload\n accept=\".pdf,.doc,.docx\"\n icon=\"lucide:file-text\"\n label=\"Upload your resume\"\n description=\"PDF, DOC, DOCX up to 5MB\"\n size=\"sm\"\n color=\"primary\"\n />\n </div>\n <div>\n <p class=\"mb-1 text-sm font-medium text-on-surface\">Portfolio (optional)</p>\n <FileUpload\n multiple\n accept=\"image/*,.pdf\"\n icon=\"lucide:briefcase\"\n label=\"Upload portfolio files\"\n description=\"Images or PDFs\"\n size=\"sm\"\n color=\"secondary\"\n />\n </div>\n </div>\n </div>\n\n <!-- Gallery Upload -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Gallery Upload</p>\n <FileUpload\n multiple\n layout=\"grid\"\n accept=\"image/*\"\n icon=\"lucide:images\"\n label=\"Upload gallery images\"\n description=\"Drop multiple images to create a gallery\"\n color=\"tertiary\"\n />\n </div>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Max size per file</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code>maxSize</code> (bytes) to reject files above a threshold. Rejections are\n reported through <code>onReject</code>.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload\n bind:value={maxSizeFiles}\n multiple\n maxSize={1024 * 1024}\n label=\"Max 1 MB per file\"\n description=\"Try a small text file vs. a high-res image\"\n onReject={(r) => (rejections = r)}\n />\n {#if rejections.length}\n <ul class=\"mt-3 space-y-1 text-sm text-error\">\n {#each rejections as r (`${r.file.name}-${r.reason}`)}\n <li>\n {r.file.name} — {reasonLabel(r.reason)}\n </li>\n {/each}\n </ul>\n {/if}\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Max files count</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code>maxFiles</code> to cap the number of files in the selection. When the cap is\n reached, the root element exposes <code>data-full</code> so CSS can style the area as inactive.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload\n bind:value={maxFilesFiles}\n multiple\n maxFiles={3}\n label=\"Up to 3 files\"\n description=\"Try selecting 4 — the 4th is rejected\"\n ui={{\n base: 'data-[full]:opacity-60 data-[full]:pointer-events-none'\n }}\n />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n {maxFilesFiles.length} / 3 selected\n </p>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Combined validation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n All three rules (<code>accept</code>, <code>maxSize</code>, <code>maxFiles</code>) work\n together. <code>onReject</code> reports every rejected file in one call with its reason.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <FileUpload\n bind:value={validationFiles}\n multiple\n accept=\"image/*\"\n maxSize={2 * 1024 * 1024}\n maxFiles={5}\n label=\"Up to 5 images, max 2 MB each\"\n description=\"image/* — up to 5 files — max 2 MB\"\n onReject={(r) => (combinedRejections = r)}\n />\n {#if combinedRejections.length}\n <ul class=\"mt-3 space-y-1 text-sm text-error\">\n {#each combinedRejections as r (`${r.file.name}-${r.reason}`)}\n <li>\n {r.file.name} — {reasonLabel(r.reason)}\n </li>\n {/each}\n </ul>\n {/if}\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Inside a Form (Zod schema)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n FileUpload reads the parent <code>FormField</code> + <code>Form</code> context. When the\n Zod schema fails, the FormField shows the error and FileUpload picks up\n <code>aria-invalid</code> + the error highlight color. Once you pick the required number of\n files, the schema passes and the error clears automatically — no manual error state.\n </p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={fileUploadFormApi}\n bind:state={fileUploadFormState}\n schema={fileUploadSchema}\n onsubmit={handleFileUploadSubmit}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"avatar\" label=\"Avatar\" description=\"JPG or PNG, max 5 MB\" required>\n <FileUpload\n bind:value={fileUploadFormState.avatar}\n accept=\"image/*\"\n label=\"Drop avatar here\"\n />\n </FormField>\n\n <FormField name=\"gallery\" label=\"Gallery\" required>\n <FileUpload\n bind:value={fileUploadFormState.gallery}\n multiple\n layout=\"grid\"\n accept=\"image/*\"\n label=\"Drop at least 2 images\"\n />\n </FormField>\n\n <div class=\"flex items-center gap-3\">\n <Button type=\"submit\" loading={fileUploadFormApi?.loading}>Submit</Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n color=\"secondary\"\n onclick={() => fileUploadFormApi?.clear()}\n >\n Clear errors\n </Button>\n </div>\n </Form>\n\n {#if fileUploadSubmitted}\n <div\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n <p class=\"font-medium\">Submitted:</p>\n <pre class=\"mt-1 text-xs\">{fileUploadSubmitted}</pre>\n </div>\n {/if}\n </div>\n </section>\n</div>\n",
|
|
119
147
|
"pin-input": "<script lang=\"ts\">\n import { PinInput, FormField } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n const variants = ['outline', 'soft', 'subtle', 'ghost', 'none'] as const\n\n let basicValue = $state('')\n let otpValue = $state('')\n let numericValue = $state('')\n let maskedValue = $state('')\n let highlightValue = $state('')\n let formValue = $state('')\n let formFieldValue = $state('')\n let formFieldErrorValue = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Pin Input</h1>\n <p class=\"text-on-surface-variant\">\n Accessible PIN / OTP input built on bits-ui. Supports masking, numeric mode, OTP\n autocomplete, and form integration.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <PinInput bind:value={basicValue} />\n <p class=\"text-sm text-on-surface-variant\">Value: \"{basicValue}\"</p>\n </div>\n </section>\n\n <!-- Lengths -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Length</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">4 cells</p>\n <PinInput length={4} />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">5 cells (default)</p>\n <PinInput />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">6 cells (OTP)</p>\n <PinInput bind:value={otpValue} length={6} otp />\n </div>\n </div>\n </section>\n\n <!-- Type -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Type</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">\n type=\"number\" — digits only\n </p>\n <PinInput bind:value={numericValue} type=\"number\" length={6} />\n <p class=\"text-sm text-on-surface-variant\">Value: \"{numericValue}\"</p>\n </div>\n </div>\n </section>\n\n <!-- Mask -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Mask</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">\n mask — shows ● instead of characters\n </p>\n <PinInput bind:value={maskedValue} mask />\n <p class=\"text-sm text-on-surface-variant\">Value: \"{maskedValue}\"</p>\n </div>\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n {#each variants as variant (variant)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-16 text-sm text-on-surface-variant\">{variant}</span>\n <PinInput {variant} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n {#each colors as color (color)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-20 text-sm text-on-surface-variant\">{color}</span>\n <PinInput {color} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-5 rounded-lg bg-surface-container-high p-6\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-6 text-xs text-on-surface-variant\">{size}</span>\n <PinInput {size} fixed />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Highlight -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Highlight</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">\n highlight — all cells show the color ring (e.g. error state)\n </p>\n <PinInput bind:value={highlightValue} highlight color=\"error\" />\n </div>\n </div>\n </section>\n\n <!-- Placeholder -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Placeholder</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Default (○)</p>\n <PinInput />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom (–)</p>\n <PinInput placeholder=\"–\" />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Digit hint (0)</p>\n <PinInput placeholder=\"0\" type=\"number\" length={6} />\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <PinInput defaultValue=\"12\" disabled />\n </div>\n </section>\n\n <!-- Form Integration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Form Integration</h2>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <form\n class=\"max-w-sm space-y-4\"\n onsubmit={(e) => {\n e.preventDefault()\n const fd = new FormData(e.currentTarget)\n alert(`otp = ${fd.get('otp')}`)\n }}\n >\n <div class=\"space-y-2\">\n <label class=\"block text-sm font-medium text-on-surface\" for=\"otp-input\"\n >OTP Code</label\n >\n <PinInput\n bind:value={formValue}\n name=\"otp\"\n inputId=\"otp-input\"\n length={6}\n type=\"number\"\n otp\n color=\"success\"\n />\n </div>\n <button\n type=\"submit\"\n class=\"rounded-md bg-primary px-4 py-2 text-sm font-medium text-on-primary\"\n >\n Verify\n </button>\n </form>\n </div>\n </section>\n\n <!-- FormField Integration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">FormField Integration</h2>\n <div class=\"max-w-sm space-y-4 rounded-lg bg-surface-container-high p-6\">\n <FormField label=\"Verification Code\" description=\"Enter the 6-digit code sent to you.\">\n <PinInput bind:value={formFieldValue} length={6} type=\"number\" otp class=\"mt-1\" />\n </FormField>\n\n <FormField\n label=\"PIN\"\n error={formFieldErrorValue.length > 0 && formFieldErrorValue.length < 4\n ? 'PIN must be 4 digits.'\n : undefined}\n >\n <PinInput bind:value={formFieldErrorValue} length={4} class=\"mt-1\" />\n </FormField>\n </div>\n </section>\n\n <!-- Custom UI Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom UI Slots</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Rounded full cells</p>\n <PinInput color=\"tertiary\" ui={{ base: 'rounded-full' }} />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Large gap</p>\n <PinInput size=\"lg\" color=\"secondary\" ui={{ root: 'gap-3' }} fixed />\n </div>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass <code>loading</code> to overlay a spinner and disable input while verifying the\n code (e.g. checking an OTP against the backend). Use <code>loadingIcon</code> to customize\n the spinner.\n </p>\n <div class=\"flex flex-wrap items-center gap-6 rounded-lg bg-surface-container-high p-4\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Loading</p>\n <PinInput loading length={6} />\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Loading + masked</p>\n <PinInput loading mask color=\"primary\" length={4} />\n </div>\n </div>\n </section>\n</div>\n",
|
|
148
|
+
"rating": "<script lang=\"ts\">\n import { Rating } from '$lib/index.js'\n \n let rating1 = $state(3)\n let rating2 = $state(4.5)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Rating</h1>\n <p class=\"text-on-surface-variant\">\n A star rating input component supporting half-stars, read-only mode, and custom icons.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">bind:value</code> to control the rating.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <Rating bind:value={rating1} />\n <div class=\"text-sm text-on-surface-variant\">\n <strong>Current Value:</strong> {rating1}\n </div>\n </div>\n </section>\n\n <!-- Half Stars -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Half Stars</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1\">allowHalf={`{true}`}</code> to enable half-star precision rating via mouse or keyboard.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <Rating bind:value={rating2} allowHalf={true} />\n <div class=\"text-sm text-on-surface-variant\">\n <strong>Current Value:</strong> {rating2}\n </div>\n </div>\n </section>\n\n <!-- Custom Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Provide <code class=\"rounded bg-surface-container-highest px-1\">iconFull</code> and <code class=\"rounded bg-surface-container-highest px-1\">iconEmpty</code> to change the shapes.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <Rating \n value={4} \n iconFull=\"lucide:heart\" \n iconEmpty=\"lucide:heart\" \n class=\"text-error\" \n />\n <Rating \n value={3} \n iconFull=\"lucide:flame\" \n iconEmpty=\"lucide:flame\" \n class=\"text-warning-500\" \n />\n </div>\n </section>\n</div>\n",
|
|
149
|
+
"tags-input": "<script lang=\"ts\">\n import { TagsInput, Button } from '$lib/index.js'\n \n let tags1 = $state(['svelte', 'tailwind', 'typescript'])\n let tags2 = $state(['apple', 'banana'])\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">TagsInput</h1>\n <p class=\"text-on-surface-variant\">\n An input component for entering and managing multiple string values as tags (pills).\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">bind:value</code> with an array of strings. Press Enter or comma (,) to add a tag.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <TagsInput bind:value={tags1} placeholder=\"Add new skill...\" />\n <div class=\"text-sm text-on-surface-variant\">\n <strong>Current Value:</strong> {tags1.join(', ')}\n </div>\n </div>\n </section>\n\n <!-- Max Tags -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Max Tags</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set the <code class=\"rounded bg-surface-container-highest px-1\">maxTags</code> prop to limit how many tags can be added.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"w-full max-w-sm\">\n <TagsInput bind:value={tags2} maxTags={3} placeholder=\"Max 3 tags allowed\" />\n </div>\n </div>\n </section>\n</div>\n",
|
|
120
150
|
"form-field": "<script lang=\"ts\">\n import { FormField, Input, Textarea, Select, Checkbox, Switch, Separator } from '$lib/index.js'\n\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n\n const countryOptions = [\n { label: 'Vietnam', value: 'vn' },\n { label: 'United States', value: 'us' },\n { label: 'Japan', value: 'jp' },\n { label: 'South Korea', value: 'kr' }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">FormField</h1>\n\n <!-- Basic Usage -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Wrap a form control with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FormField</code\n > to add a label.\n </p>\n <div class=\"max-w-sm\">\n <FormField label=\"Email\">\n <Input type=\"email\" placeholder=\"Enter your email\" />\n </FormField>\n </div>\n </section>\n\n <!-- Required -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Required</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >required</code\n > to add an asterisk indicator.\n </p>\n <div class=\"max-w-sm\">\n <FormField label=\"Email\" required>\n <Input type=\"email\" placeholder=\"Enter your email\" />\n </FormField>\n </div>\n </section>\n\n <!-- Description -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Description</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >description</code\n > to add text below the label.\n </p>\n <div class=\"max-w-sm\">\n <FormField label=\"Email\" description=\"We'll use this to send you notifications.\">\n <Input type=\"email\" placeholder=\"Enter your email\" />\n </FormField>\n </div>\n </section>\n\n <!-- Hint -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Hint</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">hint</code> to\n add text next to the label.\n </p>\n <div class=\"max-w-sm\">\n <FormField label=\"Email\" hint=\"Optional\">\n <Input type=\"email\" placeholder=\"Enter your email\" />\n </FormField>\n </div>\n </section>\n\n <!-- Help -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Help</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">help</code> to\n add help text below the form control.\n </p>\n <div class=\"max-w-sm\">\n <FormField label=\"Password\" help=\"Must be at least 8 characters.\">\n <Input type=\"password\" placeholder=\"Enter your password\" />\n </FormField>\n </div>\n </section>\n\n <!-- Error -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Error</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">error</code\n > to display an error message. Replaces help text when present.\n </p>\n <div class=\"max-w-sm space-y-4\">\n <FormField label=\"Email\" error=\"Please enter a valid email address.\">\n <Input type=\"email\" value=\"invalid-email\" color=\"error\" />\n </FormField>\n\n <FormField\n label=\"Email\"\n help=\"We'll never share your email.\"\n error=\"This email is already taken.\"\n >\n <Input type=\"email\" value=\"taken@example.com\" color=\"error\" />\n </FormField>\n </div>\n </section>\n\n <!-- Size -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Size</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code> to\n control the size of labels and text.\n </p>\n <div class=\"space-y-4\">\n {#each sizes as size (size)}\n <div class=\"max-w-sm\">\n <FormField\n label=\"Email ({size})\"\n description=\"Enter your email address.\"\n hint=\"Required\"\n {size}\n required\n >\n <Input type=\"email\" placeholder=\"Enter your email\" {size} />\n </FormField>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Orientation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >orientation</code\n > to change the layout direction.\n </p>\n <div class=\"space-y-6\">\n <div class=\"max-w-sm\">\n <p class=\"mb-2 text-xs text-on-surface-variant\">vertical (default)</p>\n <FormField label=\"Email\" description=\"Your primary email.\" orientation=\"vertical\">\n <Input type=\"email\" placeholder=\"Enter your email\" />\n </FormField>\n </div>\n\n <div class=\"max-w-lg\">\n <p class=\"mb-2 text-xs text-on-surface-variant\">horizontal</p>\n <FormField label=\"Email\" description=\"Your primary email.\" orientation=\"horizontal\">\n <Input type=\"email\" placeholder=\"Enter your email\" />\n </FormField>\n </div>\n </div>\n </section>\n\n <!-- With Different Controls -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">With Different Controls</h2>\n <p class=\"text-sm text-on-surface-variant\">\n FormField works with any form control component.\n </p>\n <div class=\"max-w-sm space-y-4\">\n <FormField label=\"Bio\" help=\"Max 280 characters.\">\n <Textarea placeholder=\"Tell us about yourself...\" />\n </FormField>\n\n <FormField label=\"Country\" hint=\"Required\" required>\n <Select items={countryOptions} placeholder=\"Select a country\" />\n </FormField>\n\n <FormField label=\"Notifications\" description=\"Receive email notifications.\">\n <Switch />\n </FormField>\n\n <FormField label=\"Terms\" required>\n <Checkbox label=\"I agree to the terms and conditions\" />\n </FormField>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Real World Examples</h2>\n\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Registration form</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <FormField label=\"Full name\" required>\n <Input placeholder=\"John Doe\" />\n </FormField>\n\n <FormField label=\"Email\" required hint=\"Required\">\n <Input type=\"email\" placeholder=\"john@example.com\" />\n </FormField>\n\n <FormField\n label=\"Password\"\n required\n help=\"Must be at least 8 characters with one uppercase letter.\"\n >\n <Input type=\"password\" placeholder=\"Create a password\" />\n </FormField>\n\n <FormField\n label=\"Bio\"\n hint=\"Optional\"\n help=\"Brief description for your profile.\"\n >\n <Textarea placeholder=\"Tell us about yourself...\" />\n </FormField>\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Validation states</p>\n <div\n class=\"max-w-sm space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <FormField label=\"Username\" error=\"Username is already taken.\">\n <Input value=\"admin\" color=\"error\" />\n </FormField>\n\n <FormField label=\"Email\" help=\"Looks good!\">\n <Input type=\"email\" value=\"valid@example.com\" color=\"success\" />\n </FormField>\n\n <FormField label=\"Password\" error=\"Password must be at least 8 characters.\">\n <Input type=\"password\" value=\"123\" color=\"error\" />\n </FormField>\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Horizontal layout form</p>\n <div\n class=\"max-w-lg space-y-4 rounded-lg border border-outline-variant bg-surface-container-low p-4\"\n >\n <FormField label=\"Name\" orientation=\"horizontal\" required>\n <Input placeholder=\"Your name\" />\n </FormField>\n\n <FormField label=\"Email\" orientation=\"horizontal\" required>\n <Input type=\"email\" placeholder=\"your@email.com\" />\n </FormField>\n\n <FormField label=\"Message\" orientation=\"horizontal\">\n <Textarea placeholder=\"Your message...\" />\n </FormField>\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
121
151
|
"form": "<script lang=\"ts\">\n import {\n Form,\n FormField,\n Input,\n Textarea,\n Select,\n Checkbox,\n Switch,\n RadioGroup,\n Slider,\n PinInput,\n Button,\n type FormApi,\n type FormError,\n type FormErrorEvent\n } from '$lib/index.js'\n import { z } from 'zod'\n import * as v from 'valibot'\n import * as yup from 'yup'\n import Joi from 'joi'\n import type { FormSchema } from '$lib/index.js'\n\n // ============================================================\n // Example 1 — Basic login form with Zod\n // ============================================================\n const basicSchema = z.object({\n email: z.string().min(1, 'Email is required').email('Invalid email format'),\n password: z.string().min(8, 'Password must be at least 8 characters')\n })\n\n type BasicInput = z.input<typeof basicSchema>\n let basicState = $state<BasicInput>({ email: '', password: '' })\n let basicForm = $state<FormApi<unknown>>()\n let basicSubmitted = $state<string | null>(null)\n\n function handleBasicSubmit(event: { data: unknown }) {\n basicSubmitted = JSON.stringify(event.data, null, 2)\n }\n\n // ============================================================\n // Example 2 — Validation with real libraries\n // Same signup form validated by Zod / Valibot / Yup / Joi interchangeably.\n // Zod, Valibot, and Yup implement the Standard Schema spec; Joi has a\n // dedicated adapter. Svelora's Form accepts all four identically via the\n // `schema` prop — zero adapter code on the user's side.\n // ============================================================\n type SignupInput = {\n name: string\n email: string\n // `age` is typed as `number | undefined` because <Input type=\"number\">\n // binds to a number via Svelte's native coercion. Undefined represents\n // \"field is empty\" — we can't use 0 as that would look valid.\n age: number | undefined\n country: string\n role: string\n bio: string\n otp: string\n volume: number\n newsletter: boolean\n terms: boolean\n }\n\n // ---- Zod schema ----\n const zodSignupSchema = z.object({\n name: z.string().min(2, 'Name must be at least 2 characters'),\n email: z.string().email('Please enter a valid email'),\n age: z.number({ message: 'Age is required' }).min(18, 'You must be at least 18 years old'),\n country: z.string().min(1, 'Please select a country'),\n role: z.string().min(1, 'Please select a role'),\n bio: z.string().min(10, 'Bio must be at least 10 characters'),\n otp: z.string().length(5, 'Please enter the 5-digit code'),\n volume: z.number(),\n newsletter: z.boolean(),\n terms: z.literal(true, { message: 'You must accept the terms' })\n })\n\n // ---- Valibot schema ----\n const valibotSignupSchema = v.object({\n name: v.pipe(v.string(), v.minLength(2, 'Name must be at least 2 characters')),\n email: v.pipe(v.string(), v.email('Please enter a valid email')),\n age: v.pipe(\n v.number('Age is required'),\n v.minValue(18, 'You must be at least 18 years old')\n ),\n country: v.pipe(v.string(), v.minLength(1, 'Please select a country')),\n role: v.pipe(v.string(), v.minLength(1, 'Please select a role')),\n bio: v.pipe(v.string(), v.minLength(10, 'Bio must be at least 10 characters')),\n otp: v.pipe(v.string(), v.length(5, 'Please enter the 5-digit code')),\n volume: v.number(),\n newsletter: v.boolean(),\n terms: v.pipe(v.boolean(), v.literal(true, 'You must accept the terms'))\n })\n\n // ---- Yup schema ----\n const yupSignupSchema = yup.object({\n name: yup.string().required().min(2, 'Name must be at least 2 characters'),\n email: yup.string().required().email('Please enter a valid email'),\n age: yup\n .number()\n .typeError('Age is required')\n .required('Age is required')\n .min(18, 'You must be at least 18 years old'),\n country: yup.string().required('Please select a country'),\n role: yup.string().required('Please select a role'),\n bio: yup.string().required().min(10, 'Bio must be at least 10 characters'),\n otp: yup.string().required().length(5, 'Please enter the 5-digit code'),\n volume: yup.number().required(),\n newsletter: yup.boolean().required(),\n terms: yup.boolean().oneOf([true], 'You must accept the terms').required()\n })\n\n // ---- Joi schema ----\n const joiSignupSchema = Joi.object({\n name: Joi.string().min(2).required().messages({\n 'string.min': 'Name must be at least 2 characters',\n 'string.empty': 'Name must be at least 2 characters',\n 'any.required': 'Name must be at least 2 characters'\n }),\n email: Joi.string()\n .email({ tlds: { allow: false } })\n .required()\n .messages({\n 'string.email': 'Please enter a valid email',\n 'string.empty': 'Please enter a valid email',\n 'any.required': 'Please enter a valid email'\n }),\n age: Joi.number().min(18).required().messages({\n 'number.base': 'Age is required',\n 'number.min': 'You must be at least 18 years old',\n 'any.required': 'Age is required'\n }),\n country: Joi.string().required().messages({\n 'string.empty': 'Please select a country',\n 'any.required': 'Please select a country'\n }),\n role: Joi.string().required().messages({\n 'string.empty': 'Please select a role',\n 'any.required': 'Please select a role'\n }),\n bio: Joi.string().min(10).required().messages({\n 'string.min': 'Bio must be at least 10 characters',\n 'string.empty': 'Bio must be at least 10 characters',\n 'any.required': 'Bio must be at least 10 characters'\n }),\n otp: Joi.string().length(5).required().messages({\n 'string.length': 'Please enter the 5-digit code',\n 'string.empty': 'Please enter the 5-digit code',\n 'any.required': 'Please enter the 5-digit code'\n }),\n volume: Joi.number().required(),\n newsletter: Joi.boolean().required(),\n terms: Joi.boolean().valid(true).required().messages({\n 'any.only': 'You must accept the terms',\n 'any.required': 'You must accept the terms'\n })\n })\n\n type SchemaLib = 'zod' | 'valibot' | 'yup' | 'joi'\n let activeSchemaLib = $state<SchemaLib>('zod')\n\n const activeSchema = $derived<FormSchema>(\n activeSchemaLib === 'zod'\n ? zodSignupSchema\n : activeSchemaLib === 'valibot'\n ? valibotSignupSchema\n : activeSchemaLib === 'yup'\n ? yupSignupSchema\n : joiSignupSchema\n )\n\n let signupState = $state<SignupInput>({\n name: '',\n email: '',\n age: undefined,\n country: '',\n role: '',\n bio: '',\n otp: '',\n volume: 50,\n newsletter: true,\n terms: false\n })\n let signupForm = $state<FormApi<unknown>>()\n let signupSubmitted = $state<string | null>(null)\n\n const countryItems = [\n { label: 'Vietnam', value: 'vn' },\n { label: 'United States', value: 'us' },\n { label: 'Japan', value: 'jp' },\n { label: 'South Korea', value: 'kr' }\n ]\n\n const roleItems = [\n { label: 'Developer', value: 'dev', description: 'Write code' },\n { label: 'Designer', value: 'design', description: 'Create experiences' },\n { label: 'Manager', value: 'pm', description: 'Coordinate teams' }\n ]\n\n function handleSignupSubmit(event: { data: unknown }) {\n signupSubmitted = JSON.stringify(event.data, null, 2)\n }\n\n let signupErrorCount = $state(0)\n function handleSignupError(event: FormErrorEvent) {\n signupErrorCount = event.errors.length\n }\n\n // ============================================================\n // Example 3 — Async submit with loading state\n // ============================================================\n let asyncState = $state({ username: '' })\n let asyncForm = $state<FormApi<unknown>>()\n let asyncResult = $state<string | null>(null)\n\n async function handleAsyncSubmit(event: { data: unknown }) {\n asyncResult = null\n // Simulate network request\n await new Promise((resolve) => setTimeout(resolve, 1500))\n asyncResult = `Created user: ${(event.data as { username: string }).username}`\n }\n\n function validateAsync(raw: unknown): FormError[] {\n const state = raw as typeof asyncState\n if (!state.username) {\n return [{ name: 'username', message: 'Username is required' }]\n }\n if (state.username.length < 3) {\n return [{ name: 'username', message: 'Username must be at least 3 characters' }]\n }\n return []\n }\n\n // ============================================================\n // Example 4 — Programmatic API (bind:api)\n // ============================================================\n let apiState = $state({ note: '' })\n let apiForm = $state<FormApi<unknown>>()\n\n function validateApi(raw: unknown): FormError[] {\n const state = raw as typeof apiState\n if (!state.note) return [{ name: 'note', message: 'Note is required' }]\n return []\n }\n\n // ============================================================\n // Example 5 — Async field validation (username availability)\n // Validates against a \"server\" via a debounced async validator.\n // ============================================================\n let availabilityState = $state({ username: '' })\n let availabilityForm = $state<FormApi<unknown>>()\n let availabilityResult = $state<string | null>(null)\n\n // Simulate a server call with a 800ms delay.\n const takenUsernames = new Set(['admin', 'root', 'test', 'user', 'svelte'])\n async function checkUsernameAvailable(username: string): Promise<boolean> {\n await new Promise((r) => setTimeout(r, 800))\n return !takenUsernames.has(username.toLowerCase())\n }\n\n async function validateAvailability(raw: unknown): Promise<FormError[]> {\n const state = raw as typeof availabilityState\n const errors: FormError[] = []\n if (!state.username || state.username.length < 3) {\n errors.push({ name: 'username', message: 'Username must be at least 3 characters' })\n return errors\n }\n const available = await checkUsernameAvailable(state.username)\n if (!available) {\n errors.push({\n name: 'username',\n message: `\"${state.username}\" is already taken`\n })\n }\n return errors\n }\n\n async function handleAvailabilitySubmit(event: { data: unknown }) {\n availabilityResult = `✓ Registered: ${(event.data as { username: string }).username}`\n }\n\n // ============================================================\n // Example 6 — Array / repeater fields (nested Form per row)\n // Each row is a <Form nested name=\"guests.{index}\"> with its own\n // item schema. FormField inside uses relative names (\"name\", \"email\")\n // because validation scope is local to the row. The parent only\n // enforces array-level rules (min length) via custom validate.\n // ============================================================\n const guestItemSchema = z.object({\n name: z.string().min(2, 'Name must be at least 2 characters'),\n email: z.string().email('Invalid email')\n })\n\n type GuestItem = z.input<typeof guestItemSchema>\n let guestsState = $state<{ guests: GuestItem[] }>({\n guests: [{ name: '', email: '' }]\n })\n let guestsForm = $state<FormApi<unknown>>()\n let guestsSubmitted = $state<string | null>(null)\n\n function validateGuestsLength(raw: unknown): FormError[] {\n const state = raw as typeof guestsState\n return state.guests.length === 0 ? [{ message: 'Add at least one guest' }] : []\n }\n\n function addGuest() {\n guestsState.guests = [...guestsState.guests, { name: '', email: '' }]\n }\n function removeGuest(index: number) {\n guestsState.guests = guestsState.guests.filter((_, i) => i !== index)\n }\n function handleGuestsSubmit(event: { data: unknown }) {\n guestsSubmitted = JSON.stringify(event.data, null, 2)\n }\n\n // ============================================================\n // Example 7 — Dependent fields (confirm password)\n // All four supported libraries natively handle cross-field validation:\n // Zod: z.object({...}).refine((data) => ..., { path: ['confirm'] })\n // Valibot: v.pipe(v.object({...}), v.forward(v.check(...), ['confirm']))\n // Yup: yup.string().oneOf([yup.ref('password')], 'Passwords must match')\n // Joi: Joi.any().valid(Joi.ref('password')).messages({...})\n // Demo uses Zod since it's the most idiomatic for object-level refinements.\n // ============================================================\n const passwordSchema = z\n .object({\n password: z.string().min(8, 'Password must be at least 8 characters'),\n confirm: z.string().min(1, 'Please confirm your password')\n })\n .refine((data) => data.password === data.confirm, {\n message: 'Passwords do not match',\n path: ['confirm'] // attach error to the `confirm` field\n })\n\n type PasswordInput = z.input<typeof passwordSchema>\n let passwordState = $state<PasswordInput>({ password: '', confirm: '' })\n let passwordForm = $state<FormApi<unknown>>()\n let passwordDone = $state(false)\n\n function handlePasswordSubmit() {\n passwordDone = true\n }\n\n // ============================================================\n // Example 8 — Nested forms\n // A parent form embeds a child form via <Form nested name=\"address\">.\n // The child attaches on mount; parent.submit() cascades validation\n // and merges the child's state into the parent payload.\n // ============================================================\n const nestedSchema = z.object({\n fullName: z.string().min(2, 'Name must be at least 2 characters')\n })\n const addressSchema = z.object({\n street: z.string().min(3, 'Street must be at least 3 characters'),\n city: z.string().min(2, 'City must be at least 2 characters')\n })\n\n let nestedState = $state({\n fullName: '',\n address: { street: '', city: '' }\n })\n let nestedForm = $state<FormApi<unknown>>()\n let nestedSubmitted = $state<string | null>(null)\n\n function handleNestedSubmit(event: { data: unknown }) {\n nestedSubmitted = JSON.stringify(event.data, null, 2)\n }\n\n // ============================================================\n // Example 9 — Error summary + reset\n // Demonstrates reading `form.errors` reactively to render a\n // summary box at the top, plus the `reset()` method.\n // ============================================================\n const summarySchema = z.object({\n title: z.string().min(1, 'Title is required'),\n description: z.string().min(10, 'Description must be at least 10 characters'),\n category: z.string().min(1, 'Please select a category')\n })\n type SummaryInput = z.input<typeof summarySchema>\n const initialSummaryState: SummaryInput = { title: '', description: '', category: '' }\n let summaryState = $state<SummaryInput>({ ...initialSummaryState })\n let summaryForm = $state<FormApi<unknown>>()\n\n function resetSummary() {\n summaryForm?.reset()\n summaryState = { ...initialSummaryState }\n }\n\n const summaryCategories = [\n { label: 'Bug', value: 'bug' },\n { label: 'Feature', value: 'feature' },\n { label: 'Question', value: 'question' }\n ]\n</script>\n\n<div class=\"space-y-12\">\n <div>\n <h1 class=\"text-2xl font-bold text-on-surface\">Form</h1>\n <p class=\"mt-2 text-sm text-on-surface-variant\">\n Centralized form validation and submission. Supports Zod, Valibot, Yup, Joi, and custom\n validators. Errors propagate to\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">FormField</code\n >\n automatically by matching\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">name</code>.\n </p>\n </div>\n\n <!-- ============================================================ -->\n <!-- Example 1 — Basic login form with Zod -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic — Login form with Zod</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Simplest usage: pass a Zod schema to the <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">schema</code\n >\n prop. Errors propagate to each FormField by matching\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">name</code\n >. Submit is blocked until validation passes.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={basicForm}\n bind:state={basicState}\n schema={basicSchema}\n onsubmit={handleBasicSubmit}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"email\" label=\"Email\" required>\n <Input\n type=\"email\"\n bind:value={basicState.email}\n placeholder=\"you@example.com\"\n />\n </FormField>\n\n <FormField\n name=\"password\"\n label=\"Password\"\n required\n help=\"Must be at least 8 characters\"\n >\n <Input type=\"password\" bind:value={basicState.password} />\n </FormField>\n\n <div class=\"flex items-center gap-3\">\n <Button type=\"submit\" loading={basicForm?.loading}>Sign in</Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n color=\"secondary\"\n onclick={() => basicForm?.clear()}\n >\n Clear errors\n </Button>\n </div>\n </Form>\n\n {#if basicSubmitted}\n <div\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n <p class=\"font-medium\">Submitted:</p>\n <pre class=\"mt-1 text-xs\">{basicSubmitted}</pre>\n </div>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 2 — Zod / Valibot / Yup / Joi -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">\n Schema validation — Zod / Valibot / Yup / Joi\n </h2>\n <p class=\"text-sm text-on-surface-variant\">\n The same signup form validated by four different libraries. Zod, Valibot, and Yup go\n through the <a\n href=\"https://standardschema.dev\"\n target=\"_blank\"\n rel=\"noopener\"\n class=\"text-primary hover:underline\">Standard Schema</a\n >\n path; Joi uses a dedicated adapter. In all four cases\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n ><Form schema={...}></code\n > accepts the schema directly — no wrapping or resolvers on your side. Switch libraries\n with the tabs below.\n </p>\n </div>\n\n <!-- Schema lib tabs -->\n <div class=\"flex gap-2\">\n {#each ['zod', 'valibot', 'yup', 'joi'] as const as lib (lib)}\n <button\n type=\"button\"\n class=\"rounded-md border px-4 py-1.5 text-sm font-medium transition-colors {activeSchemaLib ===\n lib\n ? 'border-primary bg-primary-container text-on-primary-container'\n : 'border-outline-variant bg-surface-container text-on-surface-variant hover:bg-surface-container-highest'}\"\n onclick={() => {\n activeSchemaLib = lib\n signupForm?.clear()\n signupSubmitted = null\n signupErrorCount = 0\n }}\n >\n {lib === 'zod'\n ? 'Zod'\n : lib === 'valibot'\n ? 'Valibot'\n : lib === 'yup'\n ? 'Yup'\n : 'Joi'}\n </button>\n {/each}\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={signupForm}\n bind:state={signupState}\n schema={activeSchema}\n onsubmit={handleSignupSubmit}\n onerror={handleSignupError}\n class=\"max-w-2xl space-y-4\"\n >\n <div class=\"grid gap-4 sm:grid-cols-2\">\n <FormField name=\"name\" label=\"Name\" required>\n <Input bind:value={signupState.name} placeholder=\"Jane Doe\" />\n </FormField>\n\n <FormField name=\"email\" label=\"Email\" required>\n <Input\n type=\"email\"\n bind:value={signupState.email}\n placeholder=\"jane@example.com\"\n />\n </FormField>\n\n <FormField name=\"age\" label=\"Age\" required help=\"Must be 18 or older\">\n <Input type=\"number\" bind:value={signupState.age} placeholder=\"18\" />\n </FormField>\n\n <FormField name=\"country\" label=\"Country\" required>\n <Select\n bind:value={signupState.country}\n items={countryItems}\n placeholder=\"Select a country\"\n />\n </FormField>\n </div>\n\n <FormField name=\"role\" label=\"Role\" required>\n <RadioGroup bind:value={signupState.role} items={roleItems} />\n </FormField>\n\n <FormField name=\"bio\" label=\"Bio\" required help=\"At least 10 characters\">\n <Textarea\n bind:value={signupState.bio}\n rows={3}\n placeholder=\"Tell us about yourself...\"\n />\n </FormField>\n\n <FormField name=\"otp\" label=\"Verification code\" required help=\"5-digit code\">\n <PinInput bind:value={signupState.otp} length={5} />\n </FormField>\n\n <FormField name=\"volume\" label=\"Notification volume\">\n <Slider bind:value={signupState.volume} min={0} max={100} tooltip />\n </FormField>\n\n <FormField name=\"newsletter\">\n <Switch bind:checked={signupState.newsletter} label=\"Subscribe to newsletter\" />\n </FormField>\n\n <FormField name=\"terms\" required>\n <Checkbox\n bind:checked={signupState.terms}\n label=\"I accept the terms and conditions\"\n />\n </FormField>\n\n <div class=\"flex items-center gap-3 pt-2\">\n <Button type=\"submit\" loading={signupForm?.loading}>Create account</Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n color=\"secondary\"\n onclick={() => signupForm?.clear()}\n >\n Clear errors\n </Button>\n\n {#if signupForm?.dirty}\n <span class=\"text-xs text-on-surface-variant\">\n {signupForm.dirtyFields.size} field(s) modified\n </span>\n {/if}\n </div>\n </Form>\n\n {#if signupSubmitted}\n <div\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n <p class=\"font-medium\">Submitted:</p>\n <pre class=\"mt-1 overflow-x-auto text-xs\">{signupSubmitted}</pre>\n </div>\n {:else if signupErrorCount > 0}\n <p class=\"mt-4 text-sm text-error\">\n {signupErrorCount} validation error(s) — fix the fields above.\n </p>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 3 — Async submit with loading -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Async submit — loading state</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >onsubmit</code\n >\n is async,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >loadingAuto</code\n >\n (default\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">true</code\n >) flips\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >form.loading</code\n > for the duration. Use it to disable buttons or show spinners.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={asyncForm}\n bind:state={asyncState}\n validate={validateAsync}\n onsubmit={handleAsyncSubmit}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"username\" label=\"Username\" required>\n <Input bind:value={asyncState.username} placeholder=\"johndoe\" />\n </FormField>\n\n <Button type=\"submit\" loading={asyncForm?.loading}>\n {asyncForm?.loading ? 'Creating...' : 'Create user'}\n </Button>\n </Form>\n\n {#if asyncResult}\n <p\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n {asyncResult}\n </p>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 4 — Programmatic API -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Programmatic API — bind:api</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Bind the <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >api</code\n >\n prop to control the form from outside: trigger\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >submit()</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >validate()</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >setErrors()</code\n >, read\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >errors</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">dirty</code\n >, etc.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={apiForm}\n bind:state={apiState}\n validate={validateApi}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"note\" label=\"Note\">\n <Textarea bind:value={apiState.note} rows={2} />\n </FormField>\n </Form>\n\n <div class=\"mt-4 flex flex-wrap items-center gap-2\">\n <Button size=\"sm\" onclick={() => apiForm?.submit()}>submit()</Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => apiForm?.validate({ silent: true })}\n >\n validate(silent)\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() =>\n apiForm?.setErrors([\n { name: 'note', message: 'Custom error from outside' }\n ])}\n >\n setErrors()\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n color=\"secondary\"\n onclick={() => apiForm?.clear()}\n >\n clear()\n </Button>\n </div>\n\n <dl class=\"mt-4 grid grid-cols-2 gap-2 text-sm sm:grid-cols-4\">\n <div>\n <dt class=\"text-on-surface-variant\">dirty</dt>\n <dd class=\"font-mono text-on-surface\">{String(apiForm?.dirty ?? false)}</dd>\n </div>\n <div>\n <dt class=\"text-on-surface-variant\">loading</dt>\n <dd class=\"font-mono text-on-surface\">{String(apiForm?.loading ?? false)}</dd>\n </div>\n <div>\n <dt class=\"text-on-surface-variant\">errors</dt>\n <dd class=\"font-mono text-on-surface\">{apiForm?.errors.length ?? 0}</dd>\n </div>\n <div>\n <dt class=\"text-on-surface-variant\">touched</dt>\n <dd class=\"font-mono text-on-surface\">\n {apiForm?.touchedFields.size ?? 0}\n </dd>\n </div>\n </dl>\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 5 — Async field validation -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Async validation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >validate</code\n >\n prop accepts an async function. Submit blocks until the promise resolves; try usernames\n like\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">admin</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">root</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">svelte</code>\n to see the \"already taken\" error.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={availabilityForm}\n bind:state={availabilityState}\n validate={validateAvailability}\n onsubmit={handleAvailabilitySubmit}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"username\" label=\"Username\" required help=\"Try 'admin' or 'svelte'\">\n <Input bind:value={availabilityState.username} placeholder=\"your-handle\" />\n </FormField>\n\n <Button type=\"submit\" loading={availabilityForm?.loading}>\n {availabilityForm?.loading ? 'Checking…' : 'Check availability'}\n </Button>\n </Form>\n\n {#if availabilityResult}\n <p\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n {availabilityResult}\n </p>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 6 — Array / repeater fields (nested Form per row) -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Array fields (repeater)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Each row is a <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n ><Form nested name=\"guests.{index}\"></code\n >\n with its own item schema. FormField inside uses relative names (<code\n class=\"rounded bg-surface-container-highest px-1 text-xs\">name</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">email</code>) — no\n dotted paths, no regex. Validation scope is local to the row, so blurring row 3 only\n validates row 3. The parent enforces array-level rules (min length) via a small\n custom validator.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={guestsForm}\n bind:state={guestsState}\n validate={validateGuestsLength}\n onsubmit={handleGuestsSubmit}\n class=\"space-y-4\"\n >\n {#each guestsState.guests as guest, index (index)}\n <div\n class=\"grid gap-3 rounded-md border border-outline-variant bg-surface p-4 sm:grid-cols-[1fr_1fr_auto]\"\n >\n <Form\n nested\n name=\"guests.{index}\"\n schema={guestItemSchema}\n class=\"contents\"\n >\n <FormField name=\"name\" label=\"Name\">\n <Input bind:value={guest.name} placeholder=\"Jane Doe\" />\n </FormField>\n\n <FormField name=\"email\" label=\"Email\">\n <Input\n type=\"email\"\n bind:value={guest.email}\n placeholder=\"jane@example.com\"\n />\n </FormField>\n\n <div class=\"flex items-end\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n color=\"error\"\n icon=\"lucide:trash-2\"\n square\n disabled={guestsState.guests.length === 1}\n onclick={() => removeGuest(index)}\n />\n </div>\n </Form>\n </div>\n {/each}\n\n <div class=\"flex items-center gap-3\">\n <Button type=\"button\" variant=\"outline\" icon=\"lucide:plus\" onclick={addGuest}>\n Add guest\n </Button>\n <Button type=\"submit\">Submit ({guestsState.guests.length})</Button>\n </div>\n </Form>\n\n {#if guestsSubmitted}\n <div\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n <p class=\"font-medium\">Submitted:</p>\n <pre class=\"mt-1 overflow-x-auto text-xs\">{guestsSubmitted}</pre>\n </div>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 7 — Dependent fields (confirm password) -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Dependent fields</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Zod's <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n >.refine()</code\n >\n compares two fields against each other and attaches the error to the\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">confirm</code> field\n via\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n >path: ['confirm']</code\n >. Valibot uses\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">v.forward()</code>,\n Yup uses\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">yup.ref()</code>,\n Joi uses\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">Joi.ref()</code> — all\n four libs support this natively, no custom validator needed.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={passwordForm}\n bind:state={passwordState}\n schema={passwordSchema}\n onsubmit={handlePasswordSubmit}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"password\" label=\"Password\" required>\n <Input type=\"password\" bind:value={passwordState.password} />\n </FormField>\n\n <FormField name=\"confirm\" label=\"Confirm password\" required>\n <Input type=\"password\" bind:value={passwordState.confirm} />\n </FormField>\n\n <Button type=\"submit\">Set password</Button>\n </Form>\n\n {#if passwordDone}\n <p\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n ✓ Password set successfully\n </p>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 8 — Nested forms -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Nested forms</h2>\n <p class=\"text-sm text-on-surface-variant\">\n A child <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n ><Form nested name=\"address\"></code\n >\n attaches to its parent on mount. When the parent submits, it cascades validation to every\n attached child. Child errors are prefixed with the child's\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">name</code> (e.g.\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">address.street</code\n >) and merged into the parent payload. Each form uses its own schema.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={nestedForm}\n bind:state={nestedState}\n schema={nestedSchema}\n onsubmit={handleNestedSubmit}\n class=\"max-w-xl space-y-4\"\n >\n <FormField name=\"fullName\" label=\"Full name\" required>\n <Input bind:value={nestedState.fullName} placeholder=\"Jane Doe\" />\n </FormField>\n\n <fieldset class=\"space-y-3 rounded-md border border-outline-variant bg-surface p-4\">\n <legend class=\"px-2 text-sm font-medium text-on-surface\">Address</legend>\n <Form nested name=\"address\" schema={addressSchema}>\n <FormField name=\"street\" label=\"Street\" required>\n <Input\n bind:value={nestedState.address.street}\n placeholder=\"123 Main St\"\n />\n </FormField>\n <FormField name=\"city\" label=\"City\" required>\n <Input bind:value={nestedState.address.city} placeholder=\"Hanoi\" />\n </FormField>\n </Form>\n </fieldset>\n\n <Button type=\"submit\">Submit</Button>\n </Form>\n\n {#if nestedSubmitted}\n <div\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n <p class=\"font-medium\">Submitted (parent + child merged):</p>\n <pre class=\"mt-1 overflow-x-auto text-xs\">{nestedSubmitted}</pre>\n </div>\n {/if}\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Example 9 — Error summary + reset -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <div>\n <h2 class=\"text-lg font-semibold text-on-surface\">Error summary + reset</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Read <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n >form.errors</code\n >\n reactively to render a summary of all validation errors at the top of the form. The\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">reset()</code>\n method clears errors, dirty/touched/blurred sets, and\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">submitCount</code>\n — you restore\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">state</code> yourself.\n </p>\n </div>\n\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={summaryForm}\n bind:state={summaryState}\n schema={summarySchema}\n class=\"max-w-xl space-y-4\"\n >\n {#if summaryForm && summaryForm.errors.length > 0}\n <div\n class=\"rounded-md border border-error/30 bg-error-container p-3 text-sm text-on-error-container\"\n >\n <p class=\"font-medium\">\n Please fix {summaryForm.errors.length} error{summaryForm.errors.length >\n 1\n ? 's'\n : ''}:\n </p>\n <ul class=\"mt-1 list-inside list-disc\">\n {#each summaryForm.errors as err (err.name)}\n <li>\n <strong>{err.name}:</strong>\n {err.message}\n </li>\n {/each}\n </ul>\n </div>\n {/if}\n\n <FormField name=\"title\" label=\"Title\" required>\n <Input bind:value={summaryState.title} />\n </FormField>\n\n <FormField name=\"description\" label=\"Description\" required>\n <Textarea bind:value={summaryState.description} rows={3} />\n </FormField>\n\n <FormField name=\"category\" label=\"Category\" required>\n <Select\n bind:value={summaryState.category}\n items={summaryCategories}\n placeholder=\"Choose…\"\n />\n </FormField>\n\n <div class=\"flex flex-wrap items-center gap-3\">\n <Button type=\"submit\">Submit</Button>\n <Button type=\"button\" variant=\"ghost\" color=\"secondary\" onclick={resetSummary}>\n Reset\n </Button>\n <span class=\"text-xs text-on-surface-variant\">\n Submitted {summaryForm?.submitCount ?? 0} time(s)\n </span>\n </div>\n </Form>\n </div>\n </section>\n\n <!-- ============================================================ -->\n <!-- Features list -->\n <!-- ============================================================ -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Features</h2>\n <ul class=\"list-inside list-disc space-y-1 text-sm text-on-surface-variant\">\n <li>\n Supported libraries: <strong>Zod 3.24+</strong>, <strong>Valibot 1.0+</strong>,\n <strong>Yup 1.7+</strong> (via Standard Schema), and <strong>Joi 17+</strong>\n (dedicated adapter). User installs whichever they prefer.\n </li>\n <li>\n Custom <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n >validate</code\n > function, sync or async (can run alongside schema)\n </li>\n <li>\n Full programmatic API: <code\n class=\"rounded bg-surface-container-highest px-1 text-xs\">submit</code\n >, <code class=\"rounded bg-surface-container-highest px-1 text-xs\">validate</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">clear</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">reset</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">setErrors</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">getErrors</code>\n + reactive\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">errors</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">loading</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">dirty</code>,\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">submitCount</code>\n </li>\n <li>\n Field-level validation on <code\n class=\"rounded bg-surface-container-highest px-1 text-xs\">input</code\n >\n / <code class=\"rounded bg-surface-container-highest px-1 text-xs\">blur</code> /\n <code class=\"rounded bg-surface-container-highest px-1 text-xs\">change</code>\n / <code class=\"rounded bg-surface-container-highest px-1 text-xs\">focus</code>\n </li>\n <li>Debounced input validation with eager-after-first-blur behavior</li>\n <li>Dirty / touched / blurred field tracking</li>\n <li>\n Auto loading state during async submit (<code\n class=\"rounded bg-surface-container-highest px-1 text-xs\">loadingAuto</code\n >)\n </li>\n <li>\n Nested forms via <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n >nested</code\n >\n + <code class=\"rounded bg-surface-container-highest px-1 text-xs\">name</code> props\n </li>\n <li>\n Schema transform output (e.g. Zod <code\n class=\"rounded bg-surface-container-highest px-1 text-xs\">.transform()</code\n >)\n </li>\n <li>\n Regex <code class=\"rounded bg-surface-container-highest px-1 text-xs\"\n >errorPattern</code\n > on FormField for array-style fields\n </li>\n <li>\n Full programmatic API via <code\n class=\"rounded bg-surface-container-highest px-1 text-xs\">bind:api</code\n >\n </li>\n </ul>\n </section>\n</div>\n",
|
|
122
152
|
"alert": "<script lang=\"ts\">\n import { Alert, type AlertProps } from '$lib/index.js'\n\n type Color = NonNullable<AlertProps['color']>\n type Variant = NonNullable<AlertProps['variant']>\n\n const colors: Color[] = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ]\n const variants: Variant[] = ['solid', 'outline', 'soft', 'subtle']\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Alert</h1>\n <p class=\"text-on-surface-variant\">\n Callout component to draw user's attention with customizable styling and interactive\n elements.\n </p>\n </div>\n\n <!-- Basic Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic Usage</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n title=\"Information\"\n description=\"This is a basic alert with title and description.\"\n />\n <Alert description=\"Alert with description only.\" />\n <Alert title=\"Alert with title only\" />\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n variant=\"solid\"\n title=\"Solid\"\n description=\"Filled background with contrasting text.\"\n />\n <Alert\n variant=\"outline\"\n title=\"Outline\"\n description=\"Border only with transparent background.\"\n />\n <Alert variant=\"soft\" title=\"Soft\" description=\"Light colored background.\" />\n <Alert variant=\"subtle\" title=\"Subtle\" description=\"Light background with border.\" />\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert color=\"primary\" title=\"Primary\" description=\"Primary color alert.\" />\n <Alert color=\"secondary\" title=\"Secondary\" description=\"Secondary color alert.\" />\n <Alert color=\"tertiary\" title=\"Tertiary\" description=\"Tertiary color alert.\" />\n <Alert\n color=\"success\"\n title=\"Success\"\n description=\"Operation completed successfully.\"\n />\n <Alert color=\"warning\" title=\"Warning\" description=\"Please review before proceeding.\" />\n <Alert color=\"error\" title=\"Error\" description=\"Something went wrong.\" />\n <Alert color=\"info\" title=\"Info\" description=\"Here's some useful information.\" />\n <Alert color=\"surface\" title=\"Surface\" description=\"Neutral surface color alert.\" />\n </div>\n </section>\n\n <!-- With Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icons</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n color=\"info\"\n icon=\"lucide:info\"\n title=\"Information\"\n description=\"This alert includes an icon for visual emphasis.\"\n />\n <Alert\n color=\"success\"\n icon=\"lucide:check-circle\"\n title=\"Success\"\n description=\"Your changes have been saved.\"\n />\n <Alert\n color=\"warning\"\n icon=\"lucide:alert-triangle\"\n title=\"Warning\"\n description=\"Your session will expire in 5 minutes.\"\n />\n <Alert\n color=\"error\"\n icon=\"lucide:x-circle\"\n title=\"Error\"\n description=\"Failed to connect to server.\"\n />\n </div>\n </section>\n\n <!-- With Avatar -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Avatar</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n avatar={{ src: 'https://i.pravatar.cc/150?img=1', alt: 'John' }}\n title=\"John Doe\"\n description=\"Sent you a message: 'Hey, are you available for a quick call?'\"\n />\n <Alert\n color=\"success\"\n avatar={{ alt: 'System' }}\n title=\"System Update\"\n description=\"A new version is available.\"\n />\n </div>\n </section>\n\n <!-- With Close Button -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Close Button</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n icon=\"lucide:bell\"\n title=\"Notification\"\n description=\"You have 3 new messages.\"\n close\n />\n <Alert\n color=\"warning\"\n icon=\"lucide:alert-triangle\"\n title=\"Dismissible Warning\"\n description=\"This alert can be dismissed.\"\n close\n />\n </div>\n </section>\n\n <!-- With Actions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Actions</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n icon=\"lucide:download\"\n title=\"Update Available\"\n description=\"A new version of the app is ready to install.\"\n actions={[{ label: 'Update Now' }, { label: 'Later', variant: 'ghost' }]}\n />\n <Alert\n color=\"error\"\n icon=\"lucide:trash-2\"\n title=\"Delete Item?\"\n description=\"This action cannot be undone.\"\n actions={[\n { label: 'Delete', color: 'error' },\n { label: 'Cancel', variant: 'ghost' }\n ]}\n />\n </div>\n </section>\n\n <!-- Vertical Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Vertical Orientation</h2>\n <div class=\"grid gap-3 rounded-lg bg-surface-container-high p-4 md:grid-cols-2\">\n <Alert\n orientation=\"vertical\"\n icon=\"lucide:cloud-upload\"\n title=\"Upload Complete\"\n description=\"Your files have been uploaded successfully.\"\n actions={[{ label: 'View Files' }]}\n />\n <Alert\n orientation=\"vertical\"\n color=\"warning\"\n icon=\"lucide:shield-alert\"\n title=\"Security Alert\"\n description=\"Unusual login activity detected on your account.\"\n actions={[{ label: 'Review Activity' }, { label: 'Ignore', variant: 'ghost' }]}\n close\n />\n </div>\n </section>\n\n <!-- Variants x Colors Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants x Colors</h2>\n <div class=\"overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-2 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Variant</th\n >\n {#each colors as color (color)}\n <th\n class=\"px-2 py-3 text-center text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each variants as variant (variant)}\n <tr class=\"border-b border-outline-variant/50\">\n <td\n class=\"px-2 py-3 text-sm font-medium text-on-surface-variant capitalize\"\n >{variant}</td\n >\n {#each colors as color (color)}\n <td class=\"px-2 py-3\">\n <Alert {variant} {color} title={color} class=\"min-w-48\" />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Alert\n variant=\"subtle\"\n icon=\"lucide:cookie\"\n title=\"We use cookies\"\n description=\"This website uses cookies to ensure you get the best experience.\"\n actions={[{ label: 'Accept All' }, { label: 'Customize', variant: 'outline' }]}\n />\n <Alert\n color=\"warning\"\n variant=\"soft\"\n icon=\"lucide:clock\"\n title=\"Trial Ending Soon\"\n description=\"Your free trial expires in 3 days. Upgrade now to keep your data.\"\n actions={[{ label: 'Upgrade', color: 'warning' }]}\n close\n />\n <Alert\n color=\"success\"\n variant=\"soft\"\n icon=\"lucide:check-circle\"\n title=\"Payment Successful\"\n description=\"Thank you for your purchase! A confirmation email has been sent.\"\n close\n />\n <Alert\n color=\"error\"\n variant=\"outline\"\n icon=\"lucide:wifi-off\"\n title=\"Connection Lost\"\n description=\"Unable to connect to the server. Please check your internet connection.\"\n actions={[\n { label: 'Retry', leadingIcon: 'lucide:refresh-cw' },\n { label: 'Work Offline', variant: 'ghost' }\n ]}\n />\n </div>\n </section>\n</div>\n",
|
|
123
153
|
"banner": "<script lang=\"ts\">\n import { Banner, Button, Icon, Separator, Badge } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n let dismissibleOpen = $state(true)\n let persistedOpen = $state(true)\n let onCloseFired = $state(0)\n\n function resetPersistedBanner() {\n localStorage.removeItem('svelora-banner-demo-persisted')\n persistedOpen = true\n }\n\n const prehydrationRecipe = [\n '<' + 'script>',\n ' try {',\n \" var v = localStorage.getItem('svelora-banner-your-id')\",\n \" if (v === '1') document.documentElement.dataset.bannerHidden = 'your-id'\",\n ' } catch {}',\n '<' + '/script>',\n '<' + 'style>',\n ' [data-banner-hidden=\"your-id\"] [data-banner-id=\"your-id\"] { display: none }',\n '<' + '/style>'\n ].join('\\n')\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Banner</h1>\n <p class=\"text-on-surface-variant\">\n Full-width announcement bar typically rendered at the top of a page or layout. Supports\n optional <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >localStorage</code\n >\n persistence — once dismissed by a given\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">id</code>, the\n banner stays hidden across reloads.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <Banner title=\"Welcome to Svelora — a Svelte 5 component library.\" />\n </section>\n\n <!-- With icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icon</h2>\n <Banner icon=\"lucide:megaphone\" title=\"New features available — check the changelog!\" />\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">All 8 design-token colors are supported.</p>\n <div class=\"space-y-2\">\n {#each colors as color (color)}\n <Banner {color} icon=\"lucide:info\" title={`Color: ${color}`} />\n {/each}\n </div>\n </section>\n\n <!-- With actions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Actions</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >actions</code\n >\n array of\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >ButtonProps</code\n >. Banner applies\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size=\"xs\"</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >color=\"surface\"</code\n >\n as defaults — override any prop (variant, color, icon, to, etc.) per item.\n </p>\n <div class=\"space-y-3\">\n <Banner\n color=\"primary\"\n icon=\"lucide:sparkles\"\n title=\"Update available — new features in v1.8.0!\"\n actions={[\n { label: 'Learn more', variant: 'outline' },\n { label: 'Update now', trailingIcon: 'lucide:arrow-right' }\n ]}\n />\n <Banner\n color=\"warning\"\n icon=\"lucide:cookie\"\n title=\"We use cookies to improve your experience.\"\n actions={[\n { label: 'Accept', leadingIcon: 'lucide:check', variant: 'outline' },\n { label: 'Reject', variant: 'outline' }\n ]}\n />\n </div>\n </section>\n\n <!-- Dismissible (session-only) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Dismissible (session-only)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Without an\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">id</code>, the\n banner is hidden only for this page session. Reload to bring it back.\n </p>\n <Banner\n bind:open={dismissibleOpen}\n color=\"info\"\n icon=\"lucide:bell\"\n title=\"Tap the × to dismiss for this session\"\n close\n onClose={() => onCloseFired++}\n />\n <div class=\"flex flex-wrap items-center gap-2\">\n <Button\n size=\"xs\"\n variant=\"outline\"\n label=\"Show again\"\n disabled={dismissibleOpen}\n onclick={() => (dismissibleOpen = true)}\n />\n <span class=\"text-xs text-on-surface-variant\">\n onClose fired: <Badge size=\"xs\" variant=\"soft\" label={String(onCloseFired)} />\n </span>\n </div>\n </section>\n\n <!-- Persistent (localStorage) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Persistent (localStorage)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set an\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">id</code> to\n persist the dismissal across reloads. Storage key:\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >svelora-banner-demo-persisted</code\n >.\n </p>\n <Banner\n bind:open={persistedOpen}\n id=\"demo-persisted\"\n color=\"success\"\n icon=\"lucide:save\"\n title=\"Dismiss me, reload the page — I won't come back until you reset.\"\n close\n />\n <div class=\"flex flex-wrap items-center gap-2\">\n <Button\n size=\"xs\"\n variant=\"outline\"\n leadingIcon=\"lucide:rotate-ccw\"\n label=\"Reset dismissal\"\n onclick={resetPersistedBanner}\n />\n </div>\n </section>\n\n <!-- Clickable -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Clickable</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Provide a <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >to</code\n > prop to make the entire banner a link. Hover styles are applied automatically. The close\n button (if present) stops propagation so it dismisses without navigating.\n </p>\n <Banner\n color=\"primary\"\n icon=\"lucide:rocket\"\n title=\"Read the v1.7.0 release notes →\"\n to=\"https://github.com/asphum/svelora/blob/main/CHANGELOG.md\"\n target=\"_blank\"\n close\n />\n </section>\n\n <Separator />\n\n <!-- Custom snippets -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Snippets</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >leading</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">titleSlot</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >actionsSlot</code\n >, or\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">closeSlot</code\n >\n for full control.\n </p>\n\n <div class=\"space-y-3\">\n <Banner color=\"info\">\n {#snippet leading()}\n <span\n class=\"flex size-6 items-center justify-center rounded-full bg-info-container\"\n >\n <Icon name=\"lucide:sparkles\" class=\"size-4 text-info\" />\n </span>\n {/snippet}\n {#snippet titleSlot()}\n <span class=\"text-sm text-on-info\">\n <strong class=\"font-semibold\">New:</strong> Custom indicator + snippet wrapping\n </span>\n {/snippet}\n </Banner>\n\n <Banner color=\"warning\" icon=\"lucide:wrench\" title=\"Custom actions\">\n {#snippet actionsSlot()}\n <div class=\"ms-2 flex items-center gap-2\">\n <Badge size=\"xs\" color=\"warning\" variant=\"solid\" label=\"BETA\" />\n <Button size=\"xs\" variant=\"solid\" color=\"warning\" label=\"Try it\" />\n </div>\n {/snippet}\n </Banner>\n </div>\n </section>\n\n <!-- UI overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override slot classes via the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code> prop,\n or stick the banner to viewport top via\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">class</code>.\n </p>\n <Banner\n color=\"surface\"\n icon=\"lucide:settings\"\n title=\"Taller container + rounded corners\"\n ui={{ container: 'min-h-14', root: 'rounded-lg' }}\n />\n </section>\n\n <Separator />\n\n <!-- SSR note -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">SSR & hydration notes</h2>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4 text-sm\">\n <p class=\"mb-2 font-medium\">\n When using <code class=\"text-xs\">id</code> for persistence:\n </p>\n <ul class=\"ms-5 list-disc space-y-1 text-on-surface-variant\">\n <li>\n The banner renders server-side. On the client, a\n <code class=\"rounded bg-surface-container-highest px-1 py-0.5 text-xs\"\n >$effect</code\n >\n reads localStorage and hides it if previously dismissed.\n </li>\n <li>\n Users who have <em>already dismissed</em> will see a one-frame flicker on initial\n page load. First-time visitors don't experience this.\n </li>\n <li>\n To eliminate the flicker, inject a prehydration script in your SvelteKit\n <code class=\"rounded bg-surface-container-highest px-1 py-0.5 text-xs\"\n >app.html</code\n > head:\n </li>\n </ul>\n <pre class=\"mt-3 overflow-x-auto rounded bg-surface-container-highest p-3 text-xs\"><code\n >{prehydrationRecipe}</code\n ></pre>\n </div>\n </section>\n</div>\n",
|
|
154
|
+
"chat": "<script lang=\"ts\">\n import { ChatBubble, ChatMessage, ChatInput, Avatar, Button } from '$lib/index.js'\n \n let messageText = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Chat</h1>\n <p class=\"text-on-surface-variant\">\n A comprehensive set of components to build chat interfaces, including bubbles, messages, and inputs.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Combine <code class=\"rounded bg-surface-container-highest px-1\">ChatBubble</code>, <code class=\"rounded bg-surface-container-highest px-1\">ChatMessage</code>, and <code class=\"rounded bg-surface-container-highest px-1\">ChatInput</code> to create conversational UI.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"w-full max-w-md h-[400px] flex flex-col bg-surface border border-outline-variant rounded-lg overflow-hidden\">\n <div class=\"flex-1 p-4 overflow-y-auto space-y-4\">\n <ChatBubble position=\"start\" name=\"Obi-Wan\" time=\"12:00\">\n {#snippet avatar()}\n <Avatar src=\"https://i.pravatar.cc/150?img=11\" alt=\"Obi\" />\n {/snippet}\n <ChatMessage>Hello there!</ChatMessage>\n </ChatBubble>\n \n <ChatBubble position=\"end\" name=\"Grievous\" time=\"12:01\">\n {#snippet avatar()}\n <Avatar src=\"https://i.pravatar.cc/150?img=12\" alt=\"Grievous\" />\n {/snippet}\n <ChatMessage variant=\"primary\">General Kenobi.</ChatMessage>\n <ChatMessage variant=\"primary\">You are a bold one.</ChatMessage>\n </ChatBubble>\n </div>\n \n <div class=\"p-3 border-t border-outline-variant bg-surface-50 dark:bg-surface-900\">\n <ChatInput bind:value={messageText} placeholder=\"Type a message...\">\n {#snippet trailing()}\n <Button icon=\"lucide:send\" size=\"sm\" color=\"primary\" square disabled={!messageText} />\n {/snippet}\n </ChatInput>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Message Variants</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">variant</code> prop on <code class=\"rounded bg-surface-container-highest px-1\">ChatMessage</code> to change its appearance.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-2 max-w-md\">\n <ChatBubble position=\"start\">\n <ChatMessage variant=\"surface\">Surface (Default)</ChatMessage>\n <ChatMessage variant=\"primary\">Primary</ChatMessage>\n <ChatMessage variant=\"outline\">Outline</ChatMessage>\n </ChatBubble>\n </div>\n </section>\n</div>\n",
|
|
155
|
+
"marquee": "<script lang=\"ts\">\n import { Marquee, Card } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Marquee</h1>\n <p class=\"text-on-surface-variant\">\n An infinite scrolling marquee component for displaying logos, text, or cards.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Horizontal Marquee</h2>\n <p class=\"text-sm text-on-surface-variant\">\n A standard left-scrolling marquee that pauses on hover.\n </p>\n <div class=\"rounded-lg bg-surface-container-high py-8 overflow-hidden\">\n <Marquee pauseOnHover={true} duration=\"20s\">\n {#each Array(6) as _, i}\n <Card class=\"w-48 p-4 text-center font-semibold\">Brand {i + 1}</Card>\n {/each}\n </Marquee>\n </div>\n </section>\n\n <!-- Vertical -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Vertical Marquee</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1\">direction=\"up\"</code> or <code class=\"rounded bg-surface-container-highest px-1\">\"down\"</code> for vertical scrolling.\n </p>\n <div class=\"rounded-lg bg-surface-container-high py-8 overflow-hidden flex justify-center\">\n <div class=\"h-[300px] flex gap-4 overflow-hidden\">\n <Marquee direction=\"up\" duration=\"15s\">\n {#each Array(5) as _, i}\n <Card class=\"w-48 p-4 text-center\">Feature A-{i + 1}</Card>\n {/each}\n </Marquee>\n <Marquee direction=\"down\" duration=\"15s\">\n {#each Array(5) as _, i}\n <Card class=\"w-48 p-4 text-center\">Feature B-{i + 1}</Card>\n {/each}\n </Marquee>\n </div>\n </div>\n </section>\n</div>\n",
|
|
124
156
|
"progress": "<script lang=\"ts\">\n import { Progress, Button } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const\n const animations = ['carousel', 'carousel-inverse', 'swing', 'elastic'] as const\n\n let value = $state(65)\n let stepValue = $state(2)\n const steps = ['Cart', 'Shipping', 'Payment', 'Confirm']\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Progress</h1>\n <p class=\"text-on-surface-variant\">\n Visual indicator for task completion or loading status.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress value={25} />\n <Progress value={50} />\n <Progress value={75} />\n <Progress value={100} />\n </div>\n </section>\n\n <!-- Interactive -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Interactive</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress {value} status />\n <div class=\"flex items-center gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (value = Math.max(0, value - 10))}\n >\n -10\n </Button>\n <input type=\"range\" min=\"0\" max=\"100\" bind:value class=\"flex-1\" />\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (value = Math.min(100, value + 10))}\n >\n +10\n </Button>\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <div class=\"flex items-center gap-3\">\n <span class=\"w-20 text-sm text-on-surface-variant capitalize\">{color}</span>\n <Progress {color} value={70} class=\"flex-1\" />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-3\">\n <span class=\"w-12 text-sm text-on-surface-variant\">{size}</span>\n <Progress {size} value={60} class=\"flex-1\" />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- With Status -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Status</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Display percentage text alongside the progress bar.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress value={33} status />\n <Progress value={66} status color=\"success\" />\n <Progress value={100} status color=\"tertiary\" />\n </div>\n </section>\n\n <!-- Indeterminate -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Indeterminate</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When value is null, the progress shows an animated indeterminate state.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress value={null} />\n <Progress value={null} color=\"success\" />\n <Progress value={null} color=\"tertiary\" />\n </div>\n </section>\n\n <!-- Animations -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Animations</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Different animation styles for the indeterminate state.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each animations as animation (animation)}\n <div class=\"flex items-center gap-3\">\n <span class=\"w-32 text-sm text-on-surface-variant\">{animation}</span>\n <Progress {animation} class=\"flex-1\" />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Vertical -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Vertical</h2>\n <div class=\"flex flex-wrap gap-8 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"h-32\">\n <Progress value={75} orientation=\"vertical\" />\n </div>\n <span class=\"text-xs text-on-surface-variant\">75%</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"h-32\">\n <Progress value={50} orientation=\"vertical\" color=\"success\" />\n </div>\n <span class=\"text-xs text-on-surface-variant\">50%</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"h-32\">\n <Progress value={null} orientation=\"vertical\" color=\"tertiary\" />\n </div>\n <span class=\"text-xs text-on-surface-variant\">loading</span>\n </div>\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"h-32\">\n <Progress value={60} orientation=\"vertical\" {size} />\n </div>\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Inverted -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Inverted</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Reverse the fill direction and status position.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"space-y-2\">\n <span class=\"text-sm text-on-surface-variant\">Normal</span>\n <Progress value={60} status />\n </div>\n <div class=\"space-y-2\">\n <span class=\"text-sm text-on-surface-variant\">Inverted</span>\n <Progress value={60} status inverted />\n </div>\n </div>\n <div class=\"flex gap-8\">\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"h-32\">\n <Progress value={60} orientation=\"vertical\" />\n </div>\n <span class=\"text-xs text-on-surface-variant\">Normal</span>\n </div>\n <div class=\"flex flex-col items-center gap-2\">\n <div class=\"h-32\">\n <Progress value={60} orientation=\"vertical\" inverted />\n </div>\n <span class=\"text-xs text-on-surface-variant\">Inverted</span>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Steps -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Steps</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an array of strings as max to display labeled steps with overlay transitions.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress value={stepValue} max={steps} />\n <div class=\"flex items-center justify-center gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (stepValue = Math.max(0, stepValue - 1))}\n disabled={stepValue === 0}\n >\n Previous\n </Button>\n <span class=\"px-4 text-sm text-on-surface-variant\">\n Step {stepValue + 1} of {steps.length}\n </span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (stepValue = Math.min(steps.length - 1, stepValue + 1))}\n disabled={stepValue === steps.length - 1}\n >\n Next\n </Button>\n </div>\n\n <Progress value={0} max={['Account', 'Profile', 'Review', 'Done']} color=\"tertiary\" />\n <Progress value={3} max={['Account', 'Profile', 'Review', 'Done']} color=\"success\" />\n </div>\n </section>\n\n <!-- Custom Status Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Status Slot</h2>\n <p class=\"text-sm text-on-surface-variant\">Customize the status display with a snippet.</p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress value={75} status>\n {#snippet statusSlot({ percent })}\n <span class=\"font-medium text-primary\">{percent}% completed</span>\n {/snippet}\n </Progress>\n <Progress value={40} status color=\"warning\">\n {#snippet statusSlot({ percent })}\n <span class=\"text-warning\">Uploading... {percent}%</span>\n {/snippet}\n </Progress>\n </div>\n </section>\n\n <!-- Custom Step Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Step Slot</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Customize individual step labels with a snippet.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress value={1} max={['Draft', 'Review', 'Approved', 'Published']}>\n {#snippet stepSlot({ step, index })}\n <span class=\"flex items-center gap-1\">\n <span\n class=\"inline-flex size-5 items-center justify-center rounded-full text-xs\n {index <= 1\n ? 'bg-primary text-on-primary'\n : 'bg-surface-container-highest text-on-surface-variant'}\"\n >\n {index + 1}\n </span>\n {step}\n </span>\n {/snippet}\n </Progress>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <Progress\n value={70}\n ui={{\n base: 'rounded-none',\n indicator: 'rounded-none bg-gradient-to-r from-primary to-tertiary'\n }}\n />\n <Progress\n value={50}\n status\n ui={{\n base: 'h-4 rounded-lg',\n indicator: 'rounded-lg',\n status: 'font-bold text-primary'\n }}\n />\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"space-y-6 rounded-lg bg-surface-container-high p-4\">\n <!-- File Upload -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">File Upload</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <div class=\"mb-2 flex items-center justify-between\">\n <span class=\"text-sm\">document.pdf</span>\n <span class=\"text-sm text-on-surface-variant\">2.4 MB / 3.2 MB</span>\n </div>\n <Progress value={75} color=\"primary\" size=\"sm\" />\n </div>\n </div>\n\n <!-- Loading State -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Loading State</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <div class=\"mb-2 flex items-center gap-2\">\n <span class=\"text-sm\">Processing data...</span>\n </div>\n <Progress value={null} color=\"info\" size=\"xs\" />\n </div>\n </div>\n\n <!-- Multi-step Form -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Checkout Flow</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-4\">\n <Progress\n value={2}\n max={['Cart', 'Shipping', 'Payment', 'Done']}\n color=\"success\"\n />\n </div>\n </div>\n\n <!-- Skill Bars -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Skill Bars</p>\n <div\n class=\"space-y-3 rounded-lg border border-outline-variant bg-surface-container p-4\"\n >\n <div class=\"space-y-1\">\n <div class=\"flex justify-between text-sm\">\n <span>TypeScript</span>\n <span class=\"text-on-surface-variant\">90%</span>\n </div>\n <Progress value={90} color=\"primary\" size=\"sm\" />\n </div>\n <div class=\"space-y-1\">\n <div class=\"flex justify-between text-sm\">\n <span>Svelte</span>\n <span class=\"text-on-surface-variant\">85%</span>\n </div>\n <Progress value={85} color=\"tertiary\" size=\"sm\" />\n </div>\n <div class=\"space-y-1\">\n <div class=\"flex justify-between text-sm\">\n <span>Rust</span>\n <span class=\"text-on-surface-variant\">60%</span>\n </div>\n <Progress value={60} color=\"warning\" size=\"sm\" />\n </div>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Colors x Sizes Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors x Sizes</h2>\n <div class=\"overflow-x-auto rounded-lg bg-surface-container-high p-4\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-outline-variant\">\n <th class=\"px-3 py-3 text-left text-sm font-medium text-on-surface-variant\"\n >Color</th\n >\n {#each sizes as size (size)}\n <th\n class=\"px-3 py-3 text-center text-sm font-medium text-on-surface-variant\"\n >{size}</th\n >\n {/each}\n </tr>\n </thead>\n <tbody>\n {#each colors as color (color)}\n <tr class=\"border-b border-outline-variant/50\">\n <td\n class=\"px-3 py-3 text-sm font-medium text-on-surface-variant capitalize\"\n >{color}</td\n >\n {#each sizes as size (size)}\n <td class=\"px-3 py-3\">\n <Progress {color} {size} value={65} />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\n </div>\n </section>\n</div>\n",
|
|
125
157
|
"toast": "<script lang=\"ts\">\n import { toast, Toaster } from '$lib/index.js'\n import { Button, Icon } from '$lib/index.js'\n import type { ToasterProps } from '$lib/Toast/toast.types.js'\n\n type Variant = NonNullable<ToasterProps['variant']>\n type Position = NonNullable<ToasterProps['position']>\n\n const variants: Variant[] = ['outline', 'soft', 'subtle', 'solid', 'accent']\n const positions: Position[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right'\n ]\n\n let activeVariant: Variant = $state('outline')\n let activePosition: Position = $state('bottom-right')\n let expandToasts = $state(false)\n let showCloseButton = $state(true)\n\n let counter = $state(0)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Toast</h1>\n <p class=\"text-on-surface-variant\">\n Non-intrusive notification messages that appear temporarily. Powered by svelte-sonner\n with Svelora semantic color theming.\n </p>\n </div>\n\n <!-- Toaster Configuration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Toaster Configuration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Adjust the Toaster props to see how they affect all toasts.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <!-- Variant -->\n <div class=\"space-y-1.5\">\n <p class=\"text-sm font-medium\">Variant</p>\n <div class=\"flex flex-wrap gap-2\">\n {#each variants as v (v)}\n <Button\n variant={activeVariant === v ? 'solid' : 'outline'}\n size=\"xs\"\n onclick={() => (activeVariant = v)}\n >\n {v}\n </Button>\n {/each}\n </div>\n </div>\n\n <!-- Position -->\n <div class=\"space-y-1.5\">\n <p class=\"text-sm font-medium\">Position</p>\n <div class=\"flex flex-wrap gap-2\">\n {#each positions as p (p)}\n <Button\n variant={activePosition === p ? 'solid' : 'outline'}\n size=\"xs\"\n onclick={() => (activePosition = p)}\n >\n {p}\n </Button>\n {/each}\n </div>\n </div>\n\n <!-- Toggles -->\n <div class=\"flex flex-wrap gap-4\">\n <label class=\"flex items-center gap-2 text-sm\">\n <input type=\"checkbox\" bind:checked={expandToasts} class=\"accent-primary\" />\n Expand\n </label>\n <label class=\"flex items-center gap-2 text-sm\">\n <input type=\"checkbox\" bind:checked={showCloseButton} class=\"accent-primary\" />\n Close Button\n </label>\n </div>\n </div>\n </section>\n\n <!-- Basic Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic Usage</h2>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button variant=\"outline\" onclick={() => toast('This is a default toast')}>\n Default\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => toast('Title only toast — no description needed')}\n >\n Title Only\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('Event has been created', {\n description: 'Monday, January 3rd at 6:00 PM'\n })}\n >\n With Description\n </Button>\n </div>\n </section>\n\n <!-- Types -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Types</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Each type automatically maps to a semantic color from the theme.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n color=\"success\"\n variant=\"soft\"\n onclick={() => toast.success('Operation completed successfully')}\n >\n Success\n </Button>\n <Button\n color=\"error\"\n variant=\"soft\"\n onclick={() => toast.error('Something went wrong')}\n >\n Error\n </Button>\n <Button\n color=\"warning\"\n variant=\"soft\"\n onclick={() => toast.warning('Please review before proceeding')}\n >\n Warning\n </Button>\n <Button\n color=\"info\"\n variant=\"soft\"\n onclick={() => toast.info('Here is some useful information')}\n >\n Info\n </Button>\n <Button variant=\"soft\" onclick={() => toast.loading('Loading data...')}>Loading</Button>\n </div>\n </section>\n\n <!-- Types with Description -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Types with Description</h2>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n color=\"success\"\n variant=\"soft\"\n onclick={() =>\n toast.success('Payment Successful', {\n description: 'Your payment of $49.99 has been processed.'\n })}\n >\n Success\n </Button>\n <Button\n color=\"error\"\n variant=\"soft\"\n onclick={() =>\n toast.error('Upload Failed', {\n description: 'The file exceeds the maximum size of 10MB.'\n })}\n >\n Error\n </Button>\n <Button\n color=\"warning\"\n variant=\"soft\"\n onclick={() =>\n toast.warning('Storage Almost Full', {\n description: 'You have used 90% of your storage quota.'\n })}\n >\n Warning\n </Button>\n <Button\n color=\"info\"\n variant=\"soft\"\n onclick={() =>\n toast.info('New Update Available', {\n description: 'Version 2.0 is ready to install.'\n })}\n >\n Info\n </Button>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >color</code\n >\n option to apply any semantic color, beyond the 4 built-in types.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n color=\"primary\"\n variant=\"soft\"\n onclick={() => toast('Primary notification', { color: 'primary' })}\n >\n Primary\n </Button>\n <Button\n color=\"secondary\"\n variant=\"soft\"\n onclick={() => toast('Secondary notification', { color: 'secondary' })}\n >\n Secondary\n </Button>\n <Button\n color=\"tertiary\"\n variant=\"soft\"\n onclick={() => toast('Tertiary notification', { color: 'tertiary' })}\n >\n Tertiary\n </Button>\n <Button\n color=\"success\"\n variant=\"soft\"\n onclick={() =>\n toast('Success notification', {\n color: 'success',\n description: 'Same as toast.success() but via color option'\n })}\n >\n Success\n </Button>\n <Button\n color=\"warning\"\n variant=\"soft\"\n onclick={() => toast('Warning notification', { color: 'warning' })}\n >\n Warning\n </Button>\n <Button\n color=\"error\"\n variant=\"soft\"\n onclick={() => toast('Error notification', { color: 'error' })}\n >\n Error\n </Button>\n <Button\n color=\"info\"\n variant=\"soft\"\n onclick={() => toast('Info notification', { color: 'info' })}\n >\n Info\n </Button>\n </div>\n </section>\n\n <!-- Custom Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Icons</h2>\n\n <!-- Global icons -->\n <div class=\"space-y-1.5\">\n <p class=\"text-sm font-medium\">Global Icons (via Toaster snippets)</p>\n <p class=\"text-sm text-on-surface-variant\">\n The Toaster below uses Iconify icons instead of sonner's default SVGs. All typed\n toasts inherit them automatically.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n color=\"success\"\n onclick={() =>\n toast.success('Deployed to production', {\n description: 'Uses global successIcon snippet.'\n })}\n >\n Success (global)\n </Button>\n <Button\n variant=\"outline\"\n color=\"error\"\n onclick={() =>\n toast.error('Build failed', {\n description: 'Uses global errorIcon snippet.'\n })}\n >\n Error (global)\n </Button>\n <Button\n variant=\"outline\"\n color=\"warning\"\n onclick={() =>\n toast.warning('Disk usage at 92%', {\n description: 'Uses global warningIcon snippet.'\n })}\n >\n Warning (global)\n </Button>\n <Button\n variant=\"outline\"\n color=\"info\"\n onclick={() =>\n toast.info('Maintenance scheduled', {\n description: 'Uses global infoIcon snippet.'\n })}\n >\n Info (global)\n </Button>\n </div>\n </div>\n\n <!-- Per-toast icon override -->\n <div class=\"space-y-1.5\">\n <p class=\"text-sm font-medium\">Per-toast Icon Override</p>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an Iconify icon name string, or\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">null</code>\n to hide the icon.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('Rocket launched!', {\n description: 'icon: \"lucide:rocket\"',\n icon: 'lucide:rocket'\n })}\n >\n Rocket\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.success('Git push complete', {\n description: 'icon: \"lucide:git-branch\"',\n icon: 'lucide:git-branch'\n })}\n >\n Git Branch\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.warning('Battery low', {\n description: 'icon: \"lucide:battery-low\"',\n icon: 'lucide:battery-low'\n })}\n >\n Battery\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.error('Server offline', {\n description: 'icon: \"lucide:server-off\"',\n icon: 'lucide:server-off'\n })}\n >\n Server\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.success('No icon toast', {\n description: 'icon: null',\n icon: null\n })}\n >\n icon: null\n </Button>\n </div>\n </div>\n </section>\n\n <!-- Avatar -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Avatar</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >avatar</code\n >\n option to show an avatar in the icon slot.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('John commented on your post', {\n description: '\"Great article! Thanks for sharing.\"',\n avatar: {\n src: 'https://i.pravatar.cc/150?img=1',\n alt: 'John'\n }\n })}\n >\n With Photo\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.info('Jane sent you a message', {\n description: '\"Hey, are you free for a call?\"',\n avatar: {\n src: 'https://i.pravatar.cc/150?img=5',\n alt: 'Jane'\n }\n })}\n >\n Info + Avatar\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('Alex invited you to a project', {\n description: 'Project: Svelora Design System',\n avatar: { alt: 'Alex' },\n action: {\n label: 'Accept',\n onClick: () => toast.success('Invitation accepted')\n }\n })}\n >\n Initials + Action\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.success('Team joined!', {\n description: 'You are now a member of the Design team.',\n avatar: { icon: 'lucide:users' }\n })}\n >\n Icon Fallback\n </Button>\n </div>\n </section>\n\n <!-- Action & Cancel -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Action & Cancel</h2>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('File deleted', {\n action: {\n label: 'Undo',\n onClick: () => toast.success('File restored')\n }\n })}\n >\n Action Button\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('Accept cookies?', {\n action: {\n label: 'Accept',\n onClick: () => toast.success('Cookies accepted')\n },\n cancel: {\n label: 'Decline',\n onClick: () => toast('Cookies declined')\n }\n })}\n >\n Action + Cancel\n </Button>\n <Button\n variant=\"outline\"\n color=\"info\"\n onclick={() =>\n toast.info('New version available', {\n action: {\n label: 'Update Now',\n onClick: () => toast.success('Updating...')\n },\n cancel: {\n label: 'Later',\n onClick: () => {}\n }\n })}\n >\n Info with Actions\n </Button>\n <Button\n variant=\"outline\"\n color=\"error\"\n onclick={() =>\n toast.error('Delete this item?', {\n description: 'This action cannot be undone.',\n action: {\n label: 'Delete',\n onClick: () => toast.success('Item deleted')\n },\n cancel: {\n label: 'Keep',\n onClick: () => {}\n }\n })}\n >\n Error with Confirm\n </Button>\n </div>\n </section>\n\n <!-- Promise -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Promise</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Automatically transitions between loading, success, and error states.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() => {\n toast.promise(new Promise((resolve) => setTimeout(resolve, 2000)), {\n loading: 'Saving changes...',\n success: 'Changes saved successfully!',\n error: 'Failed to save changes'\n })\n }}\n >\n Save (Resolves)\n </Button>\n <Button\n variant=\"outline\"\n color=\"error\"\n onclick={() => {\n toast.promise(\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error('timeout')), 2000)\n ),\n {\n loading: 'Connecting to server...',\n success: 'Connected!',\n error: 'Connection failed'\n }\n )\n }}\n >\n Connect (Rejects)\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n toast.promise(new Promise((resolve) => setTimeout(resolve, 3000)), {\n loading: 'Uploading file (0%)...',\n success: 'File uploaded!',\n error: 'Upload failed'\n })\n }}\n >\n Upload File\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n toast.promise(new Promise((resolve) => setTimeout(resolve, 4000)), {\n loading: 'Deploying to production...',\n success: 'Deployed! Live at https://example.com',\n error: 'Deploy failed — check CI logs'\n })\n }}\n >\n Deploy\n </Button>\n </div>\n </section>\n\n <!-- Duration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Duration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control how long a toast stays visible before auto-dismiss.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button variant=\"outline\" onclick={() => toast('Gone in a flash', { duration: 1000 })}>\n 1 second\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => toast('Standard duration', { duration: 3000 })}\n >\n 3 seconds\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => toast('Longer reading time', { duration: 8000 })}\n >\n 8 seconds\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('This toast will stay until dismissed', { duration: Infinity })}\n >\n Persistent (Infinity)\n </Button>\n </div>\n </section>\n\n <!-- Update Existing Toast -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Update Existing Toast</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Reuse the same ID to update an existing toast in-place.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() => {\n const id = toast.loading('Uploading...', { duration: Infinity })\n setTimeout(() => toast.success('Upload complete!', { id }), 2000)\n }}\n >\n Loading → Success\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n const id = toast.loading('Validating...', { duration: Infinity })\n setTimeout(() => toast.error('Validation failed!', { id }), 2000)\n }}\n >\n Loading → Error\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n const id = toast('Step 1 of 3...', { duration: Infinity })\n setTimeout(() => toast('Step 2 of 3...', { id, duration: Infinity }), 1000)\n setTimeout(() => toast('Step 3 of 3...', { id, duration: Infinity }), 2000)\n setTimeout(() => toast.success('All steps complete!', { id }), 3000)\n }}\n >\n Multi-step Progress\n </Button>\n </div>\n </section>\n\n <!-- Deduplicated -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Deduplicated Toasts</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass the same <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >id</code\n >\n to prevent duplicate toasts. Clicking multiple times updates the existing toast instead of\n creating new ones.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() =>\n toast('You have 3 new messages', {\n id: 'new-messages',\n description: 'Click again — no duplicate!'\n })}\n >\n Same ID (click many times)\n </Button>\n <Button\n variant=\"outline\"\n color=\"warning\"\n onclick={() =>\n toast.warning('Rate limit reached', {\n id: 'rate-limit',\n description: 'Try again in 30 seconds.'\n })}\n >\n Rate Limit (deduplicated)\n </Button>\n </div>\n </section>\n\n <!-- Dismiss -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Programmatic Dismiss</h2>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() => {\n const id = toast('Processing...', { duration: Infinity })\n setTimeout(() => {\n toast.dismiss(id)\n toast.success('Done!')\n }, 2000)\n }}\n >\n Auto Dismiss After 2s\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n toast('Toast 1', { duration: Infinity })\n toast.success('Toast 2', { duration: Infinity })\n toast.error('Toast 3', { duration: Infinity })\n }}\n >\n Create 3 Toasts\n </Button>\n <Button variant=\"outline\" color=\"error\" onclick={() => toast.dismiss()}>\n Dismiss All\n </Button>\n </div>\n </section>\n\n <!-- Stacking -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Stacking</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Toggle \"Expand\" in the config above, then fire multiple toasts to see stacked vs\n expanded behavior.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() => {\n counter++\n toast.success(`Notification #${counter}`, {\n description: 'Hover or expand to see all stacked toasts.'\n })\n }}\n >\n Add Toast (#{counter + 1})\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n toast.info('Info toast', { description: 'First in stack' })\n setTimeout(\n () => toast.warning('Warning toast', { description: 'Second in stack' }),\n 300\n )\n setTimeout(\n () => toast.error('Error toast', { description: 'Third in stack' }),\n 600\n )\n }}\n >\n Fire 3 Different Types\n </Button>\n </div>\n </section>\n\n <!-- Non-dismissible -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Non-dismissible</h2>\n <p class=\"text-sm text-on-surface-variant\">\n A toast that cannot be swiped away or closed by the user.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() => {\n const id = toast.warning('Processing payment...', {\n description: 'Please do not close this page.',\n dismissible: false,\n duration: Infinity\n })\n setTimeout(() => {\n toast.success('Payment complete!', { id })\n }, 3000)\n }}\n >\n Non-dismissible (3s)\n </Button>\n </div>\n </section>\n\n <!-- All Types at Once (visual test) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">All Types at Once</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Fire every type simultaneously to visually compare styling.\n </p>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() => {\n toast('Default toast', {\n description: 'Neutral surface styling',\n duration: 10000\n })\n toast.success('Success toast', {\n description: 'Operation successful',\n duration: 10000\n })\n toast.error('Error toast', {\n description: 'Something went wrong',\n duration: 10000\n })\n toast.warning('Warning toast', {\n description: 'Proceed with caution',\n duration: 10000\n })\n toast.info('Info toast', {\n description: 'Here is some context',\n duration: 10000\n })\n toast.loading('Loading toast', {\n description: 'Please wait...',\n duration: 10000\n })\n }}\n >\n Fire All Types (10s)\n </Button>\n </div>\n </section>\n\n <!-- Real World Examples -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n <div class=\"flex flex-wrap gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.success('Profile Updated', {\n description: 'Your profile changes have been saved.'\n })}\n >\n Save Profile\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.error('Permission Denied', {\n description: 'You do not have access to this resource.',\n action: {\n label: 'Request Access',\n onClick: () => toast.info('Access request sent to admin')\n }\n })}\n >\n Access Denied\n </Button>\n <Button\n variant=\"outline\"\n onclick={() =>\n toast.warning('Session Expiring', {\n description: 'Your session will expire in 5 minutes.',\n duration: 8000,\n action: {\n label: 'Extend',\n onClick: () => toast.success('Session extended by 30 minutes')\n }\n })}\n >\n Session Warning\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n toast.promise(new Promise((resolve) => setTimeout(resolve, 3000)), {\n loading: 'Sending email...',\n success: 'Email sent to john@example.com',\n error: 'Failed to send email'\n })\n }}\n >\n Send Email\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n const id = toast('Item moved to trash', {\n action: {\n label: 'Undo',\n onClick: () => toast.success('Item restored')\n },\n cancel: {\n label: 'Delete permanently',\n onClick: () => {\n toast.dismiss(id)\n toast.error('Item permanently deleted')\n }\n }\n })\n }}\n >\n Move to Trash\n </Button>\n <Button\n variant=\"outline\"\n onclick={() => {\n toast.info('New comment on your post', {\n description: '\"Great article! Thanks for sharing.\" — Jane',\n action: {\n label: 'View',\n onClick: () => toast('Opening post...')\n }\n })\n }}\n >\n New Comment\n </Button>\n </div>\n </section>\n</div>\n\n<!-- Dynamic Toaster with configurable props -->\n<Toaster\n variant={activeVariant}\n position={activePosition}\n expand={expandToasts}\n closeButton={showCloseButton}\n>\n {#snippet successIcon()}<Icon name=\"lucide:circle-check\" size=\"18\" />{/snippet}\n {#snippet errorIcon()}<Icon name=\"lucide:circle-x\" size=\"18\" />{/snippet}\n {#snippet warningIcon()}<Icon name=\"lucide:triangle-alert\" size=\"18\" />{/snippet}\n {#snippet infoIcon()}<Icon name=\"lucide:info\" size=\"18\" />{/snippet}\n</Toaster>\n",
|
|
158
|
+
"spotlight": "<script lang=\"ts\">\n import { Spotlight } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Spotlight</h1>\n <p class=\"text-on-surface-variant\">\n A hover effect that follows your mouse cursor around the container.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Wrap your content in a <code class=\"rounded bg-surface-container-highest px-1\">Spotlight</code> component. Hover over the card to see the effect.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <Spotlight class=\"w-full max-w-sm rounded-xl border border-outline bg-surface-50 dark:bg-surface-900 p-8\">\n <h3 class=\"text-xl font-bold mb-2\">Interactive Card</h3>\n <p class=\"text-surface-500\">\n The spotlight follows your cursor, creating a subtle, premium look for modern interfaces.\n </p>\n </Spotlight>\n </div>\n </section>\n \n <!-- Custom Color -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Color & Size</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">color</code> and <code class=\"rounded bg-surface-container-highest px-1\">size</code> props to adjust the spotlight appearance.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex gap-4 justify-center flex-wrap\">\n <Spotlight \n color=\"rgba(59, 130, 246, 0.2)\" \n size={300}\n class=\"w-full max-w-[200px] rounded-xl border border-outline bg-surface-900 text-white p-6\"\n >\n <div class=\"font-semibold text-primary-300\">Blue Spotlight</div>\n </Spotlight>\n \n <Spotlight \n color=\"rgba(16, 185, 129, 0.2)\" \n size={500}\n class=\"w-full max-w-[200px] rounded-xl border border-outline bg-surface-900 text-white p-6\"\n >\n <div class=\"font-semibold text-success-400\">Large Green Spotlight</div>\n </Spotlight>\n </div>\n </section>\n</div>\n",
|
|
126
159
|
"breadcrumb": "<script lang=\"ts\">\n import { Breadcrumb, Icon, Link, Separator } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Breadcrumb</h1>\n <p class=\"text-on-surface-variant\">\n Display a hierarchy of navigation links to show the user's current location within a\n site.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Home', href: '/' },\n { label: 'Products', href: '/products' },\n { label: 'Laptops', href: '/products/laptops' },\n { label: 'MacBook Pro' }\n ]}\n />\n </div>\n </section>\n\n <!-- With Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icons</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Home', href: '/', icon: 'lucide:home' },\n { label: 'Settings', href: '/settings', icon: 'lucide:settings' },\n { label: 'Profile', icon: 'lucide:user' }\n ]}\n />\n </div>\n </section>\n\n <!-- Separator Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Separator Icon</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Customize the separator between items via the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >separatorIcon</code\n > prop.\n </p>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n {#each [{ icon: 'lucide:chevron-right', name: 'Chevron (default)' }, { icon: 'lucide:slash', name: 'Slash' }, { icon: 'lucide:arrow-right', name: 'Arrow' }, { icon: 'lucide:dot', name: 'Dot' }] as sep (sep.icon)}\n <div class=\"space-y-1 rounded-lg bg-surface-container-high p-4\">\n <p class=\"text-xs font-medium text-on-surface-variant\">{sep.name}</p>\n <Breadcrumb\n separatorIcon={sep.icon}\n items={[\n { label: 'Home', href: '/' },\n { label: 'Docs', href: '/docs' },\n { label: 'API' }\n ]}\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Separator Snippet -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Separator Snippet</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">separator</code\n > snippet for fully custom separator content.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Home', href: '/' },\n { label: 'Category', href: '/category' },\n { label: 'Current Page' }\n ]}\n >\n {#snippet separator()}\n <span class=\"text-sm text-on-surface-variant/40\">/</span>\n {/snippet}\n </Breadcrumb>\n </div>\n </section>\n\n <!-- Disabled Items -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled Items</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Home', href: '/' },\n { label: 'Archived', href: '/archived', disabled: true },\n { label: 'Old Post', href: '/archived/old-post', disabled: true },\n { label: 'Detail' }\n ]}\n />\n </div>\n </section>\n\n <!-- Custom Item Snippet -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Item Snippet</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">item</code>\n snippet for fully custom item rendering.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Home', href: '/', icon: 'lucide:home' },\n { label: 'Projects', href: '/projects', icon: 'lucide:folder' },\n { label: 'svelora', icon: 'lucide:package' }\n ]}\n >\n {#snippet item({ item: crumb, active })}\n {#if active}\n <span\n class=\"inline-flex items-center gap-1.5 rounded-full bg-primary/10 px-3 py-1 text-sm font-semibold text-primary\"\n >\n {#if crumb.icon}\n <Icon name={crumb.icon} size=\"14\" />\n {/if}\n {crumb.label}\n </span>\n {:else}\n <Link\n href={crumb.href ?? ''}\n raw\n class=\"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm text-on-surface-variant transition-colors hover:bg-surface-container-highest\"\n >\n {#if crumb.icon}\n <Icon name={crumb.icon} size=\"14\" />\n {/if}\n {crumb.label}\n </Link>\n {/if}\n {/snippet}\n </Breadcrumb>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Custom active color</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n ui={{ link: 'text-on-surface font-semibold' }}\n items={[\n { label: 'Dashboard', href: '/' },\n { label: 'Analytics', href: '/analytics' },\n { label: 'Revenue' }\n ]}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Larger separator</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n ui={{ separatorIcon: 'size-6 text-primary/40' }}\n items={[\n { label: 'Home', href: '/' },\n { label: 'Blog', href: '/blog' },\n { label: 'Latest Post' }\n ]}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">With background container</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n ui={{ root: 'bg-surface-container rounded-lg px-4 py-2' }}\n items={[\n { label: 'Home', href: '/' },\n { label: 'Shop', href: '/shop' },\n { label: 'Electronics', href: '/shop/electronics' },\n { label: 'Phones' }\n ]}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Wider gap</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n ui={{ list: 'gap-3' }}\n items={[\n { label: 'Home', href: '/' },\n { label: 'Team', href: '/team' },\n { label: 'Members' }\n ]}\n />\n </div>\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <div class=\"grid gap-4 md:grid-cols-2\">\n <!-- File Browser -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">File Browser</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Root', href: '/', icon: 'lucide:hard-drive' },\n { label: 'Users', href: '/users', icon: 'lucide:users' },\n { label: 'Documents', href: '/documents', icon: 'lucide:folder' },\n { label: 'README.md', icon: 'lucide:file-text' }\n ]}\n />\n </div>\n </div>\n\n <!-- E-commerce -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">E-commerce</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n items={[\n { label: 'Store', href: '/' },\n { label: 'Electronics', href: '/electronics' },\n { label: 'Laptops', href: '/electronics/laptops' },\n { label: 'ASUS ROG Strix' }\n ]}\n />\n </div>\n </div>\n\n <!-- Admin Dashboard -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Admin Dashboard</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n ui={{ root: 'bg-surface-container rounded-lg px-4 py-2.5' }}\n items={[\n { label: 'Admin', href: '/admin', icon: 'lucide:shield' },\n { label: 'Users', href: '/admin/users', icon: 'lucide:users' },\n { label: 'Permissions', icon: 'lucide:lock' }\n ]}\n />\n </div>\n </div>\n\n <!-- Documentation -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Documentation</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Breadcrumb\n separatorIcon=\"lucide:slash\"\n items={[\n { label: 'Docs', href: '/docs', icon: 'lucide:book-open' },\n { label: 'Components', href: '/docs/components' },\n { label: 'Breadcrumb' }\n ]}\n />\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
127
160
|
"pagination": "<script lang=\"ts\">\n import { Pagination, Button, Separator } from '$lib/index.js'\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n const activeVariants = ['solid', 'outline', 'soft', 'subtle', 'ghost'] as const\n const navVariants = ['ghost', 'outline', 'soft', 'subtle', 'solid', 'link'] as const\n\n let controlledPage = $state(5)\n let callbackPage = $state(1)\n let callbackLog = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">Pagination</h1>\n\n <!-- Basic -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Basic Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Simple pagination with previous/next controls and page numbers.\n </p>\n <Pagination total={100} itemsPerPage={10} />\n </section>\n\n <!-- Default Page -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Default Page</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set the initial page with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">defaultPage</code\n >.\n </p>\n <Pagination total={100} itemsPerPage={10} defaultPage={5} />\n </section>\n\n <!-- Show Edges -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Show Edges</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Display first/last page buttons with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">showEdges</code\n >.\n </p>\n <Pagination total={100} itemsPerPage={10} showEdges />\n </section>\n\n <!-- Without Controls -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Without Controls</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Hide prev/next buttons with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >showControls={'{false}'}</code\n >.\n </p>\n <div class=\"space-y-3\">\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">Pages only</p>\n <Pagination total={100} itemsPerPage={10} showControls={false} />\n </div>\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">With edges, no prev/next</p>\n <Pagination total={100} itemsPerPage={10} showEdges showControls={false} />\n </div>\n </div>\n </section>\n\n <!-- Sibling Count -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Sibling Count</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control visible siblings around current page with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >siblingCount</code\n >.\n </p>\n <div class=\"space-y-3\">\n {#each [0, 1, 2] as count (count)}\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">\n siblingCount={count}{count === 1 ? ' (default)' : ''}\n </p>\n <Pagination total={100} itemsPerPage={10} page={5} siblingCount={count} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Items Per Page -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Items Per Page</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Same total (100) with different <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >itemsPerPage</code\n > values.\n </p>\n <div class=\"space-y-3\">\n {#each [5, 10, 25, 50] as perPage (perPage)}\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">\n {perPage} items/page ({Math.ceil(100 / perPage)} pages)\n </p>\n <Pagination total={100} itemsPerPage={perPage} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Controlled State -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Controlled State</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >bind:page</code\n >\n for two-way binding. Current page:\n <strong class=\"text-on-surface\">{controlledPage}</strong>\n </p>\n <Pagination total={200} itemsPerPage={10} bind:page={controlledPage} showEdges />\n <div class=\"flex gap-2\">\n {#each [1, 10, 20] as p (p)}\n <Button\n variant=\"solid\"\n color=\"primary\"\n size=\"sm\"\n label=\"Go to page {p}\"\n onclick={() => (controlledPage = p)}\n />\n {/each}\n </div>\n </section>\n\n <!-- Active Colors -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Active Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Customize the active page color with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">activeColor</code\n >.\n </p>\n <div class=\"space-y-3\">\n {#each colors as color (color)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-20 text-sm text-on-surface-variant\">{color}</span>\n <Pagination total={100} itemsPerPage={10} page={3} activeColor={color} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Active Variant -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Active Variant</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Change the selected page style with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >activeVariant</code\n >.\n </p>\n <div class=\"space-y-3\">\n {#each activeVariants as av (av)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-16 text-sm text-on-surface-variant\">{av}</span>\n <Pagination total={100} itemsPerPage={10} page={3} activeVariant={av} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Nav Button Variant -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Nav Button Variant</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Change navigation button style with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">variant</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code>.\n </p>\n <div class=\"space-y-3\">\n {#each navVariants as v (v)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-16 text-sm text-on-surface-variant\">{v}</span>\n <Pagination\n total={100}\n itemsPerPage={10}\n page={3}\n variant={v}\n color=\"primary\"\n showEdges\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Sizes</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control the pagination size with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code\n >.\n </p>\n <div class=\"space-y-4\">\n {#each sizes as size (size)}\n <div class=\"flex items-center gap-4\">\n <span class=\"w-8 text-sm text-on-surface-variant\">{size}</span>\n <Pagination total={100} itemsPerPage={10} {size} showEdges />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Disabled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Disable all controls with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">disabled</code\n >.\n </p>\n <Pagination total={100} itemsPerPage={10} page={3} disabled showEdges />\n </section>\n\n <!-- Page Change Callback -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Page Change Callback</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Listen for page changes with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >onPageChange</code\n >. Current page: <strong class=\"text-on-surface\">{callbackPage}</strong>\n </p>\n <Pagination\n total={100}\n itemsPerPage={10}\n bind:page={callbackPage}\n onPageChange={(p) => (callbackLog = `Navigated to page ${p}`)}\n showEdges\n />\n {#if callbackLog}\n <p class=\"text-xs text-on-surface-variant\">{callbackLog}</p>\n {/if}\n </section>\n\n <!-- Custom Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Custom Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">Customize navigation icons with icon props.</p>\n <Pagination\n total={100}\n itemsPerPage={10}\n page={5}\n showEdges\n prevIcon=\"lucide:arrow-left\"\n nextIcon=\"lucide:arrow-right\"\n ellipsisIcon=\"lucide:more-horizontal\"\n />\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">UI Slot Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Customize individual parts with <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code\n >.\n </p>\n <div class=\"space-y-4\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Rounded buttons</p>\n <Pagination\n total={100}\n itemsPerPage={10}\n page={3}\n showEdges\n ui={{\n item: 'rounded-full',\n first: 'rounded-full',\n prev: 'rounded-full',\n next: 'rounded-full',\n last: 'rounded-full'\n }}\n />\n </div>\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Custom gap & root</p>\n <Pagination\n total={100}\n itemsPerPage={10}\n page={3}\n class=\"rounded-lg border border-outline-variant bg-surface-container p-3\"\n ui={{ list: 'gap-2' }}\n />\n </div>\n </div>\n </section>\n\n <!-- Custom Snippet Slots -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Custom Snippet Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">Override individual parts with snippet slots.</p>\n <div class=\"space-y-4\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Custom item slot (rounded)</p>\n <Pagination total={50} itemsPerPage={10} page={2}>\n {#snippet itemSlot({ page, selected })}\n <span\n class=\"inline-flex size-9 items-center justify-center rounded-full text-sm font-bold {selected\n ? 'bg-primary text-on-primary'\n : 'text-on-surface-variant'}\"\n >\n {page}\n </span>\n {/snippet}\n </Pagination>\n </div>\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Custom prev/next text</p>\n <Pagination total={100} itemsPerPage={10} page={3}>\n {#snippet prevSlot({ disabled })}\n <span\n class=\"text-sm font-medium {disabled\n ? 'text-on-surface-variant/50'\n : 'text-primary'}\"\n >\n Previous\n </span>\n {/snippet}\n {#snippet nextSlot({ disabled })}\n <span\n class=\"text-sm font-medium {disabled\n ? 'text-on-surface-variant/50'\n : 'text-primary'}\"\n >\n Next\n </span>\n {/snippet}\n </Pagination>\n </div>\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Custom ellipsis</p>\n <Pagination total={100} itemsPerPage={10} page={5}>\n {#snippet ellipsisSlot()}\n <span class=\"text-on-surface-variant\">---</span>\n {/snippet}\n </Pagination>\n </div>\n </div>\n </section>\n\n <!-- Edge Cases -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Edge Cases</h2>\n <p class=\"text-sm text-on-surface-variant\">Handling special scenarios.</p>\n <div class=\"space-y-3\">\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">Single page (5 items, 10/page)</p>\n <Pagination total={5} itemsPerPage={10} showEdges />\n </div>\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">Few pages (30 items, 10/page)</p>\n <Pagination total={30} itemsPerPage={10} showEdges />\n </div>\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">Many pages (1000 items, 10/page)</p>\n <Pagination total={1000} itemsPerPage={10} page={50} showEdges />\n </div>\n <div>\n <p class=\"mb-1 text-xs text-on-surface-variant\">Zero total</p>\n <Pagination total={0} itemsPerPage={10} />\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Example -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Real World Examples</h2>\n\n <div class=\"space-y-6\">\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Table-style pagination</p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container-low p-4\">\n <div class=\"mb-3 text-sm text-on-surface-variant\">\n Showing <strong class=\"text-on-surface\">41-50</strong> of\n <strong class=\"text-on-surface\">200</strong> results\n </div>\n <Pagination\n total={200}\n itemsPerPage={10}\n page={5}\n showEdges\n size=\"sm\"\n activeColor=\"primary\"\n />\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Minimal blog pagination</p>\n <div class=\"flex items-center justify-center\">\n <Pagination\n total={50}\n itemsPerPage={5}\n page={3}\n showControls={false}\n activeColor=\"surface\"\n ui={{ item: 'rounded-full' }}\n />\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Combined features</p>\n <Pagination\n total={200}\n itemsPerPage={10}\n bind:page={controlledPage}\n activeColor=\"success\"\n size=\"lg\"\n showEdges\n siblingCount={2}\n ui={{\n item: 'rounded-full',\n first: 'rounded-full',\n prev: 'rounded-full',\n next: 'rounded-full',\n last: 'rounded-full',\n list: 'gap-1.5'\n }}\n />\n <p class=\"mt-2 text-xs text-on-surface-variant\">\n Page <strong class=\"text-on-surface\">{controlledPage}</strong> | Size: lg | Color:\n success | Edges | Siblings: 2 | Rounded\n </p>\n </div>\n </div>\n </section>\n</div>\n",
|
|
161
|
+
"menu": "<script lang=\"ts\">\n import { Menu } from '$lib/index.js'\n import type { MenuItemType } from '$lib/index.js'\n \n const menuItems: MenuItemType[] = [\n { type: 'item', label: 'Dashboard', icon: 'lucide:layout-dashboard', active: true },\n { type: 'item', label: 'Analytics', icon: 'lucide:bar-chart' },\n {\n type: 'group',\n label: 'Workspace',\n icon: 'lucide:folder',\n open: true,\n items: [\n { type: 'item', label: 'Projects', icon: 'lucide:briefcase' },\n { type: 'item', label: 'Team Members', icon: 'lucide:users' }\n ]\n },\n { type: 'item', label: 'Settings', icon: 'lucide:settings' }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Menu</h1>\n <p class=\"text-on-surface-variant\">\n A flexible navigation menu system driven by a data array.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass an array of <code class=\"rounded bg-surface-container-highest px-1\">MenuItemType</code> objects to the <code class=\"rounded bg-surface-container-highest px-1\">items</code> prop. Items can be links, buttons, or collapsible groups.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center\">\n <div class=\"w-full max-w-xs bg-surface border border-outline-variant rounded-lg p-2 shadow-sm\">\n <Menu items={menuItems} />\n </div>\n </div>\n </section>\n</div>\n",
|
|
162
|
+
"sidebar": "<script lang=\"ts\">\n import { Sidebar, Button } from '$lib/index.js'\n \n let collapsed = $state(false)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Sidebar</h1>\n <p class=\"text-on-surface-variant\">\n A layout component for application sidebars with built-in collapsible state.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">Sidebar</code> with <code class=\"rounded bg-surface-container-highest px-1\">header</code> and <code class=\"rounded bg-surface-container-highest px-1\">footer</code> snippets. Pass <code class=\"rounded bg-surface-container-highest px-1\">collapsed</code> prop to toggle the expanded state.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex justify-center overflow-hidden\">\n <div class=\"w-full max-w-3xl h-[400px] border border-outline-variant rounded-lg flex overflow-hidden bg-surface-50 dark:bg-surface-900\">\n <Sidebar {collapsed}>\n {#snippet header()}\n <div class=\"p-4 flex items-center justify-between border-b border-outline-variant\">\n {#if !collapsed}\n <span class=\"font-bold text-lg\">Acme Corp</span>\n {/if}\n <Button \n icon={collapsed ? \"lucide:chevron-right\" : \"lucide:chevron-left\"} \n variant=\"ghost\" \n size=\"sm\" \n square \n onclick={() => collapsed = !collapsed} \n class={collapsed ? \"mx-auto\" : \"\"}\n />\n </div>\n {/snippet}\n \n <div class=\"flex-1 overflow-y-auto p-4 space-y-2\">\n {#each Array(4) as _, i}\n <div class=\"h-10 rounded-md bg-surface-container flex items-center px-3\">\n <div class=\"w-5 h-5 bg-surface-300 dark:bg-surface-600 rounded-sm shrink-0\"></div>\n {#if !collapsed}\n <div class=\"ml-3 h-4 bg-surface-300 dark:bg-surface-600 rounded-sm w-24\"></div>\n {/if}\n </div>\n {/each}\n </div>\n \n {#snippet footer()}\n <div class=\"p-4 border-t border-outline-variant flex items-center gap-3\">\n <div class=\"w-8 h-8 rounded-full bg-primary flex items-center justify-center text-primary-content font-bold shrink-0\">\n JD\n </div>\n {#if !collapsed}\n <div class=\"flex-1 overflow-hidden\">\n <div class=\"font-semibold text-sm truncate\">John Doe</div>\n </div>\n {/if}\n </div>\n {/snippet}\n </Sidebar>\n \n <main class=\"flex-1 p-6\">\n <h2 class=\"text-xl font-semibold mb-4\">Main Content Area</h2>\n <p class=\"text-on-surface-variant\">Toggle the sidebar to see it collapse.</p>\n </main>\n </div>\n </div>\n </section>\n</div>\n",
|
|
128
163
|
"stepper": "<script lang=\"ts\">\n import {\n Stepper,\n Badge,\n Button,\n Icon,\n Separator,\n Form,\n FormField,\n Input,\n Textarea,\n Checkbox,\n type StepperItem,\n type StepperApi,\n type FormApi\n } from '$lib/index.js'\n\n const basicItems: StepperItem[] = [\n { value: 'address', title: 'Address', description: 'Enter shipping address' },\n { value: 'shipping', title: 'Shipping', description: 'Pick a carrier' },\n { value: 'payment', title: 'Payment', description: 'Add a card' },\n { value: 'review', title: 'Review', description: 'Confirm and place order' }\n ]\n\n const iconItems: StepperItem[] = [\n { value: 'cart', title: 'Cart', icon: 'lucide:shopping-cart' },\n { value: 'address', title: 'Address', icon: 'lucide:map-pin' },\n { value: 'shipping', title: 'Shipping', icon: 'lucide:truck' },\n { value: 'payment', title: 'Payment', icon: 'lucide:credit-card' },\n { value: 'done', title: 'Done', icon: 'lucide:check' }\n ]\n\n const compactItems: StepperItem[] = [\n { value: 1, title: 'Plan' },\n { value: 2, title: 'Build' },\n { value: 3, title: 'Ship' }\n ]\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n // Controlled with bind:value\n let controlledValue = $state<string | number>('address')\n let lastChange = $state('')\n\n // Imperative API wizard\n let apiValue = $state<string | number>('address')\n let api = $state<StepperApi>()\n\n // Linear vs free\n let linearValue = $state<string | number>('address')\n let freeValue = $state<string | number>('address')\n\n // Custom snippet demos\n let snippetValue = $state<string | number>('shipping')\n\n // -----------------------------------------------------------------\n // Form-driven wizard — each step validates before advancing.\n // -----------------------------------------------------------------\n type WizardData = {\n // step 1\n firstName: string\n lastName: string\n email: string\n // step 2\n address: string\n city: string\n zip: string\n // step 3\n notes: string\n consent: boolean\n }\n\n const wizardSteps: StepperItem[] = [\n {\n value: 'account',\n title: 'Account',\n description: 'Who is ordering',\n icon: 'lucide:user'\n },\n {\n value: 'shipping',\n title: 'Shipping',\n description: 'Where to deliver',\n icon: 'lucide:map-pin'\n },\n {\n value: 'review',\n title: 'Review',\n description: 'Confirm and submit',\n icon: 'lucide:clipboard-check'\n }\n ]\n\n const wizardState = $state<WizardData>({\n firstName: '',\n lastName: '',\n email: '',\n address: '',\n city: '',\n zip: '',\n notes: '',\n consent: false\n })\n\n let wizardValue = $state<string | number>('account')\n let wizardApi = $state<StepperApi>()\n let wizardForm = $state<FormApi<unknown>>()\n let wizardSubmitted = $state<string | null>(null)\n\n const STEP_FIELDS: Record<string, (keyof WizardData)[]> = {\n account: ['firstName', 'lastName', 'email'],\n shipping: ['address', 'city', 'zip'],\n review: ['consent']\n }\n\n function validateRequired(field: keyof WizardData) {\n const value = wizardState[field]\n if (typeof value === 'boolean') return value ? null : 'Required'\n if (typeof value === 'string' && value.trim().length === 0) return 'Required'\n if (field === 'email' && typeof value === 'string' && !/.+@.+\\..+/.test(value)) {\n return 'Invalid email'\n }\n return null\n }\n\n function validateCurrentStep(): boolean {\n const fields = STEP_FIELDS[String(wizardValue)] ?? []\n const errors: { name: string; message: string }[] = []\n for (const f of fields) {\n const err = validateRequired(f)\n if (err) errors.push({ name: String(f), message: err })\n else wizardForm?.clear(String(f))\n }\n if (errors.length > 0) {\n wizardForm?.setErrors(errors)\n return false\n }\n return true\n }\n\n function handleWizardNext() {\n if (!validateCurrentStep()) return\n wizardApi?.next()\n }\n\n function handleWizardSubmit() {\n if (!validateCurrentStep()) return\n wizardSubmitted = JSON.stringify(wizardState, null, 2)\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Stepper</h1>\n <p class=\"text-on-surface-variant\">\n A wizard-style progress indicator for multi-step flows. Renders a sequential list of\n steps with <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >pending</code\n >\n /\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">active</code> /\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">completed</code\n >\n states. Pure custom build — no bits-ui primitive.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Pass <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >items</code\n >\n and the Stepper renders progress with sensible defaults.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <Stepper items={basicItems} />\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Switch between\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >horizontal</code\n >\n (default) and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">vertical</code> layout.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-4 text-sm font-medium\">Horizontal</p>\n <Stepper items={basicItems} value=\"shipping\" content={false} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-4 text-sm font-medium\">Vertical</p>\n <Stepper\n items={basicItems}\n orientation=\"vertical\"\n value=\"shipping\"\n content={false}\n />\n </div>\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Five sizes scale indicator and typography together.\n </p>\n <div class=\"space-y-4\">\n {#each ['xs', 'sm', 'md', 'lg', 'xl'] as const as size (size)}\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-xs font-medium tracking-wide uppercase\">{size}</p>\n <Stepper items={compactItems} value={2} {size} content={false} linear={false} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code\n >\n prop tints the indicator, separator, and active title.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n {#each colors as color (color)}\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-xs font-medium capitalize\">{color}</p>\n <Stepper\n items={compactItems}\n value={2}\n {color}\n content={false}\n linear={false}\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code>\n on an item to replace the default number indicator. Completed steps automatically show a check\n icon when no\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code> is set.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Per-item icons (horizontal)</p>\n <Stepper items={iconItems} value=\"shipping\" content={false} linear={false} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Per-item icons (vertical)</p>\n <Stepper\n items={iconItems}\n value=\"shipping\"\n content={false}\n orientation=\"vertical\"\n linear={false}\n />\n </div>\n </div>\n </section>\n\n <!-- Linear vs Free -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Linear vs Free Navigation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n By default Stepper is <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">linear</code\n >: a click can only advance one step ahead. Set\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >linear={'{false}'}</code\n >\n to allow jumping anywhere.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">\n Linear (default) · Current: <Badge variant=\"soft\" label={String(linearValue)} />\n </p>\n <Stepper items={basicItems} bind:value={linearValue} content={false} />\n <p class=\"mt-3 text-xs text-on-surface-variant\">\n Click any step — only the immediate next one (and prior ones) responds.\n </p>\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">\n Free · Current: <Badge variant=\"soft\" color=\"info\" label={String(freeValue)} />\n </p>\n <Stepper items={basicItems} bind:value={freeValue} content={false} linear={false} />\n <p class=\"mt-3 text-xs text-on-surface-variant\">\n Click any step to jump there directly.\n </p>\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">All steps disabled</p>\n <Stepper items={basicItems} disabled content={false} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Per-item disabled</p>\n <Stepper\n items={[\n { value: 'a', title: 'Open', description: 'Free' },\n {\n value: 'b',\n title: 'Locked',\n description: 'Premium only',\n disabled: true\n },\n { value: 'c', title: 'Open', description: 'Free' }\n ]}\n linear={false}\n content={false}\n />\n </div>\n </div>\n </section>\n\n <!-- Imperative API -->\n <section class=\"space-y-3\">\n <div class=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h2 class=\"text-lg font-semibold\">Imperative API — bind:api</h2>\n <p class=\"text-xs text-on-surface-variant\">\n Drive the Stepper from outside with Back / Next buttons.\n </p>\n </div>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <Stepper items={basicItems} bind:api bind:value={apiValue}>\n {#snippet body({ item })}\n <div\n class=\"flex items-start gap-3 rounded-lg border border-outline-variant bg-surface-container p-4\"\n >\n <Icon name=\"lucide:info\" size=\"18\" class=\"mt-0.5 shrink-0 text-primary\" />\n <div>\n <p class=\"text-sm font-medium\">{item.title}</p>\n <p class=\"text-sm text-on-surface-variant\">\n {item.description}\n </p>\n </div>\n </div>\n {/snippet}\n </Stepper>\n <div class=\"flex flex-wrap items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n leadingIcon=\"lucide:chevron-left\"\n label=\"Back\"\n disabled={!api?.hasPrev}\n onclick={() => api?.prev()}\n />\n <Button\n color=\"primary\"\n size=\"sm\"\n trailingIcon=\"lucide:chevron-right\"\n label={api?.hasNext ? 'Next' : 'Done'}\n disabled={!api?.hasNext}\n onclick={() => api?.next()}\n />\n <span class=\"ms-auto text-xs text-on-surface-variant\">\n Step {(api?.activeIndex ?? 0) + 1} of {basicItems.length}\n </span>\n </div>\n </div>\n </section>\n\n <!-- Controlled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Controlled — bind:value</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Two-way bind to drive Stepper from any UI; useful when navigation lives elsewhere on the\n page.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-6\">\n <p class=\"text-sm font-medium\">\n Active: <Badge variant=\"soft\" color=\"info\" label={String(controlledValue)} />\n </p>\n <div class=\"flex flex-wrap gap-2\">\n {#each basicItems as item (item.value)}\n <Button\n size=\"xs\"\n variant={controlledValue === item.value ? 'solid' : 'outline'}\n label={item.title}\n onclick={() => (controlledValue = item.value ?? '')}\n />\n {/each}\n </div>\n <Stepper items={basicItems} bind:value={controlledValue} content={false} />\n </div>\n </section>\n\n <!-- Callback -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">onValueChange Callback</h2>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm\">\n Last change:\n <Badge\n variant=\"soft\"\n color={lastChange ? 'success' : 'surface'}\n label={lastChange || 'None'}\n />\n </p>\n <Stepper\n items={basicItems}\n onValueChange={(v) => (lastChange = `Switched to: ${v}`)}\n content={false}\n />\n </div>\n </section>\n\n <!-- Custom Snippets -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Snippets</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">indicator</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">titleSlot</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >descriptionSlot</code\n >, or\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">body</code> for custom\n rendering.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Custom indicator</p>\n <Stepper items={basicItems} value=\"shipping\" content={false} linear={false}>\n {#snippet indicator({ number, state })}\n <span\n class=\"inline-flex size-8 items-center justify-center rounded-full text-sm font-bold {state ===\n 'completed'\n ? 'bg-success text-on-success'\n : state === 'active'\n ? 'bg-primary text-on-primary ring-4 ring-primary/20'\n : 'border-2 border-dashed border-outline-variant text-on-surface-variant'}\"\n aria-hidden=\"true\"\n >\n {#if state === 'completed'}\n <Icon name=\"lucide:check\" size=\"16\" />\n {:else}\n {number}\n {/if}\n </span>\n {/snippet}\n </Stepper>\n </div>\n\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Custom title with badge</p>\n <Stepper items={basicItems} value=\"shipping\" content={false} linear={false}>\n {#snippet titleSlot({ item, state })}\n <span class=\"inline-flex items-center gap-1.5 text-sm font-medium\">\n {item.title}\n {#if state === 'active'}\n <Badge size=\"xs\" variant=\"soft\" color=\"primary\" label=\"Now\" />\n {:else if state === 'completed'}\n <Badge size=\"xs\" variant=\"soft\" color=\"success\" label=\"Done\" />\n {/if}\n </span>\n {/snippet}\n </Stepper>\n </div>\n\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Custom body</p>\n <Stepper items={basicItems} bind:value={snippetValue}>\n {#snippet body({ item, number })}\n <div\n class=\"flex items-start gap-3 rounded-lg border border-outline-variant bg-surface-container p-4\"\n >\n <div\n class=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary\"\n >\n <Icon name=\"lucide:sparkles\" size=\"20\" />\n </div>\n <div>\n <p class=\"text-sm font-semibold\">\n Step {number}: {item.title}\n </p>\n <p class=\"text-sm text-on-surface-variant\">\n Body snippet renders anywhere below the progress bar.\n </p>\n </div>\n </div>\n {/snippet}\n </Stepper>\n </div>\n\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Custom description</p>\n <Stepper items={basicItems} value=\"shipping\" content={false} linear={false}>\n {#snippet descriptionSlot({ item, state })}\n <span\n class=\"text-xs {state === 'pending'\n ? 'text-on-surface-variant/60 italic'\n : 'text-on-surface-variant'}\"\n >\n {state === 'completed' ? '✓ ' : ''}{item.description}\n </span>\n {/snippet}\n </Stepper>\n </div>\n </div>\n </section>\n\n <!-- UI Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override slot styles via the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code> prop,\n or per-item via\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >items[i].ui</code\n >.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Square indicators</p>\n <Stepper\n items={compactItems}\n value={2}\n content={false}\n linear={false}\n ui={{ indicator: 'rounded-md' }}\n />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Thicker separator</p>\n <Stepper\n items={compactItems}\n value={2}\n content={false}\n linear={false}\n ui={{ separator: 'h-1' }}\n />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Bold titles</p>\n <Stepper\n items={basicItems}\n value=\"shipping\"\n content={false}\n linear={false}\n ui={{ title: 'font-bold uppercase tracking-wider' }}\n />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <p class=\"mb-3 text-sm font-medium\">Per-item override</p>\n <Stepper\n items={[\n { value: 'a', title: 'Standard', description: 'Normal item' },\n {\n value: 'b',\n title: 'Spotlight',\n description: 'Highlighted',\n ui: { indicator: 'ring-4 ring-warning/40' }\n },\n { value: 'c', title: 'Standard', description: 'Normal item' }\n ]}\n value=\"b\"\n content={false}\n linear={false}\n />\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Example: Form Wizard -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World — Form Wizard</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Multi-step form with per-step validation. The Next button calls a custom validator\n before invoking\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >api.next()</code\n >.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <Stepper\n items={wizardSteps}\n bind:api={wizardApi}\n bind:value={wizardValue}\n color=\"primary\"\n >\n {#snippet body({ item })}\n <Form\n bind:api={wizardForm}\n state={wizardState}\n onsubmit={handleWizardSubmit}\n class=\"space-y-4 rounded-lg border border-outline-variant bg-surface-container p-5\"\n >\n {#if item.value === 'account'}\n <div class=\"space-y-4\">\n <h3 class=\"text-base font-semibold\">Tell us about yourself</h3>\n <div class=\"grid gap-3 sm:grid-cols-2\">\n <FormField name=\"firstName\" label=\"First name\" required>\n <Input bind:value={wizardState.firstName} />\n </FormField>\n <FormField name=\"lastName\" label=\"Last name\" required>\n <Input bind:value={wizardState.lastName} />\n </FormField>\n </div>\n <FormField name=\"email\" label=\"Email\" required>\n <Input type=\"email\" bind:value={wizardState.email} />\n </FormField>\n </div>\n {:else if item.value === 'shipping'}\n <div class=\"space-y-4\">\n <h3 class=\"text-base font-semibold\">Shipping address</h3>\n <FormField name=\"address\" label=\"Street address\" required>\n <Input bind:value={wizardState.address} />\n </FormField>\n <div class=\"grid gap-3 sm:grid-cols-2\">\n <FormField name=\"city\" label=\"City\" required>\n <Input bind:value={wizardState.city} />\n </FormField>\n <FormField name=\"zip\" label=\"ZIP\" required>\n <Input bind:value={wizardState.zip} />\n </FormField>\n </div>\n <FormField name=\"notes\" label=\"Delivery notes (optional)\">\n <Textarea bind:value={wizardState.notes} rows={2} />\n </FormField>\n </div>\n {:else}\n <div class=\"space-y-4\">\n <h3 class=\"text-base font-semibold\">Review</h3>\n <div\n class=\"space-y-2 rounded-lg bg-surface-container-highest p-3 text-sm\"\n >\n <div>\n <span class=\"text-on-surface-variant\">Name:</span>\n <span class=\"font-medium\"\n >{wizardState.firstName}\n {wizardState.lastName}</span\n >\n </div>\n <div>\n <span class=\"text-on-surface-variant\">Email:</span>\n <span class=\"font-medium\">{wizardState.email}</span>\n </div>\n <div>\n <span class=\"text-on-surface-variant\">Address:</span>\n <span class=\"font-medium\"\n >{wizardState.address}, {wizardState.city}\n {wizardState.zip}</span\n >\n </div>\n {#if wizardState.notes}\n <div>\n <span class=\"text-on-surface-variant\">Notes:</span>\n <span class=\"font-medium\">{wizardState.notes}</span>\n </div>\n {/if}\n </div>\n <FormField name=\"consent\" required>\n <label class=\"flex items-start gap-2 text-sm\">\n <Checkbox bind:checked={wizardState.consent} />\n I confirm the details above are correct.\n </label>\n </FormField>\n </div>\n {/if}\n\n <div class=\"flex flex-wrap items-center gap-2 pt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n leadingIcon=\"lucide:chevron-left\"\n label=\"Back\"\n disabled={!wizardApi?.hasPrev}\n onclick={() => wizardApi?.prev()}\n />\n {#if wizardApi?.hasNext}\n <Button\n color=\"primary\"\n size=\"sm\"\n trailingIcon=\"lucide:chevron-right\"\n label=\"Next\"\n onclick={handleWizardNext}\n />\n {:else}\n <Button\n color=\"success\"\n size=\"sm\"\n leadingIcon=\"lucide:check\"\n label=\"Submit\"\n type=\"submit\"\n />\n {/if}\n <span class=\"ms-auto text-xs text-on-surface-variant\">\n Step {(wizardApi?.activeIndex ?? 0) + 1} of {wizardSteps.length}\n </span>\n </div>\n </Form>\n {/snippet}\n </Stepper>\n\n {#if wizardSubmitted}\n <div class=\"mt-4 rounded-lg border border-success/40 bg-success/10 p-4 text-sm\">\n <div class=\"mb-2 flex items-center gap-2 font-semibold text-success\">\n <Icon name=\"lucide:party-popper\" size=\"18\" />\n Submitted!\n </div>\n <pre\n class=\"overflow-x-auto text-xs whitespace-pre-wrap text-on-surface-variant\">{wizardSubmitted}</pre>\n </div>\n {/if}\n </div>\n </section>\n\n <!-- Real World Example: Onboarding -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World — Onboarding Checklist</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Vertical Stepper with rich descriptions and inline actions per step.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-6\">\n <Stepper\n items={[\n {\n value: 'profile',\n title: 'Complete your profile',\n description: 'Add a photo and bio so teammates recognize you.',\n icon: 'lucide:user-circle'\n },\n {\n value: 'team',\n title: 'Invite your team',\n description: 'Bring teammates so you can collaborate from day one.',\n icon: 'lucide:users'\n },\n {\n value: 'integrate',\n title: 'Connect your tools',\n description: 'GitHub, Slack, Linear — wire everything in one click.',\n icon: 'lucide:plug'\n },\n {\n value: 'done',\n title: 'You are ready',\n description: 'Explore the dashboard or start your first project.',\n icon: 'lucide:rocket'\n }\n ]}\n orientation=\"vertical\"\n value=\"team\"\n color=\"success\"\n linear={false}\n >\n {#snippet body({ item, state, active })}\n {#if active}\n <div\n class=\"mt-2 flex items-center gap-2 rounded-lg border border-outline-variant bg-surface-container p-3\"\n >\n <Icon name={item.icon ?? 'lucide:zap'} size=\"18\" class=\"text-primary\" />\n <span class=\"text-sm\">Continue with this step</span>\n <Button\n class=\"ms-auto\"\n size=\"xs\"\n color=\"primary\"\n label={state === 'completed' ? 'Revisit' : 'Continue'}\n />\n </div>\n {/if}\n {/snippet}\n </Stepper>\n </div>\n </section>\n</div>\n",
|
|
129
164
|
"tabs": "<script lang=\"ts\">\n import { Tabs, Badge, Button, Icon, Separator } from '$lib/index.js'\n import type { TabsItem } from '$lib/index.js'\n\n const basicItems: TabsItem[] = [\n {\n label: 'Account',\n content: 'Manage your account settings and preferences.',\n value: 'account'\n },\n {\n label: 'Password',\n content: 'Change your password and security keys.',\n value: 'password'\n },\n {\n label: 'Notifications',\n content: 'Configure notification preferences.',\n value: 'notifications'\n }\n ]\n\n const iconItems: TabsItem[] = [\n {\n label: 'Profile',\n icon: 'lucide:user',\n content: 'Update your profile information.',\n value: 'profile'\n },\n {\n label: 'Security',\n icon: 'lucide:shield',\n content: 'Manage security settings.',\n value: 'security'\n },\n {\n label: 'Billing',\n icon: 'lucide:credit-card',\n content: 'View invoices and payments.',\n value: 'billing'\n },\n {\n label: 'Integrations',\n icon: 'lucide:plug',\n content: 'Connect third-party services.',\n value: 'integrations'\n }\n ]\n\n const disabledItems: TabsItem[] = [\n { label: 'General', content: 'General settings.', value: 'general' },\n { label: 'Advanced', content: 'Requires premium.', value: 'advanced', disabled: true },\n { label: 'About', content: 'Version and license info.', value: 'about' }\n ]\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n\n let controlledValue = $state('account')\n let lastChange = $state('')\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Tabs</h1>\n <p class=\"text-on-surface-variant\">\n A set of layered panels of content, where only one panel is visible at a time. Built on\n bits-ui Tabs primitive.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Tabs items={basicItems} />\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >variant</code\n >\n to switch between pill and link styles.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n {#each [{ variant: 'pill' as const, label: 'Pill (default)' }, { variant: 'link' as const, label: 'Link' }] as item (item.variant)}\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">{item.label}</p>\n <Tabs items={basicItems} variant={item.variant} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">color</code\n >\n prop controls the indicator and active text color.\n </p>\n <div class=\"space-y-4\">\n {#each ['pill', 'link'] as variant (variant)}\n <div>\n <p class=\"mb-3 text-sm font-medium capitalize\">{variant} variant</p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n {#each colors as color (color)}\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-2 text-xs font-medium capitalize\">{color}</p>\n <Tabs\n items={basicItems}\n {color}\n variant={variant as 'pill' | 'link'}\n content={false}\n />\n </div>\n {/each}\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"space-y-4\">\n {#each ['xs', 'sm', 'md', 'lg', 'xl'] as const as size (size)}\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-2 text-xs font-medium uppercase\">{size}</p>\n <Tabs items={basicItems} {size} content={false} />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- With Icons -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Add leading icons via the <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">icon</code\n >\n property on each item.\n </p>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Pill</p>\n <Tabs items={iconItems} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Link</p>\n <Tabs items={iconItems} variant=\"link\" />\n </div>\n </div>\n </section>\n\n <!-- Orientation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Orientation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >orientation=\"vertical\"</code\n >\n for vertical layout.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Vertical - Pill</p>\n <Tabs items={iconItems} orientation=\"vertical\" />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Vertical - Link</p>\n <Tabs items={iconItems} orientation=\"vertical\" variant=\"link\" />\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <div class=\"grid gap-4 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">All disabled</p>\n <Tabs items={basicItems} disabled />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Individual disabled</p>\n <Tabs items={disabledItems} />\n </div>\n </div>\n </section>\n\n <!-- Content -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">No Content</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Set <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >content={'{false}'}</code\n >\n to use tabs purely for navigation.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Tabs\n items={[\n { label: 'Overview', value: 'overview' },\n { label: 'Tasks', value: 'tasks' },\n { label: 'Files', value: 'files' },\n { label: 'Members', value: 'members' },\n { label: 'Settings', value: 'settings' },\n { label: 'Activity', value: 'activity' }\n ]}\n content={false}\n />\n </div>\n </section>\n\n <!-- Controlled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Controlled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Bind <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >value</code\n >\n to control the active tab programmatically.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">\n Active: <Badge variant=\"soft\" color=\"info\" label={controlledValue} />\n </p>\n <div class=\"mb-3 flex gap-2\">\n {#each basicItems as item (item.value)}\n <Button\n size=\"xs\"\n variant={controlledValue === item.value ? 'solid' : 'outline'}\n label={item.label}\n onclick={() => (controlledValue = item.value ?? '')}\n />\n {/each}\n </div>\n <Tabs items={basicItems} bind:value={controlledValue} />\n </div>\n </section>\n\n <!-- Callback -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Value Change Callback</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >onValueChange</code\n >\n to react to tab changes.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm\">\n Last change: <Badge\n variant=\"soft\"\n color={lastChange ? 'success' : 'surface'}\n label={lastChange || 'None'}\n />\n </p>\n <Tabs items={basicItems} onValueChange={(v) => (lastChange = `Switched to: ${v}`)} />\n </div>\n </section>\n\n <!-- Custom Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use snippets for custom rendering:\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">leading</code>,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">label</code>,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">trailing</code\n >,\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">body</code>.\n </p>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom leading</p>\n <Tabs items={basicItems} variant=\"link\">\n {#snippet leading({ active })}\n <div\n class=\"flex size-6 items-center justify-center rounded-full {active\n ? 'bg-primary text-on-primary'\n : 'bg-surface-container-highest'}\"\n >\n <Icon name={active ? 'lucide:check' : 'lucide:circle'} size=\"14\" />\n </div>\n {/snippet}\n </Tabs>\n </div>\n\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom label with badge</p>\n <Tabs items={basicItems}>\n {#snippet label({ item })}\n <span class=\"flex items-center gap-1.5\">\n {item.label}\n {#if item.value === 'account'}\n <Badge size=\"xs\" variant=\"soft\" color=\"primary\" label=\"New\" />\n {/if}\n </span>\n {/snippet}\n </Tabs>\n </div>\n\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom trailing</p>\n <Tabs items={basicItems} variant=\"link\">\n {#snippet trailing({ item })}\n {#if item.value === 'notifications'}\n <Badge size=\"xs\" variant=\"soft\" color=\"error\" label=\"3\" />\n {/if}\n {/snippet}\n </Tabs>\n </div>\n\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-3 text-sm font-medium\">Custom body</p>\n <Tabs items={basicItems}>\n {#snippet body({ item })}\n <div class=\"flex items-start gap-3 rounded-lg bg-surface-container p-4\">\n <Icon\n name=\"lucide:info\"\n size=\"18\"\n class=\"mt-0.5 shrink-0 text-primary\"\n />\n <div>\n <p class=\"text-sm font-medium\">{item.label}</p>\n <p class=\"text-sm text-on-surface-variant\">{item.content}</p>\n </div>\n </div>\n {/snippet}\n </Tabs>\n </div>\n </div>\n </section>\n\n <!-- Per-Item Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Per-Item Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Each item supports\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">class</code>\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code>\n overrides.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Tabs\n items={[\n { label: 'Normal', content: 'Standard styling.', value: 'normal' },\n { label: 'Bold', content: 'Custom class.', value: 'bold', class: 'font-bold' },\n {\n label: 'Italic',\n content: 'Custom ui.',\n value: 'italic',\n ui: { trigger: 'italic' }\n }\n ]}\n variant=\"link\"\n />\n </div>\n </section>\n\n <!-- UI Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Overrides</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Override slot styles via the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">ui</code> prop.\n </p>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">\n Custom list background\n </p>\n <Tabs\n items={basicItems}\n ui={{ list: 'bg-surface-container-highest rounded-xl' }}\n content={false}\n />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">Rounded indicator</p>\n <Tabs items={basicItems} ui={{ indicator: 'rounded-full' }} content={false} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">Bold triggers</p>\n <Tabs\n items={basicItems}\n ui={{ trigger: 'font-bold uppercase tracking-wide' }}\n content={false}\n />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <p class=\"mb-2 text-sm font-medium text-on-surface-variant\">Custom content</p>\n <Tabs\n items={basicItems}\n ui={{\n content:\n 'mt-3 p-4 bg-surface-container rounded-lg border border-outline-variant'\n }}\n />\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <div class=\"space-y-4\">\n <!-- Settings Page -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Settings Page</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"mb-4\">\n <h3 class=\"font-semibold text-on-surface\">Settings</h3>\n <p class=\"text-sm text-on-surface-variant\">\n Manage your account settings and preferences.\n </p>\n </div>\n <Tabs items={iconItems} variant=\"link\" color=\"primary\">\n {#snippet body({ item })}\n <div class=\"space-y-4 rounded-lg bg-surface-container p-4\">\n {#if item.value === 'profile'}\n <div class=\"flex items-center gap-4\">\n <div\n class=\"flex size-16 items-center justify-center rounded-full bg-primary/10\"\n >\n <Icon\n name=\"lucide:user\"\n size=\"32\"\n class=\"text-primary\"\n />\n </div>\n <div class=\"flex-1\">\n <h4 class=\"font-semibold\">John Doe</h4>\n <p class=\"text-sm text-on-surface-variant\">\n john.doe@example.com\n </p>\n </div>\n <Button\n size=\"sm\"\n variant=\"outline\"\n leadingIcon=\"lucide:pencil\"\n label=\"Edit\"\n />\n </div>\n {:else if item.value === 'security'}\n <div class=\"space-y-3\">\n <div\n class=\"flex items-center justify-between rounded-lg bg-surface-container-highest p-3\"\n >\n <div class=\"flex items-center gap-3\">\n <Icon\n name=\"lucide:shield-check\"\n size=\"20\"\n class=\"text-success\"\n />\n <div>\n <p class=\"text-sm font-medium\">\n Two-Factor Authentication\n </p>\n <p class=\"text-xs text-on-surface-variant\">\n Add an extra layer of security\n </p>\n </div>\n </div>\n <Badge\n variant=\"soft\"\n color=\"success\"\n size=\"xs\"\n label=\"Enabled\"\n />\n </div>\n <div\n class=\"flex items-center justify-between rounded-lg bg-surface-container-highest p-3\"\n >\n <div class=\"flex items-center gap-3\">\n <Icon\n name=\"lucide:key\"\n size=\"20\"\n class=\"text-warning\"\n />\n <div>\n <p class=\"text-sm font-medium\">Password</p>\n <p class=\"text-xs text-on-surface-variant\">\n Last changed 30 days ago\n </p>\n </div>\n </div>\n <Button size=\"xs\" variant=\"outline\" label=\"Change\" />\n </div>\n </div>\n {:else if item.value === 'billing'}\n <div class=\"grid grid-cols-3 gap-3\">\n {#each [{ value: 'Pro', label: 'Current Plan', color: 'text-primary' }, { value: '$29', label: 'Monthly', color: 'text-success' }, { value: 'Mar 15', label: 'Next Billing', color: 'text-on-surface' }] as stat (stat.label)}\n <div\n class=\"rounded-lg bg-surface-container-highest p-3 text-center\"\n >\n <p class=\"text-2xl font-bold {stat.color}\">\n {stat.value}\n </p>\n <p class=\"text-xs text-on-surface-variant\">\n {stat.label}\n </p>\n </div>\n {/each}\n </div>\n {:else}\n <div class=\"space-y-2\">\n {#each [{ name: 'GitHub', icon: 'lucide:github', connected: true }, { name: 'Slack', icon: 'lucide:slack', connected: false }] as svc (svc.name)}\n <div\n class=\"flex items-center justify-between rounded-lg bg-surface-container-highest p-3\"\n >\n <div class=\"flex items-center gap-3\">\n <Icon name={svc.icon} size=\"20\" />\n <span class=\"text-sm font-medium\"\n >{svc.name}</span\n >\n </div>\n {#if svc.connected}\n <Badge\n variant=\"soft\"\n color=\"success\"\n size=\"xs\"\n label=\"Connected\"\n />\n {:else}\n <Button\n size=\"xs\"\n variant=\"outline\"\n label=\"Connect\"\n />\n {/if}\n </div>\n {/each}\n </div>\n {/if}\n </div>\n {/snippet}\n </Tabs>\n </div>\n </div>\n\n <!-- Dashboard -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Dashboard Tabs</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Tabs\n items={[\n {\n label: 'Overview',\n icon: 'lucide:layout-dashboard',\n value: 'overview'\n },\n { label: 'Analytics', icon: 'lucide:bar-chart-3', value: 'analytics' },\n { label: 'Reports', icon: 'lucide:file-text', value: 'reports' }\n ]}\n color=\"primary\"\n >\n {#snippet body({ item })}\n {#if item.value === 'overview'}\n <div class=\"grid gap-3 sm:grid-cols-3\">\n {#each [{ label: 'Users', value: '12,345', change: '+12.5%', icon: 'lucide:users', iconColor: 'text-primary', changeColor: 'text-success' }, { label: 'Revenue', value: '$48,290', change: '+8.2%', icon: 'lucide:dollar-sign', iconColor: 'text-success', changeColor: 'text-success' }, { label: 'Orders', value: '1,890', change: '-3.1%', icon: 'lucide:shopping-cart', iconColor: 'text-warning', changeColor: 'text-error' }] as stat (stat.label)}\n <div class=\"rounded-lg bg-surface-container p-4\">\n <div class=\"flex items-center gap-2\">\n <Icon\n name={stat.icon}\n size=\"18\"\n class={stat.iconColor}\n />\n <span class=\"text-sm text-on-surface-variant\"\n >{stat.label}</span\n >\n </div>\n <p class=\"mt-2 text-2xl font-bold\">{stat.value}</p>\n <p class=\"text-xs {stat.changeColor}\">\n {stat.change} from last month\n </p>\n </div>\n {/each}\n </div>\n {:else if item.value === 'analytics'}\n <div\n class=\"flex flex-col items-center justify-center rounded-lg bg-surface-container p-8\"\n >\n <Icon\n name=\"lucide:bar-chart-3\"\n size=\"48\"\n class=\"text-on-surface-variant/30\"\n />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n Analytics charts would be rendered here\n </p>\n </div>\n {:else}\n <div class=\"space-y-2\">\n {#each ['Monthly Revenue Report', 'User Growth Report', 'Conversion Funnel Report'] as report (report)}\n <div\n class=\"flex items-center justify-between rounded-lg bg-surface-container p-3\"\n >\n <div class=\"flex items-center gap-3\">\n <Icon\n name=\"lucide:file-text\"\n size=\"18\"\n class=\"text-on-surface-variant\"\n />\n <span class=\"text-sm\">{report}</span>\n </div>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n leadingIcon=\"lucide:download\"\n label=\"Download\"\n />\n </div>\n {/each}\n </div>\n {/if}\n {/snippet}\n </Tabs>\n </div>\n </div>\n\n <!-- Vertical Settings -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Vertical Settings</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Tabs\n items={[\n {\n label: 'General',\n icon: 'lucide:settings',\n value: 'general',\n content:\n 'Configure general application settings like language, timezone, and display preferences.'\n },\n {\n label: 'Appearance',\n icon: 'lucide:palette',\n value: 'appearance',\n content:\n 'Customize the look and feel including themes, fonts, and layout.'\n },\n {\n label: 'Privacy',\n icon: 'lucide:lock',\n value: 'privacy',\n content:\n 'Control your privacy settings and data sharing preferences.'\n },\n {\n label: 'Notifications',\n icon: 'lucide:bell',\n value: 'notifications',\n content: 'Manage email, push, and in-app notification preferences.'\n }\n ]}\n orientation=\"vertical\"\n color=\"primary\"\n />\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
130
165
|
"collapsible": "<script lang=\"ts\">\n import { Collapsible, Button, Icon, Badge, Separator } from '$lib/index.js'\n\n let basicOpen = $state(false)\n let controlledOpen = $state(true)\n</script>\n\n<div class=\"mx-auto max-w-3xl space-y-12 p-8\">\n <div>\n <h1 class=\"text-2xl font-bold\">Collapsible</h1>\n <p class=\"mt-1 text-on-surface-variant\">\n An interactive component that expands/collapses content.\n </p>\n </div>\n\n <Separator />\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <Collapsible>\n {#snippet trigger({ open, props })}\n <Button\n {...props}\n variant=\"subtle\"\n trailingIcon={open ? 'lucide:chevron-up' : 'lucide:chevron-down'}\n >\n {open ? 'Hide' : 'Show'} content\n </Button>\n {/snippet}\n {#snippet content()}\n <div class=\"mt-2 rounded-lg border border-outline-variant p-4 text-sm\">\n <p>This is the collapsible content. It can contain any elements you need.</p>\n </div>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- Initially Open -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Initially Open</h2>\n <Collapsible open>\n {#snippet trigger({ open, props })}\n <Button\n {...props}\n variant=\"outline\"\n trailingIcon={open ? 'lucide:minus' : 'lucide:plus'}\n >\n Toggle section\n </Button>\n {/snippet}\n {#snippet content()}\n <div class=\"mt-2 rounded-lg bg-surface-container p-4 text-sm\">\n <p>This section is open by default when the page loads.</p>\n </div>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- Controlled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Controlled (bind:open)</h2>\n <div class=\"flex items-center gap-2\">\n <Badge\n label={controlledOpen ? 'Open' : 'Closed'}\n color={controlledOpen ? 'success' : 'error'}\n />\n <Button variant=\"ghost\" size=\"xs\" onclick={() => (controlledOpen = !controlledOpen)}>\n Toggle externally\n </Button>\n </div>\n <Collapsible bind:open={controlledOpen}>\n {#snippet trigger({ open, props })}\n <Button\n {...props}\n variant=\"soft\"\n color=\"primary\"\n trailingIcon={open ? 'lucide:chevron-up' : 'lucide:chevron-down'}\n >\n Controlled collapsible\n </Button>\n {/snippet}\n {#snippet content()}\n <div\n class=\"mt-2 rounded-lg border border-primary/30 bg-primary-container/20 p-4 text-sm\"\n >\n <p>\n This collapsible is controlled externally via <code\n class=\"rounded bg-surface-container px-1\">bind:open</code\n >.\n </p>\n </div>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- onOpenChange callback -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">onOpenChange Callback</h2>\n <!-- eslint-disable-next-line no-console -->\n <Collapsible bind:open={basicOpen} onOpenChange={(v) => console.log('open changed:', v)}>\n {#snippet trigger({ open, props })}\n <Button\n {...props}\n variant=\"outline\"\n color=\"secondary\"\n trailingIcon={open ? 'lucide:eye-off' : 'lucide:eye'}\n >\n {open ? 'Collapse' : 'Expand'} (check console)\n </Button>\n {/snippet}\n {#snippet content()}\n <div class=\"mt-2 rounded-lg border border-outline-variant p-4 text-sm\">\n <p>\n Open the console to see the <code class=\"rounded bg-surface-container px-1\"\n >onOpenChange</code\n > callback firing.\n </p>\n </div>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <Collapsible disabled>\n {#snippet trigger({ props })}\n <Button {...props} variant=\"outline\" disabled trailingIcon=\"lucide:chevron-down\">\n Cannot toggle (disabled)\n </Button>\n {/snippet}\n {#snippet content()}\n <div class=\"mt-2 rounded-lg border border-outline-variant p-4 text-sm\">\n <p>You should never see this content.</p>\n </div>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- Custom trigger (non-Button) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Trigger</h2>\n <Collapsible>\n {#snippet trigger({ open, props })}\n <div\n {...props}\n class=\"flex cursor-pointer items-center gap-2 rounded-lg border border-outline-variant px-4 py-3 transition-colors hover:bg-surface-container\"\n >\n <Icon name=\"lucide:settings\" class=\"size-5\" />\n <span class=\"flex-1 text-sm font-medium\">Advanced Settings</span>\n <Icon\n name=\"lucide:chevron-down\"\n class=\"size-4 transition-transform duration-200 {open ? 'rotate-180' : ''}\"\n />\n </div>\n {/snippet}\n {#snippet content()}\n <div class=\"mt-1 space-y-3 rounded-lg border border-outline-variant p-4\">\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm\">Enable notifications</span>\n <Badge label=\"On\" color=\"success\" variant=\"soft\" />\n </div>\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm\">Auto-save drafts</span>\n <Badge label=\"Off\" color=\"error\" variant=\"soft\" />\n </div>\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm\">Dark mode</span>\n <Badge label=\"System\" color=\"info\" variant=\"soft\" />\n </div>\n </div>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- Custom UI Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom UI Slots</h2>\n <Collapsible\n ui={{\n root: 'rounded-xl border border-outline-variant p-4',\n content: 'mt-3 border-t border-outline-variant pt-3'\n }}\n >\n {#snippet trigger({ open, props })}\n <Button\n {...props}\n variant=\"ghost\"\n size=\"sm\"\n trailingIcon={open ? 'lucide:chevron-up' : 'lucide:chevron-down'}\n >\n Styled with ui prop\n </Button>\n {/snippet}\n {#snippet content()}\n <p class=\"text-sm text-on-surface-variant\">\n The root has a rounded border and padding. The content has a top border\n separator.\n </p>\n {/snippet}\n </Collapsible>\n </section>\n\n <Separator />\n\n <!-- Multiple collapsibles -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Multiple Collapsibles</h2>\n <div class=\"divide-y divide-outline-variant rounded-lg border border-outline-variant\">\n {#each ['Getting Started', 'Installation', 'Configuration'] as title (title)}\n <Collapsible ui={{ root: 'px-4' }}>\n {#snippet trigger({ open, props })}\n <div\n {...props}\n class=\"flex w-full cursor-pointer items-center justify-between py-3\"\n >\n <span class=\"text-sm font-medium\">{title}</span>\n <Icon\n name=\"lucide:chevron-down\"\n class=\"size-4 transition-transform duration-200 {open\n ? 'rotate-180'\n : ''}\"\n />\n </div>\n {/snippet}\n {#snippet content()}\n <div class=\"pb-3 text-sm text-on-surface-variant\">\n Content for the \"{title}\" section. Each collapsible operates\n independently.\n </div>\n {/snippet}\n </Collapsible>\n {/each}\n </div>\n </section>\n</div>\n",
|
|
@@ -138,6 +173,7 @@
|
|
|
138
173
|
"tooltip": "<script lang=\"ts\">\n import { Tooltip, Button, Badge, Icon, Separator } from '$lib/index.js'\n\n let controlledOpen = $state(false)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Tooltip</h1>\n <p class=\"text-on-surface-variant\">\n Display brief, informative text on hover or focus. Built on bits-ui Tooltip primitive.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"flex flex-wrap gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"This is a tooltip\">\n <Button variant=\"outline\">Hover me</Button>\n </Tooltip>\n\n <Tooltip text=\"Tooltip for icon button\">\n <Button icon=\"lucide:settings\" square variant=\"ghost\" />\n </Tooltip>\n\n <Tooltip text=\"Information tooltip\">\n <Badge label=\"Hover for info\" color=\"info\" />\n </Tooltip>\n </div>\n </section>\n\n <!-- Positions -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Positions</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control tooltip placement with the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">side</code>\n prop.\n </p>\n <div\n class=\"flex flex-wrap items-center justify-center gap-8 rounded-lg bg-surface-container-high p-8\"\n >\n <Tooltip text=\"I appear on top\" side=\"top\">\n <Button variant=\"soft\">Top</Button>\n </Tooltip>\n\n <Tooltip text=\"I appear on the right\" side=\"right\">\n <Button variant=\"soft\">Right</Button>\n </Tooltip>\n\n <Tooltip text=\"I appear on the bottom\" side=\"bottom\">\n <Button variant=\"soft\">Bottom</Button>\n </Tooltip>\n\n <Tooltip text=\"I appear on the left\" side=\"left\">\n <Button variant=\"soft\">Left</Button>\n </Tooltip>\n </div>\n </section>\n\n <!-- Alignment -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Alignment</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control alignment with the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">align</code>\n prop.\n </p>\n <div\n class=\"flex flex-wrap items-center justify-center gap-8 rounded-lg bg-surface-container-high p-8\"\n >\n <Tooltip text=\"Aligned to start\" side=\"bottom\" align=\"start\">\n <Button variant=\"outline\">Start</Button>\n </Tooltip>\n\n <Tooltip text=\"Aligned to center\" side=\"bottom\" align=\"center\">\n <Button variant=\"outline\">Center</Button>\n </Tooltip>\n\n <Tooltip text=\"Aligned to end\" side=\"bottom\" align=\"end\">\n <Button variant=\"outline\">End</Button>\n </Tooltip>\n </div>\n </section>\n\n <!-- Arrow -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Arrow</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Add an arrow pointing to the trigger element with the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">arrow</code>\n prop.\n </p>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n {#each [{ side: 'top' as const, label: 'Top' }, { side: 'right' as const, label: 'Right' }, { side: 'bottom' as const, label: 'Bottom' }, { side: 'left' as const, label: 'Left' }] as item (item.side)}\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-6\"\n >\n <Tooltip text=\"Arrow on {item.side}\" arrow side={item.side}>\n <Button>{item.label}</Button>\n </Tooltip>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Keyboard Shortcuts -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Keyboard Shortcuts</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Display keyboard shortcuts alongside text using the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">kbds</code>\n prop.\n </p>\n <div class=\"flex flex-wrap gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"Search\" kbds={['meta', 'k']}>\n <Button icon=\"lucide:search\" variant=\"outline\">Search</Button>\n </Tooltip>\n\n <Tooltip text=\"Save changes\" kbds={['meta', 's']}>\n <Button icon=\"lucide:save\" variant=\"soft\">Save</Button>\n </Tooltip>\n\n <Tooltip text=\"Undo action\" kbds={['meta', 'z']}>\n <Button icon=\"lucide:undo\" variant=\"ghost\">Undo</Button>\n </Tooltip>\n\n <Tooltip text=\"Open command palette\" kbds={['meta', 'shift', 'p']}>\n <Button icon=\"lucide:terminal\" variant=\"outline\">Command</Button>\n </Tooltip>\n </div>\n </section>\n\n <!-- Delay Duration -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Delay Duration</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control how long to wait before showing the tooltip.\n </p>\n <div class=\"flex flex-wrap gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"Instant (0ms)\" delayDuration={0}>\n <Button variant=\"outline\">No Delay</Button>\n </Tooltip>\n\n <Tooltip text=\"Fast (200ms)\" delayDuration={200}>\n <Button variant=\"outline\">200ms</Button>\n </Tooltip>\n\n <Tooltip text=\"Slow (500ms)\" delayDuration={500}>\n <Button variant=\"outline\">500ms</Button>\n </Tooltip>\n\n <Tooltip text=\"Very slow (1s)\" delayDuration={1000}>\n <Button variant=\"outline\">1 second</Button>\n </Tooltip>\n </div>\n </section>\n\n <!-- Controlled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Controlled</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Control tooltip visibility programmatically with\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">open</code>\n binding.\n </p>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Observe hover state</p>\n <div class=\"flex items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"Hover to see state change\" bind:open={controlledOpen}>\n <Button variant={controlledOpen ? 'solid' : 'outline'}>Hover me</Button>\n </Tooltip>\n <span class=\"text-sm text-on-surface-variant\">\n open: <code class=\"text-primary\">{controlledOpen}</code>\n </span>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Toggle programmatically</p>\n <div class=\"flex items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"Programmatic tooltip\" open={controlledOpen}>\n <Button variant=\"outline\">Target</Button>\n </Tooltip>\n <Button variant=\"soft\" onclick={() => (controlledOpen = !controlledOpen)}>\n {controlledOpen ? 'Hide' : 'Show'}\n </Button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled</h2>\n <div class=\"flex flex-wrap gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"This won't show\" disabled>\n <Button>Tooltip Disabled</Button>\n </Tooltip>\n\n <Tooltip text=\"This will show\">\n <Button variant=\"soft\">Tooltip Enabled</Button>\n </Tooltip>\n </div>\n </section>\n\n <!-- Custom Content Snippet -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Content</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">content</code>\n snippet for rich tooltip content.\n </p>\n <div class=\"flex flex-wrap gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip>\n <Button variant=\"soft\" icon=\"lucide:user\">User Info</Button>\n {#snippet content()}\n <div class=\"flex items-center gap-2\">\n <Icon name=\"lucide:user-circle\" size=\"16\" />\n <span>John Doe</span>\n <Badge label=\"Admin\" size=\"xs\" color=\"success\" />\n </div>\n {/snippet}\n </Tooltip>\n\n <Tooltip arrow>\n <Button variant=\"soft\" icon=\"lucide:info\">Details</Button>\n {#snippet content()}\n <div class=\"flex flex-col gap-1\">\n <span class=\"font-medium\">Server Status</span>\n <span class=\"text-inverse-on-surface/80\">All systems operational</span>\n </div>\n {/snippet}\n </Tooltip>\n\n <Tooltip>\n <Button icon=\"lucide:palette\" variant=\"outline\">Colors</Button>\n {#snippet content()}\n <div class=\"flex gap-1\">\n <span class=\"size-4 rounded-full bg-red-500\"></span>\n <span class=\"size-4 rounded-full bg-yellow-500\"></span>\n <span class=\"size-4 rounded-full bg-green-500\"></span>\n <span class=\"size-4 rounded-full bg-blue-500\"></span>\n </div>\n {/snippet}\n </Tooltip>\n </div>\n </section>\n\n <!-- Hoverable Content -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Hoverable Content</h2>\n <p class=\"text-sm text-on-surface-variant\">\n By default, users can hover over tooltip content. Disable with\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >disableHoverableContent</code\n >.\n </p>\n <div class=\"flex flex-wrap gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"You can hover over this tooltip\" delayDuration={0}>\n <Button variant=\"outline\">Hoverable (default)</Button>\n </Tooltip>\n\n <Tooltip\n text=\"This closes immediately when you leave trigger\"\n disableHoverableContent\n delayDuration={0}\n >\n <Button variant=\"outline\">Not Hoverable</Button>\n </Tooltip>\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Primary style</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-6\"\n >\n <Tooltip\n text=\"Custom styled tooltip\"\n ui={{ content: 'bg-primary text-on-primary rounded-full px-4' }}\n >\n <Button>Primary</Button>\n </Tooltip>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Error style</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-6\"\n >\n <Tooltip text=\"Error styled\" ui={{ content: 'bg-error text-on-error' }}>\n <Button color=\"error\" variant=\"soft\">Error</Button>\n </Tooltip>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Success style</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-6\"\n >\n <Tooltip text=\"Success message\" ui={{ content: 'bg-success text-on-success' }}>\n <Button color=\"success\" variant=\"soft\">Success</Button>\n </Tooltip>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Large size</p>\n <div\n class=\"flex items-center justify-center rounded-lg bg-surface-container-high p-6\"\n >\n <Tooltip text=\"Large tooltip\" ui={{ content: 'text-sm px-4 py-2' }}>\n <Button variant=\"outline\">Large</Button>\n </Tooltip>\n </div>\n </div>\n </div>\n </section>\n\n <Separator />\n\n <!-- Real World Examples -->\n <section class=\"space-y-6\">\n <h2 class=\"text-lg font-semibold\">Real World Examples</h2>\n\n <div class=\"grid gap-4 md:grid-cols-2\">\n <!-- Editor Toolbar -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Editor Toolbar</p>\n <div class=\"flex items-center gap-1 rounded-lg bg-surface-container-high p-2\">\n <Tooltip text=\"Bold\" kbds={['meta', 'b']}>\n <Button icon=\"lucide:bold\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n <Tooltip text=\"Italic\" kbds={['meta', 'i']}>\n <Button icon=\"lucide:italic\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n <Tooltip text=\"Underline\" kbds={['meta', 'u']}>\n <Button icon=\"lucide:underline\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n <div class=\"mx-1 h-6 w-px bg-outline-variant\"></div>\n <Tooltip text=\"Align Left\">\n <Button icon=\"lucide:align-left\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n <Tooltip text=\"Align Center\">\n <Button icon=\"lucide:align-center\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n <Tooltip text=\"Align Right\">\n <Button icon=\"lucide:align-right\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n <div class=\"mx-1 h-6 w-px bg-outline-variant\"></div>\n <Tooltip text=\"Insert Link\" kbds={['meta', 'k']}>\n <Button icon=\"lucide:link\" square variant=\"ghost\" size=\"sm\" />\n </Tooltip>\n </div>\n </div>\n\n <!-- Social Actions -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Social Actions</p>\n <div class=\"flex items-center gap-2 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"Like this post\">\n <Button icon=\"lucide:heart\" variant=\"ghost\" size=\"sm\">128</Button>\n </Tooltip>\n <Tooltip text=\"Leave a comment\">\n <Button icon=\"lucide:message-circle\" variant=\"ghost\" size=\"sm\">24</Button>\n </Tooltip>\n <Tooltip text=\"Share this post\">\n <Button icon=\"lucide:share-2\" variant=\"ghost\" size=\"sm\">Share</Button>\n </Tooltip>\n <Tooltip text=\"Save to bookmarks\">\n <Button icon=\"lucide:bookmark\" variant=\"ghost\" size=\"sm\" square />\n </Tooltip>\n </div>\n </div>\n\n <!-- Status Indicators -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Status Indicators</p>\n <div class=\"flex items-center gap-4 rounded-lg bg-surface-container-high p-4\">\n <Tooltip text=\"Server is healthy and responding\">\n <span class=\"flex items-center gap-2\">\n <span class=\"size-3 rounded-full bg-success\"></span>\n <span class=\"text-sm\">API Server</span>\n </span>\n </Tooltip>\n <Tooltip text=\"High CPU usage detected\">\n <span class=\"flex items-center gap-2\">\n <span class=\"size-3 rounded-full bg-warning\"></span>\n <span class=\"text-sm\">Worker Node</span>\n </span>\n </Tooltip>\n <Tooltip text=\"Service unavailable\">\n <span class=\"flex items-center gap-2\">\n <span class=\"size-3 rounded-full bg-error\"></span>\n <span class=\"text-sm\">Database</span>\n </span>\n </Tooltip>\n </div>\n </div>\n\n <!-- Truncated Content -->\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium\">Truncated Content</p>\n <div class=\"flex flex-col gap-2 rounded-lg bg-surface-container-high p-4\">\n <Tooltip\n text=\"This is the full title of a very long document that would normally be truncated in the UI\"\n >\n <p class=\"w-64 truncate text-sm\">\n This is the full title of a very long document that would normally be\n truncated in the UI\n </p>\n </Tooltip>\n <Tooltip text=\"user@example.com, admin@company.org, support@helpdesk.io\">\n <p class=\"w-48 truncate text-sm text-on-surface-variant\">\n user@example.com, admin@company.org, support@helpdesk.io\n </p>\n </Tooltip>\n </div>\n </div>\n </div>\n </section>\n</div>\n",
|
|
139
174
|
"calendar": "<script lang=\"ts\">\n import { Calendar, Button, Popover, Icon, Form, FormField } from '$lib/index.js'\n import type { FormApi } from '$lib/index.js'\n import { z } from 'zod'\n import { CalendarDate, today, getLocalTimeZone } from '@internationalized/date'\n import type { DateValue } from '@internationalized/date'\n import type { DateRange } from 'bits-ui'\n\n let datePickerValue: DateValue | undefined = $state(undefined)\n let datePickerOpen = $state(false)\n let rangeDatePickerValue: DateRange | undefined = $state(undefined)\n let rangeDatePickerOpen = $state(false)\n let singleValue: DateValue | undefined = $state(undefined)\n let multipleValues: DateValue[] | undefined = $state([\n new CalendarDate(2024, 3, 10),\n new CalendarDate(2024, 3, 15),\n new CalendarDate(2024, 3, 20)\n ])\n let rangeValue: DateRange | undefined = $state({\n start: new CalendarDate(2024, 3, 10),\n end: new CalendarDate(2024, 3, 20)\n })\n\n let capValues: DateValue[] | undefined = $state([])\n let markedValue: DateValue | undefined = $state(undefined)\n\n const todayDate = today(getLocalTimeZone())\n const markedDays = new Set([1, 7, 14, 23])\n const isDayMarked = (d: DateValue) => markedDays.has(d.day)\n\n const calendarFormSchema = z.object({\n birthday: z.custom<DateValue>((v) => v !== null && v !== undefined, {\n message: 'Birthday is required'\n })\n })\n\n let calendarFormState = $state<{ birthday: DateValue | undefined }>({ birthday: undefined })\n let calendarFormApi = $state<FormApi<unknown>>()\n let calendarSubmitted = $state<string | null>(null)\n\n function handleCalendarSubmit(event: { data: unknown }) {\n calendarSubmitted = JSON.stringify(event.data, null, 2)\n }\n\n function formatDate(date: DateValue | undefined): string {\n if (!date) return 'Pick a date'\n return date.toDate('UTC').toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric'\n })\n }\n\n function formatRange(range: DateRange | undefined): string {\n if (!range?.start) return 'Pick a date range'\n const start = range.start\n .toDate('UTC')\n .toLocaleDateString('en-US', { month: 'short', day: 'numeric' })\n if (!range.end) return `${start} - ...`\n const end = range.end\n .toDate('UTC')\n .toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })\n return `${start} - ${end}`\n }\n\n function isWeekend(date: DateValue): boolean {\n const jsDate = date.toDate('UTC')\n const day = jsDate.getDay()\n return day === 0 || day === 6\n }\n\n function isHoliday(date: DateValue): boolean {\n return date.month === 3 && (date.day === 25 || date.day === 26)\n }\n\n const colors = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'success',\n 'warning',\n 'error',\n 'info',\n 'surface'\n ] as const\n const variants = ['solid', 'outline', 'soft', 'subtle'] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Calendar</h1>\n <p class=\"text-on-surface-variant\">\n A date selection component that supports single, multiple, and range selection modes.\n Built on bits-ui Calendar and RangeCalendar primitives.\n </p>\n </div>\n\n <!-- Date Picker -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Date Picker</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Combine Calendar with Button and Popover to create a date picker.\n </p>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Single Date</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Popover bind:open={datePickerOpen}>\n <Button variant=\"outline\" color=\"surface\" class=\"w-56 justify-start\">\n <span class=\"flex items-center gap-2\">\n <Icon name=\"lucide:calendar\" class=\"size-4\" />\n <span\n class={datePickerValue\n ? 'text-on-surface'\n : 'text-on-surface-variant'}\n >\n {formatDate(datePickerValue)}\n </span>\n </span>\n </Button>\n {#snippet content({ close })}\n <Calendar\n bind:value={datePickerValue}\n class=\"p-2\"\n onValueChange={() => close()}\n />\n {/snippet}\n </Popover>\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Date Range</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Popover bind:open={rangeDatePickerOpen}>\n <Button variant=\"outline\" color=\"surface\" class=\"w-72 justify-start\">\n <span class=\"flex items-center gap-2\">\n <Icon name=\"lucide:calendar\" class=\"size-4\" />\n <span\n class={rangeDatePickerValue?.start\n ? 'text-on-surface'\n : 'text-on-surface-variant'}\n >\n {formatRange(rangeDatePickerValue)}\n </span>\n </span>\n </Button>\n {#snippet content()}\n <Calendar\n range\n bind:value={rangeDatePickerValue}\n class=\"p-2\"\n numberOfMonths={2}\n />\n {/snippet}\n </Popover>\n </div>\n </div>\n </div>\n </section>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"flex flex-wrap items-start gap-6 rounded-lg bg-surface-container-high p-4\">\n <Calendar bind:value={singleValue} />\n <div class=\"text-sm text-on-surface-variant\">\n Selected: <code class=\"text-primary\">{singleValue?.toString() ?? 'none'}</code>\n </div>\n </div>\n </section>\n\n <!-- Colors -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Colors</h2>\n <div class=\"flex flex-wrap items-start gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <div class=\"space-y-2\">\n <p class=\"text-center text-xs font-medium text-on-surface-variant capitalize\">\n {color}\n </p>\n <Calendar {color} value={today(getLocalTimeZone())} size=\"xs\" />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"flex flex-wrap items-start gap-6 rounded-lg bg-surface-container-high p-4\">\n {#each variants as variant (variant)}\n <div class=\"space-y-2\">\n <p class=\"text-center text-xs font-medium text-on-surface-variant capitalize\">\n {variant}\n </p>\n <Calendar {variant} value={today(getLocalTimeZone())} size=\"sm\" />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Sizes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {#each ['xs', 'sm', 'md', 'lg', 'xl'] as size (size)}\n {@const calendarSize = size as 'xs' | 'sm' | 'md' | 'lg' | 'xl'}\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant uppercase\">{size}</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar size={calendarSize} />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Multiple Selection -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Multiple Selection</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >type=\"multiple\"</code\n >\n to allow selecting multiple dates.\n </p>\n <div class=\"flex flex-wrap items-start gap-6 rounded-lg bg-surface-container-high p-4\">\n <Calendar\n type=\"multiple\"\n bind:value={multipleValues}\n placeholder={new CalendarDate(2024, 3, 1)}\n />\n <div class=\"text-sm text-on-surface-variant\">\n <p class=\"font-medium\">Selected dates:</p>\n {#if multipleValues?.length}\n <ul class=\"mt-1 list-inside list-disc\">\n {#each multipleValues as date (date.toString())}\n <li><code class=\"text-primary\">{date.toString()}</code></li>\n {/each}\n </ul>\n {:else}\n <p class=\"text-on-surface-variant/60\">No dates selected</p>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- Range Selection -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Range Selection</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">range</code>\n prop to enable date range selection.\n </p>\n <div class=\"flex flex-wrap items-start gap-6 rounded-lg bg-surface-container-high p-4\">\n <Calendar\n range\n bind:value={rangeValue}\n placeholder={new CalendarDate(2024, 3, 1)}\n numberOfMonths={2}\n />\n <div class=\"text-sm text-on-surface-variant\">\n <p>\n Start: <code class=\"text-primary\"\n >{rangeValue?.start?.toString() ?? 'none'}</code\n >\n </p>\n <p>\n End: <code class=\"text-primary\">{rangeValue?.end?.toString() ?? 'none'}</code>\n </p>\n </div>\n </div>\n </section>\n\n <!-- Multiple Months -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Multiple Months</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar numberOfMonths={2} />\n </div>\n </section>\n\n <!-- Disabled & Unavailable Dates -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disabled & Unavailable Dates</h2>\n <div class=\"grid gap-4 sm:grid-cols-2\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Weekends disabled</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar isDateDisabled={isWeekend} />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Holidays unavailable</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar\n isDateUnavailable={isHoliday}\n placeholder={new CalendarDate(2024, 3, 1)}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Min/Max range</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar\n minValue={new CalendarDate(2024, 3, 5)}\n maxValue={new CalendarDate(2024, 3, 25)}\n placeholder={new CalendarDate(2024, 3, 1)}\n />\n </div>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">Entire calendar disabled</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar disabled />\n </div>\n </div>\n </div>\n </section>\n\n <!-- Locale -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Locale</h2>\n <div class=\"grid gap-4 sm:grid-cols-3\">\n {#each [{ locale: 'en', label: 'English' }, { locale: 'vi', label: 'Vietnamese' }, { locale: 'ja', label: 'Japanese' }] as item (item.locale)}\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-on-surface-variant\">{item.label}</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar locale={item.locale} size=\"sm\" />\n </div>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- UI Slot Overrides -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI Slot Overrides</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar\n ui={{\n root: 'shadow-xl rounded-lg bg-surface-container-lowest p-3',\n heading: 'text-primary',\n headCell: 'text-primary/60'\n }}\n />\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Max days (multiple)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Cap how many dates can be selected in <code>type=\"multiple\"</code> via\n <code>maxDays</code>. The same prop is available on range calendars (and\n <code>minDays</code> too).\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar type=\"multiple\" bind:value={capValues} maxDays={3} placeholder={todayDate} />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n {capValues?.length ?? 0} / 3 selected\n </p>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Highlight specific dates</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code>isDateHighlightable</code> to mark special dates (holidays, events) with a\n small dot indicator. Marked dates remain selectable; only their appearance changes.\n Style overrides via <code>ui.cellTrigger</code> with the <code>data-[marked]:</code>\n modifier.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Calendar\n bind:value={markedValue}\n isDateHighlightable={isDayMarked}\n placeholder={todayDate}\n />\n <p class=\"mt-3 text-sm text-on-surface-variant\">\n Highlighted: days {Array.from(markedDays).join(', ')} of the visible month.\n </p>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Inside a Form (Zod schema)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Calendar reads the parent <code>FormField</code> + <code>Form</code> context. Try\n submitting without picking a date — the Zod schema fails and the calendar switches to\n error color with <code>aria-invalid</code>. Pick any date and the error clears\n automatically as the schema re-runs on the value change.\n </p>\n <div class=\"rounded-lg border border-outline-variant bg-surface-container p-6\">\n <Form\n bind:api={calendarFormApi}\n bind:state={calendarFormState}\n schema={calendarFormSchema}\n onsubmit={handleCalendarSubmit}\n class=\"max-w-md space-y-4\"\n >\n <FormField name=\"birthday\" label=\"Birthday\" required>\n <Calendar bind:value={calendarFormState.birthday} />\n </FormField>\n\n <div class=\"flex items-center gap-3\">\n <Button type=\"submit\" loading={calendarFormApi?.loading}>Submit</Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n color=\"secondary\"\n onclick={() => calendarFormApi?.clear()}\n >\n Clear errors\n </Button>\n </div>\n </Form>\n\n {#if calendarSubmitted}\n <div\n class=\"mt-4 rounded-md border border-primary/20 bg-primary-container p-3 text-sm text-on-primary-container\"\n >\n <p class=\"font-medium\">Submitted:</p>\n <pre class=\"mt-1 text-xs\">{calendarSubmitted}</pre>\n </div>\n {/if}\n </div>\n </section>\n</div>\n",
|
|
140
175
|
"range-calendar": "<script lang=\"ts\">\n import { Alert, Button, Card, Icon } from '$lib/index.js'\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">RangeCalendar</h1>\n <p class=\"text-on-surface-variant\">\n A date range picker for selecting a start and end date.\n </p>\n </div>\n\n <Alert\n variant=\"soft\"\n color=\"info\"\n title=\"Coming soon\"\n description=\"RangeCalendar is part of the docs navigation but is not yet implemented as a standalone component. Use Calendar for now and check back later.\"\n />\n\n <Card>\n <div class=\"flex flex-col items-center gap-4 py-12 text-center\">\n <div\n class=\"flex size-16 items-center justify-center rounded-full bg-primary-container text-on-primary-container\"\n >\n <Icon name=\"lucide:calendar-range\" size=\"32\" />\n </div>\n <h3 class=\"text-lg font-semibold\">RangeCalendar</h3>\n <p class=\"max-w-md text-sm text-on-surface-variant\">\n A primitive-based date range picker built on <code\n class=\"rounded bg-surface-container px-1\">bits-ui</code\n >, styled with the same color tokens and theming as the rest of Svelora.\n </p>\n <Button label=\"View Calendar\" variant=\"outline\" leadingIcon=\"lucide:arrow-right\" href=\"/calendar\" />\n </div>\n </Card>\n</div>\n",
|
|
176
|
+
"date-range-picker": "<script lang=\"ts\">\n import { DateRangePicker } from '$lib/index.js'\n \n let range1 = $state<any>({ start: undefined, end: undefined })\n let range2 = $state<any>({ \n start: undefined, \n end: undefined \n })\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">DateRangePicker</h1>\n <p class=\"text-on-surface-variant\">\n A popover input that allows users to select a start and end date using a calendar interface.\n </p>\n </div>\n\n <!-- Usage -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Usage</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">bind:value</code> with an object containing <code class=\"rounded bg-surface-container-highest px-1\">start</code> and <code class=\"rounded bg-surface-container-highest px-1\">end</code> properties (which are <code class=\"rounded bg-surface-container-highest px-1\">Date</code> objects or <code class=\"rounded bg-surface-container-highest px-1\">null</code>).\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <DateRangePicker bind:value={range1} placeholder=\"Pick a date range\" />\n <div class=\"text-sm text-on-surface-variant\">\n <strong>Start:</strong> {range1.start?.toString() ?? 'None'} <br />\n <strong>End:</strong> {range1.end?.toString() ?? 'None'}\n </div>\n </div>\n </section>\n\n <!-- Default Value -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Pre-selected Range</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Initialize the bound value to have the date range pre-filled.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4 flex flex-col gap-4 max-w-sm\">\n <DateRangePicker bind:value={range2} />\n </div>\n </section>\n</div>\n",
|
|
141
177
|
"use-media-query": "<script lang=\"ts\">\n import { useMediaQuery } from '$lib/index.js'\n import { Badge, Button, Icon, Card } from '$lib/index.js'\n\n const isMobile = useMediaQuery('(max-width: 640px)')\n const isTablet = useMediaQuery('(min-width: 641px) and (max-width: 1024px)')\n const isDesktop = useMediaQuery('(min-width: 1025px)')\n const prefersDark = useMediaQuery('(prefers-color-scheme: dark)')\n const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)')\n const isLandscape = useMediaQuery('(orientation: landscape)')\n\n let customQuery = $state('(min-width: 768px)')\n const customResult = useMediaQuery(() => customQuery)\n\n const breakpoints = [\n { label: 'Mobile', query: '(max-width: 640px)', result: isMobile },\n { label: 'Tablet', query: '(min-width: 641px) and (max-width: 1024px)', result: isTablet },\n { label: 'Desktop', query: '(min-width: 1025px)', result: isDesktop }\n ] as const\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useMediaQuery</h1>\n <p class=\"text-on-surface-variant\">\n Reactive media query hook. Tracks whether a CSS media query matches in JavaScript.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Resize the browser window to see values change in real time.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each breakpoints as bp (bp.label)}\n <Badge\n label=\"{bp.label}: {bp.result.matches}\"\n color={bp.result.matches ? 'success' : 'surface'}\n variant=\"subtle\"\n size=\"md\"\n />\n {/each}\n </div>\n </section>\n\n <!-- Current Breakpoint -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Current Breakpoint</h2>\n <div class=\"flex flex-col items-center gap-4 rounded-lg bg-surface-container-high p-8\">\n <Icon\n name={isMobile.matches\n ? 'lucide:smartphone'\n : isTablet.matches\n ? 'lucide:tablet'\n : 'lucide:monitor'}\n size=\"48\"\n class=\"text-primary\"\n />\n <span class=\"text-lg font-medium\">\n {isMobile.matches ? 'Mobile' : isTablet.matches ? 'Tablet' : 'Desktop'}\n </span>\n <span class=\"text-sm text-on-surface-variant\">\n {isMobile.matches ? '< 640px' : isTablet.matches ? '641px - 1024px' : '> 1025px'}\n </span>\n </div>\n </section>\n\n <!-- User Preferences -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">User Preferences</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Detect OS-level user preferences for dark mode, motion, and orientation.\n </p>\n <div class=\"grid gap-3 rounded-lg bg-surface-container-high p-4 sm:grid-cols-2\">\n <div class=\"flex items-center gap-3 rounded-md bg-surface-container p-3\">\n <Icon\n name={prefersDark.matches ? 'lucide:moon' : 'lucide:sun'}\n size=\"20\"\n class=\"text-on-surface-variant\"\n />\n <div>\n <p class=\"text-sm font-medium\">Color Scheme</p>\n <p class=\"text-xs text-on-surface-variant\">\n {prefersDark.matches ? 'Dark mode' : 'Light mode'}\n </p>\n </div>\n <Badge\n label={prefersDark.matches ? 'dark' : 'light'}\n color={prefersDark.matches ? 'info' : 'warning'}\n variant=\"soft\"\n size=\"sm\"\n class=\"ml-auto\"\n />\n </div>\n\n <div class=\"flex items-center gap-3 rounded-md bg-surface-container p-3\">\n <Icon\n name={prefersReducedMotion.matches ? 'lucide:pause' : 'lucide:play'}\n size=\"20\"\n class=\"text-on-surface-variant\"\n />\n <div>\n <p class=\"text-sm font-medium\">Motion</p>\n <p class=\"text-xs text-on-surface-variant\">\n {prefersReducedMotion.matches ? 'Reduced' : 'Normal'}\n </p>\n </div>\n <Badge\n label={prefersReducedMotion.matches ? 'reduced' : 'normal'}\n color={prefersReducedMotion.matches ? 'warning' : 'success'}\n variant=\"soft\"\n size=\"sm\"\n class=\"ml-auto\"\n />\n </div>\n\n <div class=\"flex items-center gap-3 rounded-md bg-surface-container p-3\">\n <Icon\n name={isLandscape.matches\n ? 'lucide:rectangle-horizontal'\n : 'lucide:rectangle-vertical'}\n size=\"20\"\n class=\"text-on-surface-variant\"\n />\n <div>\n <p class=\"text-sm font-medium\">Orientation</p>\n <p class=\"text-xs text-on-surface-variant\">\n {isLandscape.matches ? 'Landscape' : 'Portrait'}\n </p>\n </div>\n <Badge\n label={isLandscape.matches ? 'landscape' : 'portrait'}\n color=\"info\"\n variant=\"soft\"\n size=\"sm\"\n class=\"ml-auto\"\n />\n </div>\n </div>\n </section>\n\n <!-- Custom Query -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Query</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Supports reactive query strings via getter functions.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <Button\n variant={customQuery === '(min-width: 768px)' ? 'solid' : 'outline'}\n size=\"sm\"\n onclick={() => (customQuery = '(min-width: 768px)')}\n >\n min-width: 768px\n </Button>\n <Button\n variant={customQuery === '(max-width: 1200px)' ? 'solid' : 'outline'}\n size=\"sm\"\n onclick={() => (customQuery = '(max-width: 1200px)')}\n >\n max-width: 1200px\n </Button>\n <Button\n variant={customQuery === '(hover: hover)' ? 'solid' : 'outline'}\n size=\"sm\"\n onclick={() => (customQuery = '(hover: hover)')}\n >\n hover: hover\n </Button>\n </div>\n <div class=\"flex items-center gap-2 text-sm\">\n <code class=\"rounded bg-surface-container px-2 py-1 font-mono\">{customQuery}</code>\n <span class=\"text-on-surface-variant\">=</span>\n <Badge\n label={String(customResult.matches)}\n color={customResult.matches ? 'success' : 'error'}\n variant=\"subtle\"\n />\n </div>\n </div>\n </section>\n\n <!-- Responsive Layout -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World: Responsive Layout</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Switch between layouts based on viewport. Resize the window to see the layout change.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n {#if isMobile.matches}\n <div class=\"space-y-3\">\n {#each ['Card 1', 'Card 2', 'Card 3'] as title (title)}\n <Card class=\"p-4\">\n <p class=\"text-sm font-medium\">{title}</p>\n <p class=\"text-xs text-on-surface-variant\">Mobile: stacked layout</p>\n </Card>\n {/each}\n </div>\n {:else}\n <div class=\"grid grid-cols-3 gap-3\">\n {#each ['Card 1', 'Card 2', 'Card 3'] as title (title)}\n <Card class=\"p-4\">\n <p class=\"text-sm font-medium\">{title}</p>\n <p class=\"text-xs text-on-surface-variant\">Desktop: grid layout</p>\n </Card>\n {/each}\n </div>\n {/if}\n </div>\n </section>\n</div>\n",
|
|
142
178
|
"use-clipboard": "<script lang=\"ts\">\n import { useClipboard } from '$lib/index.js'\n import { Button, Input, Textarea, Badge, Icon, Card } from '$lib/index.js'\n\n const clipboard = useClipboard()\n const clipboardLong = useClipboard({ timeout: 5000 })\n\n let inputValue = $state('Hello, Svelora!')\n let textareaValue = $state('const greeting = \"Hello World\";\\nconsole.log(greeting);')\n\n const snippets = [\n { label: 'npm', text: 'npm install svelora', icon: 'lucide:terminal' },\n { label: 'pnpm', text: 'pnpm add svelora', icon: 'lucide:terminal' },\n { label: 'yarn', text: 'yarn add svelora', icon: 'lucide:terminal' }\n ]\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useClipboard</h1>\n <p class=\"text-on-surface-variant\">\n Reactive clipboard hook. Copies text and tracks the copied state with auto-reset.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n onclick={() => clipboard.copy('Hello from Svelora!')}\n icon={clipboard.copied ? 'lucide:check' : 'lucide:copy'}\n color={clipboard.copied ? 'success' : 'primary'}\n >\n {clipboard.copied ? 'Copied!' : 'Copy Text'}\n </Button>\n\n <Badge\n label={clipboard.copied ? 'Copied' : 'Ready'}\n color={clipboard.copied ? 'success' : 'surface'}\n variant=\"subtle\"\n />\n </div>\n </section>\n\n <!-- Custom Timeout -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Custom Timeout</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The default timeout is 2 seconds. You can set a custom timeout for longer feedback.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n onclick={() => clipboardLong.copy('Copied with 5s timeout!')}\n icon={clipboardLong.copied ? 'lucide:check' : 'lucide:copy'}\n color={clipboardLong.copied ? 'success' : 'secondary'}\n variant=\"outline\"\n >\n {clipboardLong.copied ? 'Copied! (5s reset)' : 'Copy (5s timeout)'}\n </Button>\n </div>\n </section>\n\n <!-- Copy from Input -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Copy from Input</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex items-center gap-2\">\n <div class=\"flex-1\">\n <Input bind:value={inputValue} placeholder=\"Type something...\" />\n </div>\n <Button\n onclick={() => clipboard.copy(inputValue)}\n icon={clipboard.copied ? 'lucide:check' : 'lucide:copy'}\n color={clipboard.copied ? 'success' : 'primary'}\n variant=\"outline\"\n square\n />\n </div>\n </div>\n </section>\n\n <!-- Copy Code Block -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Copy from Textarea</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Textarea bind:value={textareaValue} rows={3} />\n <Button\n onclick={() => clipboard.copy(textareaValue)}\n icon={clipboard.copied ? 'lucide:check' : 'lucide:copy'}\n color={clipboard.copied ? 'success' : 'primary'}\n size=\"sm\"\n >\n {clipboard.copied ? 'Copied!' : 'Copy Code'}\n </Button>\n </div>\n </section>\n\n <!-- Install Commands -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World: Install Commands</h2>\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n {#each snippets as snippet (snippet.label)}\n <div class=\"flex items-center justify-between rounded-md bg-surface-container p-3\">\n <div class=\"flex items-center gap-3\">\n <Icon name={snippet.icon} size=\"16\" class=\"text-on-surface-variant\" />\n <code class=\"font-mono text-sm\">{snippet.text}</code>\n </div>\n <Button\n onclick={() => clipboard.copy(snippet.text)}\n icon={clipboard.copied ? 'lucide:check' : 'lucide:clipboard'}\n color={clipboard.copied ? 'success' : 'surface'}\n variant=\"ghost\"\n size=\"xs\"\n square\n />\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Share Card -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World: Share Link</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Card class=\"p-4\">\n <div class=\"space-y-3\">\n <p class=\"text-sm font-medium\">Share this page</p>\n <div class=\"flex items-center gap-2\">\n <div\n class=\"flex-1 truncate rounded-md bg-surface-container px-3 py-2 font-mono text-xs text-on-surface-variant\"\n >\n https://svelora.vercel.app/use-clipboard\n </div>\n <Button\n onclick={() => clipboard.copy('https://svelora.vercel.app/use-clipboard')}\n icon={clipboard.copied ? 'lucide:check' : 'lucide:link'}\n color={clipboard.copied ? 'success' : 'primary'}\n variant=\"soft\"\n size=\"sm\"\n >\n {clipboard.copied ? 'Copied!' : 'Copy Link'}\n </Button>\n </div>\n </div>\n </Card>\n </div>\n </section>\n</div>\n",
|
|
143
179
|
"use-form-field": "<script lang=\"ts\">\n import { FormField, Input, Select, Switch, Checkbox, Badge, Separator } from '$lib/index.js'\n\n let name = $state('')\n let email = $state('')\n let nameError = $state<string | boolean>(false)\n let emailError = $state<string | boolean>(false)\n\n function validateName() {\n if (!name) {\n nameError = 'Name is required'\n } else if (name.length < 2) {\n nameError = 'Name must be at least 2 characters'\n } else {\n nameError = false\n }\n }\n\n function validateEmail() {\n if (!email) {\n emailError = 'Email is required'\n } else if (!email.includes('@')) {\n emailError = 'Please enter a valid email'\n } else {\n emailError = false\n }\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useFormField</h1>\n <p class=\"text-on-surface-variant\">\n Access the nearest FormField context from any child component. Provides reactive access\n to name, size, error, and ariaId.\n </p>\n </div>\n\n <!-- Basic -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Child form controls automatically inherit size, error state, name, and ariaId from the\n parent FormField via <code class=\"rounded bg-surface-container px-1\"\n >useFormField()</code\n >.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <FormField label=\"Username\" name=\"username\" description=\"Choose a unique username\">\n <Input placeholder=\"Enter username\" />\n </FormField>\n\n <FormField label=\"Email\" name=\"email\" description=\"We'll never share your email\">\n <Input type=\"email\" placeholder=\"you@example.com\" />\n </FormField>\n </div>\n </section>\n\n <!-- Size Inheritance -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Size Inheritance</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The Input automatically picks up the size from FormField — no need to pass it\n explicitly.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n {#each ['sm', 'md', 'lg'] as size (size)}\n {@const formSize = size as 'sm' | 'md' | 'lg'}\n <FormField label=\"Size: {formSize}\" name=\"size-{formSize}\" size={formSize}>\n <Input placeholder=\"Inherits size={formSize}\" />\n </FormField>\n {/each}\n </div>\n </section>\n\n <!-- Error Propagation -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Error Propagation</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When FormField has an error, child inputs automatically get error styling and ARIA\n attributes via the shared context.\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <FormField label=\"Name\" name=\"name-validate\" error={nameError} required>\n <Input bind:value={name} placeholder=\"Enter your name\" oninput={validateName} />\n </FormField>\n\n <FormField label=\"Email\" name=\"email-validate\" error={emailError} required>\n <Input\n bind:value={email}\n type=\"email\"\n placeholder=\"you@example.com\"\n oninput={validateEmail}\n />\n </FormField>\n\n <div class=\"flex items-center gap-2 text-sm text-on-surface-variant\">\n <Badge\n label=\"Name: {nameError || 'valid'}\"\n color={nameError ? 'error' : 'success'}\n variant=\"soft\"\n size=\"sm\"\n />\n <Badge\n label=\"Email: {emailError || 'valid'}\"\n color={emailError ? 'error' : 'success'}\n variant=\"soft\"\n size=\"sm\"\n />\n </div>\n </div>\n </section>\n\n <!-- Different Form Controls -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Works with All Form Controls</h2>\n <p class=\"text-sm text-on-surface-variant\">\n All form components (Input, Select, Switch, Checkbox, etc.) consume the same FormField\n context via useFormField().\n </p>\n <div class=\"space-y-4 rounded-lg bg-surface-container-high p-4\">\n <FormField label=\"Full Name\" name=\"full-name\" help=\"Your legal full name\">\n <Input placeholder=\"John Doe\" />\n </FormField>\n\n <Separator />\n\n <FormField label=\"Country\" name=\"country\" help=\"Select your country\">\n <Select\n placeholder=\"Choose a country\"\n items={[\n { label: 'Vietnam', value: 'vn' },\n { label: 'United States', value: 'us' },\n { label: 'Japan', value: 'jp' },\n { label: 'South Korea', value: 'kr' }\n ]}\n />\n </FormField>\n\n <Separator />\n\n <FormField label=\"Notifications\" name=\"notifications\">\n <Switch label=\"Enable email notifications\" />\n </FormField>\n\n <Separator />\n\n <FormField label=\"Terms\" name=\"terms\" error=\"You must accept the terms\">\n <Checkbox label=\"I agree to the terms and conditions\" />\n </FormField>\n </div>\n </section>\n\n <!-- How it works -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">How It Works</h2>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <div class=\"rounded-md bg-surface-container p-4\">\n <p class=\"mb-2 text-sm font-medium\">Context Shape</p>\n <pre\n class=\"overflow-x-auto rounded bg-surface-container-highest p-3 font-mono text-xs\">{`interface FormFieldContext {\n name?: string // Form field name for submission\n size: 'sm' | 'md' | 'lg' // Inherited size\n error?: string | boolean // Error state/message\n ariaId: string // ARIA ID for accessibility\n}`}</pre>\n </div>\n\n <div class=\"rounded-md bg-surface-container p-4\">\n <p class=\"mb-2 text-sm font-medium\">Usage in Custom Components</p>\n <pre\n class=\"overflow-x-auto rounded bg-surface-container-highest p-3 font-mono text-xs\">{`import { useFormField } from 'svelora'\n\nconst formField = useFormField()\n// formField?.name → 'email'\n// formField?.size → 'md'\n// formField?.error → 'Invalid email'\n// formField?.ariaId → 'form-field-email'`}</pre>\n </div>\n </div>\n </section>\n</div>\n",
|