svelora 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/Button/Button.svelte +65 -33
  2. package/dist/Fonts/fonts.js +3 -1
  3. package/dist/Link/Link.context-harness.svelte +8 -0
  4. package/dist/Link/Link.context-harness.svelte.d.ts +7 -0
  5. package/dist/Link/Link.svelte +57 -30
  6. package/dist/Link/index.d.ts +2 -0
  7. package/dist/Link/index.js +1 -0
  8. package/dist/Link/location-context.d.ts +4 -0
  9. package/dist/Link/location-context.js +1 -0
  10. package/dist/SelectMenu/SelectMenu.svelte +46 -14
  11. package/dist/Stepper/Stepper.svelte +12 -9
  12. package/dist/docs/navigation.js +54 -0
  13. package/dist/hooks/index.d.ts +14 -0
  14. package/dist/hooks/index.js +9 -0
  15. package/dist/hooks/useDebouncedState.svelte.d.ts +30 -0
  16. package/dist/hooks/useDebouncedState.svelte.js +45 -0
  17. package/dist/hooks/useEventListener.svelte.d.ts +30 -0
  18. package/dist/hooks/useEventListener.svelte.js +16 -0
  19. package/dist/hooks/useFocusTrap.svelte.d.ts +42 -0
  20. package/dist/hooks/useFocusTrap.svelte.js +87 -0
  21. package/dist/hooks/useIntersectionObserver.svelte.d.ts +30 -0
  22. package/dist/hooks/useIntersectionObserver.svelte.js +46 -0
  23. package/dist/hooks/useLocalStorage.svelte.d.ts +39 -0
  24. package/dist/hooks/useLocalStorage.svelte.js +73 -0
  25. package/dist/hooks/useResizeObserver.svelte.d.ts +50 -0
  26. package/dist/hooks/useResizeObserver.svelte.js +71 -0
  27. package/dist/hooks/useScrollLock.svelte.d.ts +28 -0
  28. package/dist/hooks/useScrollLock.svelte.js +79 -0
  29. package/dist/hooks/useThrottle.svelte.d.ts +37 -0
  30. package/dist/hooks/useThrottle.svelte.js +72 -0
  31. package/dist/hooks/useTimers.svelte.d.ts +62 -0
  32. package/dist/hooks/useTimers.svelte.js +90 -0
  33. package/dist/hooks/utils.d.ts +1 -0
  34. package/dist/hooks/utils.js +3 -0
  35. package/dist/mcp/svelora-docs.data.json +23 -5
  36. package/package.json +3 -3
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": 1,
3
3
  "packageName": "svelora",
4
- "packageVersion": "3.0.4",
5
- "generatedAt": "2026-06-23T23:37:07.305Z",
4
+ "packageVersion": "3.0.6",
5
+ "generatedAt": "2026-06-25T08:54:35.884Z",
6
6
  "slugs": {
7
7
  "components": [
8
8
  "button",
@@ -69,11 +69,20 @@
69
69
  "use-click-outside",
70
70
  "use-infinite-scroll",
71
71
  "use-escape-keydown",
72
- "use-debounce"
72
+ "use-debounce",
73
+ "use-debounced-state",
74
+ "use-event-listener",
75
+ "use-resize-observer",
76
+ "use-intersection-observer",
77
+ "use-scroll-lock",
78
+ "use-focus-trap",
79
+ "use-local-storage",
80
+ "use-throttle",
81
+ "use-timers"
73
82
  ]
74
83
  },
75
84
  "pages": {
76
- "button": "<script lang=\"ts\">\n import { Button, Icon } 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\">Button</h1>\n <p class=\"text-on-surface-variant\">\n Use the Button component to trigger actions or navigate with\n <code class=\"rounded bg-surface-container-highest px-1\">href</code>. Supports multiple\n variants, colors, sizes, icons, avatars, and loading states.\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 the <code class=\"rounded bg-surface-container-highest px-1\">label</code> prop or the default\n slot to set the button text.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Button\" />\n </div>\n </section>\n\n <!-- Variant -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variant</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 to\n change the visual style. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">solid</code>.\n </p>\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 <Button {variant} label={variant[0].toUpperCase() + variant.slice(1)} />\n {/each}\n </div>\n </section>\n\n <!-- Color -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Color</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">color</code> prop to\n change the color scheme. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">primary</code>.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <Button {color} label={color[0].toUpperCase() + color.slice(1)} />\n {/each}\n </div>\n </section>\n\n <!-- Variant x Color Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variant & Color Matrix</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 <Button {variant} {color} label=\"Button\" />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\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 change the dimensions. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">md</code>.\n </p>\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 <Button {size} label={size.toUpperCase()} />\n {/each}\n </div>\n </section>\n\n <!-- Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Icon</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\n display an icon in the button. By default the icon is placed on the leading side. Use\n the\n <code class=\"rounded bg-surface-container-highest px-1\">trailing</code> prop to move it to\n the trailing side.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button icon=\"lucide:rocket\" label=\"Launch\" />\n <Button icon=\"lucide:arrow-right\" trailing label=\"Next\" />\n </div>\n </section>\n\n <!-- Leading & Trailing Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Leading & Trailing Icon</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">leadingIcon</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">trailingIcon</code> to display icons\n on both sides simultaneously.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button leadingIcon=\"lucide:plus\" label=\"Add Item\" />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n trailingIcon=\"lucide:arrow-right\"\n label=\"Next\"\n />\n <Button\n variant=\"soft\"\n color=\"success\"\n leadingIcon=\"lucide:check\"\n trailingIcon=\"lucide:chevron-down\"\n label=\"Confirm\"\n />\n </div>\n </section>\n\n <!-- Square (Icon Only) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Square</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">square</code> prop to\n force equal width and height — ideal for icon-only buttons. When only\n <code class=\"rounded bg-surface-container-highest px-1\">icon</code> is provided without\n <code class=\"rounded bg-surface-container-highest px-1\">label</code>, the button is\n automatically treated as icon-only.\n </p>\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 <Button {size} icon=\"lucide:plus\" square />\n {/each}\n <span class=\"mx-2 text-on-surface-variant\">|</span>\n <Button variant=\"outline\" color=\"secondary\" icon=\"lucide:settings\" square />\n <Button variant=\"soft\" color=\"success\" icon=\"lucide:check\" square />\n <Button variant=\"subtle\" color=\"warning\" icon=\"lucide:alert-triangle\" square />\n <Button variant=\"ghost\" color=\"error\" icon=\"lucide:x\" square />\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\">avatar</code> prop to\n display an avatar before the label. Takes precedence over\n <code class=\"rounded bg-surface-container-highest px-1\">leadingIcon</code>.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"soft\"\n avatar={{ src: 'https://i.pravatar.cc/150?img=1', alt: 'John' }}\n label=\"John Doe\"\n />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n avatar={{ src: 'https://i.pravatar.cc/150?img=2', alt: 'Jane' }}\n label=\"Jane Smith\"\n />\n <Button variant=\"solid\" color=\"tertiary\" avatar={{ alt: 'New' }} label=\"New User\" />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n avatar={{ src: 'https://i.pravatar.cc/150?img=5', alt: 'User' }}\n label=\"Profile\"\n trailingIcon=\"lucide:chevron-down\"\n />\n </div>\n\n <p class=\"text-sm text-on-surface-variant\">\n The avatar size automatically adapts to the button size.\n </p>\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 <Button\n variant=\"soft\"\n {size}\n avatar={{ src: 'https://i.pravatar.cc/150?img=3', alt: 'User' }}\n label={size.toUpperCase()}\n />\n {/each}\n </div>\n </section>\n\n <!-- Loading -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">loading</code> prop to\n show a loading spinner and disable the button. The spinner replaces the leading icon by\n default. Use\n <code class=\"rounded bg-surface-container-highest px-1\">trailing</code> to place it on the\n trailing side.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button loading label=\"Loading...\" />\n <Button variant=\"outline\" color=\"secondary\" loading label=\"Processing\" />\n <Button variant=\"soft\" color=\"success\" loading icon=\"lucide:loader-2\" square />\n <Button variant=\"solid\" color=\"info\" loading trailing label=\"Uploading\" />\n </div>\n </section>\n\n <!-- Loading Auto -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Loading Auto</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">loadingAuto</code> prop\n to automatically show loading state while the\n <code class=\"rounded bg-surface-container-highest px-1\">onclick</code> handler's Promise is\n pending. The button is disabled until the Promise resolves or rejects.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n loadingAuto\n label=\"Save (2s)\"\n leadingIcon=\"lucide:save\"\n onclick={() => new Promise((r) => setTimeout(r, 2000))}\n />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n loadingAuto\n label=\"Submit (3s)\"\n onclick={() => new Promise((r) => setTimeout(r, 3000))}\n />\n <Button\n variant=\"soft\"\n color=\"error\"\n loadingAuto\n label=\"Delete (1s)\"\n leadingIcon=\"lucide:trash-2\"\n onclick={() => new Promise((r) => setTimeout(r, 1000))}\n />\n </div>\n </section>\n\n <!-- Disabled -->\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 disable\n the button and prevent interaction.\n </p>\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 <Button {variant} disabled label=\"Disabled\" />\n {/each}\n </div>\n </section>\n\n <!-- Block -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Block</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">block</code> prop to stretch\n the button to fill the full width of its container.\n </p>\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <Button block label=\"Full Width Button\" />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n block\n leadingIcon=\"lucide:mail\"\n trailingIcon=\"lucide:arrow-right\"\n label=\"Send Email\"\n />\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 <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">href</code> prop to\n render as an anchor element. External URLs are auto-detected with\n <code class=\"rounded bg-surface-container-highest px-1\">target=\"_blank\"</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">rel=\"noopener noreferrer\"</code\n >. Use the\n <code class=\"rounded bg-surface-container-highest px-1\">external</code> prop to force this\n behavior on internal paths.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button href=\"/link\" label=\"Internal Link\" />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n href=\"https://svelte.dev\"\n label=\"External Link\"\n trailingIcon=\"lucide:external-link\"\n />\n <Button\n variant=\"soft\"\n color=\"info\"\n href=\"/button\"\n label=\"Current Page\"\n leadingIcon=\"lucide:link\"\n />\n <Button variant=\"ghost\" color=\"error\" href=\"/about\" disabled label=\"Disabled Link\" />\n </div>\n </section>\n\n <!-- Type -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Type</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">type</code> prop to set\n the button type attribute. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">button</code>. Only applies when\n rendering as a &lt;button&gt; element (no\n <code class=\"rounded bg-surface-container-highest px-1\">href</code>).\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Button (default)\" />\n <Button variant=\"outline\" color=\"success\" type=\"submit\" label=\"Submit\" />\n <Button variant=\"ghost\" color=\"secondary\" type=\"reset\" label=\"Reset\" />\n </div>\n </section>\n\n <!-- Active Color & Variant -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Active Color & Variant</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use\n <code class=\"rounded bg-surface-container-highest px-1\">activeColor</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">activeVariant</code> to change\n the button appearance when\n <code class=\"rounded bg-surface-container-highest px-1\">active</code> is true. Falls\n back to the default\n <code class=\"rounded bg-surface-container-highest px-1\">color</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">variant</code> when inactive.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n active\n activeColor=\"primary\"\n activeVariant=\"solid\"\n label=\"Dashboard\"\n leadingIcon=\"lucide:home\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n label=\"Settings\"\n leadingIcon=\"lucide:settings\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n active\n activeColor=\"error\"\n activeVariant=\"soft\"\n label=\"Alerts\"\n leadingIcon=\"lucide:bell\"\n />\n <Button variant=\"ghost\" color=\"secondary\" label=\"Profile\" leadingIcon=\"lucide:user\" />\n </div>\n </section>\n\n <!-- Children Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Children</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the default slot to render custom content inside the button instead of the\n <code class=\"rounded bg-surface-container-highest px-1\">label</code> prop.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button>\n <Icon name=\"lucide:sparkles\" size=\"16\" />\n <span>Custom Content</span>\n </Button>\n <Button variant=\"outline\" color=\"tertiary\">\n <span class=\"font-bold\">Bold</span>\n <span class=\"font-light\">& Light</span>\n </Button>\n </div>\n </section>\n\n <!-- Leading & Trailing Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Leading & Trailing Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">leadingSlot</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">trailingSlot</code> for fully\n custom leading/trailing content. They take precedence over\n <code class=\"rounded bg-surface-container-highest px-1\">avatar</code> and icon props.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#snippet statusSlot()}\n <span class=\"relative flex size-2\">\n <span\n class=\"absolute inline-flex size-full animate-ping rounded-full bg-success opacity-75\"\n ></span>\n <span class=\"relative inline-flex size-2 rounded-full bg-success\"></span>\n </span>\n {/snippet}\n <Button variant=\"outline\" color=\"success\" leadingSlot={statusSlot} label=\"Online\" />\n\n {#snippet badgeSlot()}\n <span\n class=\"flex size-5 items-center justify-center rounded-full bg-error text-xs font-bold text-on-error\"\n >3</span\n >\n {/snippet}\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n leadingIcon=\"lucide:bell\"\n trailingSlot={badgeSlot}\n label=\"Notifications\"\n />\n\n {#snippet kbdSlot()}\n <kbd\n class=\"rounded border border-outline-variant bg-surface-container px-1.5 py-0.5 font-mono text-xs text-on-surface-variant\"\n >&#8984;K</kbd\n >\n {/snippet}\n <Button\n variant=\"outline\"\n color=\"secondary\"\n leadingIcon=\"lucide:search\"\n trailingSlot={kbdSlot}\n label=\"Search\"\n />\n </div>\n </section>\n\n <!-- UI -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">ui</code> prop to\n override classes for specific slots:\n <code class=\"rounded bg-surface-container-highest px-1\">base</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">label</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">leadingIcon</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">trailingIcon</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">leadingAvatar</code>.\n </p>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Base slot</p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Pill Button\" class=\"rounded-full\" />\n <Button\n color=\"tertiary\"\n label=\"With Shadow\"\n ui={{ base: 'shadow-lg shadow-tertiary/30' }}\n />\n <Button\n label=\"Gradient\"\n ui={{\n base: 'bg-linear-to-r from-primary to-tertiary hover:from-primary/90 hover:to-tertiary/90'\n }}\n />\n <Button\n variant=\"outline\"\n color=\"warning\"\n label=\"Dashed Border\"\n ui={{ base: 'border-2 border-dashed ring-0' }}\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Label slot</p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Uppercase\" ui={{ label: 'uppercase tracking-wider' }} />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n label=\"Italic Text\"\n ui={{ label: 'italic' }}\n />\n <Button variant=\"soft\" color=\"success\" label=\"monospace\" ui={{ label: 'font-mono' }} />\n <Button\n variant=\"ghost\"\n label=\"Gradient Text\"\n ui={{\n label: 'bg-linear-to-r from-primary to-tertiary bg-clip-text text-transparent'\n }}\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Icon slots</p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n color=\"secondary\"\n leadingIcon=\"lucide:settings\"\n label=\"Hover Rotate\"\n ui={{ leadingIcon: 'transition-transform duration-300 group-hover:rotate-90' }}\n class=\"group\"\n />\n <Button\n variant=\"soft\"\n color=\"success\"\n leadingIcon=\"lucide:bell\"\n label=\"Bouncing\"\n ui={{ leadingIcon: 'animate-bounce' }}\n />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n leadingIcon=\"lucide:heart\"\n label=\"Colored Icon\"\n ui={{ leadingIcon: 'text-error' }}\n />\n <Button\n variant=\"solid\"\n color=\"info\"\n trailingIcon=\"lucide:arrow-right\"\n label=\"Hover Slide\"\n ui={{ trailingIcon: 'transition-transform duration-200 group-hover:translate-x-1' }}\n class=\"group\"\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 <!-- Action Bar -->\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\">Action bar</p>\n <div class=\"flex items-center gap-2\">\n <Button leadingIcon=\"lucide:save\" label=\"Save\" />\n <Button variant=\"outline\" color=\"secondary\" label=\"Cancel\" />\n <div class=\"flex-1\"></div>\n <Button variant=\"ghost\" color=\"error\" leadingIcon=\"lucide:trash-2\" label=\"Delete\" />\n </div>\n </div>\n\n <!-- Navigation -->\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\">Navigation</p>\n <div class=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n active\n activeColor=\"primary\"\n activeVariant=\"soft\"\n href=\"/button\"\n label=\"Dashboard\"\n leadingIcon=\"lucide:home\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n href=\"/link\"\n label=\"Links\"\n leadingIcon=\"lucide:link\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n href=\"/settings\"\n label=\"Settings\"\n leadingIcon=\"lucide:settings\"\n />\n </div>\n </div>\n\n <!-- Social Buttons -->\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\">Social buttons</p>\n <div class=\"flex items-center gap-2\">\n <Button variant=\"soft\" color=\"info\" leadingIcon=\"mdi:twitter\" label=\"Tweet\" />\n <Button variant=\"soft\" leadingIcon=\"mdi:facebook\" label=\"Share\" />\n <Button variant=\"soft\" color=\"tertiary\" leadingIcon=\"mdi:linkedin\" label=\"Post\" />\n </div>\n </div>\n\n <!-- Pagination -->\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\">Pagination</p>\n <div class=\"flex items-center gap-1\">\n <Button variant=\"ghost\" color=\"secondary\" icon=\"lucide:chevron-left\" square />\n <Button variant=\"ghost\" color=\"secondary\" label=\"1\" />\n <Button variant=\"solid\" label=\"2\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"3\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"...\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"10\" />\n <Button variant=\"ghost\" color=\"secondary\" icon=\"lucide:chevron-right\" square />\n </div>\n </div>\n </section>\n</div>\n",
85
+ "button": "<script lang=\"ts\">\n import { Button, Icon } 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\">Button</h1>\n <p class=\"text-on-surface-variant\">\n Use the Button component to trigger actions or navigate with\n <code class=\"rounded bg-surface-container-highest px-1\">href</code>. Supports multiple\n variants, colors, sizes, icons, avatars, and loading states.\n </p>\n <div\n class=\"rounded-lg border border-outline-variant bg-surface-container-high px-4 py-3 text-sm text-on-surface-variant\"\n >\n <strong class=\"text-on-surface\">Mode guide:</strong>\n omit <code class=\"rounded bg-surface-container-highest px-1\">href</code> for a native\n <code class=\"rounded bg-surface-container-highest px-1\">&lt;button&gt;</code> with form\n attributes like\n <code class=\"rounded bg-surface-container-highest px-1\">type</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">formaction</code>, and\n <code class=\"rounded bg-surface-container-highest px-1\">formmethod</code>. Add\n <code class=\"rounded bg-surface-container-highest px-1\">href</code> to render an anchor\n for navigation instead.\n </div>\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 the <code class=\"rounded bg-surface-container-highest px-1\">label</code> prop or the default\n slot to set the button text.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Button\" />\n </div>\n </section>\n\n <!-- Variant -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variant</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 to\n change the visual style. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">solid</code>.\n </p>\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 <Button {variant} label={variant[0].toUpperCase() + variant.slice(1)} />\n {/each}\n </div>\n </section>\n\n <!-- Color -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Color</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">color</code> prop to\n change the color scheme. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">primary</code>.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#each colors as color (color)}\n <Button {color} label={color[0].toUpperCase() + color.slice(1)} />\n {/each}\n </div>\n </section>\n\n <!-- Variant x Color Matrix -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variant & Color Matrix</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 <Button {variant} {color} label=\"Button\" />\n </td>\n {/each}\n </tr>\n {/each}\n </tbody>\n </table>\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 change the dimensions. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">md</code>.\n </p>\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 <Button {size} label={size.toUpperCase()} />\n {/each}\n </div>\n </section>\n\n <!-- Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Icon</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\n display an icon in the button. By default the icon is placed on the leading side. Use\n the\n <code class=\"rounded bg-surface-container-highest px-1\">trailing</code> prop to move it to\n the trailing side.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button icon=\"lucide:rocket\" label=\"Launch\" />\n <Button icon=\"lucide:arrow-right\" trailing label=\"Next\" />\n </div>\n </section>\n\n <!-- Leading & Trailing Icon -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Leading & Trailing Icon</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">leadingIcon</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">trailingIcon</code> to display icons\n on both sides simultaneously.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button leadingIcon=\"lucide:plus\" label=\"Add Item\" />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n trailingIcon=\"lucide:arrow-right\"\n label=\"Next\"\n />\n <Button\n variant=\"soft\"\n color=\"success\"\n leadingIcon=\"lucide:check\"\n trailingIcon=\"lucide:chevron-down\"\n label=\"Confirm\"\n />\n </div>\n </section>\n\n <!-- Square (Icon Only) -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Square</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">square</code> prop to\n force equal width and height — ideal for icon-only buttons. When only\n <code class=\"rounded bg-surface-container-highest px-1\">icon</code> is provided without\n <code class=\"rounded bg-surface-container-highest px-1\">label</code>, the button is\n automatically treated as icon-only.\n </p>\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 <Button {size} icon=\"lucide:plus\" square />\n {/each}\n <span class=\"mx-2 text-on-surface-variant\">|</span>\n <Button variant=\"outline\" color=\"secondary\" icon=\"lucide:settings\" square />\n <Button variant=\"soft\" color=\"success\" icon=\"lucide:check\" square />\n <Button variant=\"subtle\" color=\"warning\" icon=\"lucide:alert-triangle\" square />\n <Button variant=\"ghost\" color=\"error\" icon=\"lucide:x\" square />\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\">avatar</code> prop to\n display an avatar before the label. Takes precedence over\n <code class=\"rounded bg-surface-container-highest px-1\">leadingIcon</code>.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"soft\"\n avatar={{ src: 'https://i.pravatar.cc/150?img=1', alt: 'John' }}\n label=\"John Doe\"\n />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n avatar={{ src: 'https://i.pravatar.cc/150?img=2', alt: 'Jane' }}\n label=\"Jane Smith\"\n />\n <Button variant=\"solid\" color=\"tertiary\" avatar={{ alt: 'New' }} label=\"New User\" />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n avatar={{ src: 'https://i.pravatar.cc/150?img=5', alt: 'User' }}\n label=\"Profile\"\n trailingIcon=\"lucide:chevron-down\"\n />\n </div>\n\n <p class=\"text-sm text-on-surface-variant\">\n The avatar size automatically adapts to the button size.\n </p>\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 <Button\n variant=\"soft\"\n {size}\n avatar={{ src: 'https://i.pravatar.cc/150?img=3', alt: 'User' }}\n label={size.toUpperCase()}\n />\n {/each}\n </div>\n </section>\n\n <!-- Loading -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Loading</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">loading</code> prop to\n show a loading spinner and disable the button. The spinner replaces the leading icon by\n default. Use\n <code class=\"rounded bg-surface-container-highest px-1\">trailing</code> to place it on the\n trailing side.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button loading label=\"Loading...\" />\n <Button variant=\"outline\" color=\"secondary\" loading label=\"Processing\" />\n <Button variant=\"soft\" color=\"success\" loading icon=\"lucide:loader-2\" square />\n <Button variant=\"solid\" color=\"info\" loading trailing label=\"Uploading\" />\n </div>\n </section>\n\n <!-- Loading Auto -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Loading Auto</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">loadingAuto</code> prop\n to automatically show loading state while the\n <code class=\"rounded bg-surface-container-highest px-1\">onclick</code> handler's Promise is\n pending. The button is disabled until the Promise resolves or rejects.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n loadingAuto\n label=\"Save (2s)\"\n leadingIcon=\"lucide:save\"\n onclick={() => new Promise((r) => setTimeout(r, 2000))}\n />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n loadingAuto\n label=\"Submit (3s)\"\n onclick={() => new Promise((r) => setTimeout(r, 3000))}\n />\n <Button\n variant=\"soft\"\n color=\"error\"\n loadingAuto\n label=\"Delete (1s)\"\n leadingIcon=\"lucide:trash-2\"\n onclick={() => new Promise((r) => setTimeout(r, 1000))}\n />\n </div>\n </section>\n\n <!-- Disabled -->\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 disable\n the button and prevent interaction.\n </p>\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 <Button {variant} disabled label=\"Disabled\" />\n {/each}\n </div>\n </section>\n\n <!-- Block -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Block</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">block</code> prop to stretch\n the button to fill the full width of its container.\n </p>\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <Button block label=\"Full Width Button\" />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n block\n leadingIcon=\"lucide:mail\"\n trailingIcon=\"lucide:arrow-right\"\n label=\"Send Email\"\n />\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 <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">href</code> prop to\n render as an anchor element. External URLs are auto-detected with\n <code class=\"rounded bg-surface-container-highest px-1\">target=\"_blank\"</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">rel=\"noopener noreferrer\"</code\n >. Use the\n <code class=\"rounded bg-surface-container-highest px-1\">external</code> prop to force this\n behavior on internal paths.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button href=\"/link\" label=\"Internal Link\" />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n href=\"https://svelte.dev\"\n label=\"External Link\"\n trailingIcon=\"lucide:external-link\"\n />\n <Button\n variant=\"soft\"\n color=\"info\"\n href=\"/button\"\n label=\"Current Page\"\n leadingIcon=\"lucide:link\"\n />\n <Button variant=\"ghost\" color=\"error\" href=\"/about\" disabled label=\"Disabled Link\" />\n </div>\n </section>\n\n <!-- Type -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Type</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">type</code> prop to set\n the button type attribute. Defaults to\n <code class=\"rounded bg-surface-container-highest px-1\">button</code>. Only applies when\n rendering as a &lt;button&gt; element (no\n <code class=\"rounded bg-surface-container-highest px-1\">href</code>).\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Button (default)\" />\n <Button variant=\"outline\" color=\"success\" type=\"submit\" label=\"Submit\" />\n <Button variant=\"ghost\" color=\"secondary\" type=\"reset\" label=\"Reset\" />\n </div>\n </section>\n\n <!-- Native Form -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Native Form Submit</h2>\n <p class=\"text-sm text-on-surface-variant\">\n When <code class=\"rounded bg-surface-container-highest px-1\">href</code> is omitted,\n <code class=\"rounded bg-surface-container-highest px-1\">Button</code> renders a native\n <code class=\"rounded bg-surface-container-highest px-1\">&lt;button&gt;</code> element. This\n means browser form semantics such as\n <code class=\"rounded bg-surface-container-highest px-1\">type</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">formaction</code>, and\n <code class=\"rounded bg-surface-container-highest px-1\">formmethod</code> work as expected.\n </p>\n <form class=\"space-y-3 rounded-lg bg-surface-container-high p-4\" onsubmit={(event) => event.preventDefault()}>\n <div class=\"grid gap-3 sm:grid-cols-2\">\n <label class=\"grid gap-1 text-sm\">\n <span class=\"text-on-surface-variant\">Phone</span>\n <input\n class=\"rounded-lg border border-outline-variant bg-surface px-3 py-2\"\n placeholder=\"081-234-5678\"\n />\n </label>\n <label class=\"grid gap-1 text-sm\">\n <span class=\"text-on-surface-variant\">Code</span>\n <input\n class=\"rounded-lg border border-outline-variant bg-surface px-3 py-2\"\n placeholder=\"123456\"\n />\n </label>\n </div>\n <div class=\"flex flex-wrap gap-3\">\n <Button\n type=\"submit\"\n formaction=\"?/sendOtp\"\n formmethod=\"post\"\n variant=\"outline\"\n color=\"secondary\"\n label=\"Send Code\"\n />\n <Button\n type=\"submit\"\n formaction=\"?/login\"\n formmethod=\"post\"\n label=\"Sign In\"\n />\n </div>\n </form>\n </section>\n\n <!-- Mode Notes -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Mode Notes</h2>\n <div class=\"overflow-x-auto rounded-lg bg-surface-container-high p-4\">\n <table class=\"w-full text-sm\">\n <thead>\n <tr class=\"border-b border-outline-variant text-left text-on-surface-variant\">\n <th class=\"px-2 py-2 font-medium\">Mode</th>\n <th class=\"px-2 py-2 font-medium\">Rendered element</th>\n <th class=\"px-2 py-2 font-medium\">Use for</th>\n <th class=\"px-2 py-2 font-medium\">Important props</th>\n </tr>\n </thead>\n <tbody>\n <tr class=\"border-b border-outline-variant/50 align-top\">\n <td class=\"px-2 py-3 font-medium text-on-surface\">Button mode</td>\n <td class=\"px-2 py-3\">\n <code class=\"rounded bg-surface-container-highest px-1\"\n >&lt;button&gt;</code\n >\n </td>\n <td class=\"px-2 py-3\">Actions, submit, reset, and native form behavior</td>\n <td class=\"px-2 py-3\">\n <code class=\"rounded bg-surface-container-highest px-1\">type</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">name</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">value</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">formaction</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">formmethod</code>\n </td>\n </tr>\n <tr class=\"align-top\">\n <td class=\"px-2 py-3 font-medium text-on-surface\">Link mode</td>\n <td class=\"px-2 py-3\">\n <code class=\"rounded bg-surface-container-highest px-1\">&lt;a&gt;</code>\n </td>\n <td class=\"px-2 py-3\">Navigation to internal or external destinations</td>\n <td class=\"px-2 py-3\">\n <code class=\"rounded bg-surface-container-highest px-1\">href</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">target</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">rel</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">download</code>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </section>\n\n <!-- Active Color & Variant -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Active Color & Variant</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use\n <code class=\"rounded bg-surface-container-highest px-1\">activeColor</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">activeVariant</code> to change\n the button appearance when\n <code class=\"rounded bg-surface-container-highest px-1\">active</code> is true. Falls\n back to the default\n <code class=\"rounded bg-surface-container-highest px-1\">color</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">variant</code> when inactive.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n active\n activeColor=\"primary\"\n activeVariant=\"solid\"\n label=\"Dashboard\"\n leadingIcon=\"lucide:home\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n label=\"Settings\"\n leadingIcon=\"lucide:settings\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n active\n activeColor=\"error\"\n activeVariant=\"soft\"\n label=\"Alerts\"\n leadingIcon=\"lucide:bell\"\n />\n <Button variant=\"ghost\" color=\"secondary\" label=\"Profile\" leadingIcon=\"lucide:user\" />\n </div>\n </section>\n\n <!-- Children Slot -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Children</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the default slot to render custom content inside the button instead of the\n <code class=\"rounded bg-surface-container-highest px-1\">label</code> prop.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button>\n <Icon name=\"lucide:sparkles\" size=\"16\" />\n <span>Custom Content</span>\n </Button>\n <Button variant=\"outline\" color=\"tertiary\">\n <span class=\"font-bold\">Bold</span>\n <span class=\"font-light\">& Light</span>\n </Button>\n </div>\n </section>\n\n <!-- Leading & Trailing Slots -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Leading & Trailing Slots</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use <code class=\"rounded bg-surface-container-highest px-1\">leadingSlot</code> and\n <code class=\"rounded bg-surface-container-highest px-1\">trailingSlot</code> for fully\n custom leading/trailing content. They take precedence over\n <code class=\"rounded bg-surface-container-highest px-1\">avatar</code> and icon props.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n {#snippet statusSlot()}\n <span class=\"relative flex size-2\">\n <span\n class=\"absolute inline-flex size-full animate-ping rounded-full bg-success opacity-75\"\n ></span>\n <span class=\"relative inline-flex size-2 rounded-full bg-success\"></span>\n </span>\n {/snippet}\n <Button variant=\"outline\" color=\"success\" leadingSlot={statusSlot} label=\"Online\" />\n\n {#snippet badgeSlot()}\n <span\n class=\"flex size-5 items-center justify-center rounded-full bg-error text-xs font-bold text-on-error\"\n >3</span\n >\n {/snippet}\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n leadingIcon=\"lucide:bell\"\n trailingSlot={badgeSlot}\n label=\"Notifications\"\n />\n\n {#snippet kbdSlot()}\n <kbd\n class=\"rounded border border-outline-variant bg-surface-container px-1.5 py-0.5 font-mono text-xs text-on-surface-variant\"\n >&#8984;K</kbd\n >\n {/snippet}\n <Button\n variant=\"outline\"\n color=\"secondary\"\n leadingIcon=\"lucide:search\"\n trailingSlot={kbdSlot}\n label=\"Search\"\n />\n </div>\n </section>\n\n <!-- UI -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">UI</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Use the <code class=\"rounded bg-surface-container-highest px-1\">ui</code> prop to\n override classes for specific slots:\n <code class=\"rounded bg-surface-container-highest px-1\">base</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">label</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">leadingIcon</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">trailingIcon</code>,\n <code class=\"rounded bg-surface-container-highest px-1\">leadingAvatar</code>.\n </p>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Base slot</p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Pill Button\" class=\"rounded-full\" />\n <Button\n color=\"tertiary\"\n label=\"With Shadow\"\n ui={{ base: 'shadow-lg shadow-tertiary/30' }}\n />\n <Button\n label=\"Gradient\"\n ui={{\n base: 'bg-linear-to-r from-primary to-tertiary hover:from-primary/90 hover:to-tertiary/90'\n }}\n />\n <Button\n variant=\"outline\"\n color=\"warning\"\n label=\"Dashed Border\"\n ui={{ base: 'border-2 border-dashed ring-0' }}\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Label slot</p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button label=\"Uppercase\" ui={{ label: 'uppercase tracking-wider' }} />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n label=\"Italic Text\"\n ui={{ label: 'italic' }}\n />\n <Button variant=\"soft\" color=\"success\" label=\"monospace\" ui={{ label: 'font-mono' }} />\n <Button\n variant=\"ghost\"\n label=\"Gradient Text\"\n ui={{\n label: 'bg-linear-to-r from-primary to-tertiary bg-clip-text text-transparent'\n }}\n />\n </div>\n\n <p class=\"text-xs font-medium text-on-surface-variant uppercase\">Icon slots</p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n variant=\"outline\"\n color=\"secondary\"\n leadingIcon=\"lucide:settings\"\n label=\"Hover Rotate\"\n ui={{ leadingIcon: 'transition-transform duration-300 group-hover:rotate-90' }}\n class=\"group\"\n />\n <Button\n variant=\"soft\"\n color=\"success\"\n leadingIcon=\"lucide:bell\"\n label=\"Bouncing\"\n ui={{ leadingIcon: 'animate-bounce' }}\n />\n <Button\n variant=\"outline\"\n color=\"secondary\"\n leadingIcon=\"lucide:heart\"\n label=\"Colored Icon\"\n ui={{ leadingIcon: 'text-error' }}\n />\n <Button\n variant=\"solid\"\n color=\"info\"\n trailingIcon=\"lucide:arrow-right\"\n label=\"Hover Slide\"\n ui={{ trailingIcon: 'transition-transform duration-200 group-hover:translate-x-1' }}\n class=\"group\"\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 <!-- Action Bar -->\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\">Action bar</p>\n <div class=\"flex items-center gap-2\">\n <Button leadingIcon=\"lucide:save\" label=\"Save\" />\n <Button variant=\"outline\" color=\"secondary\" label=\"Cancel\" />\n <div class=\"flex-1\"></div>\n <Button variant=\"ghost\" color=\"error\" leadingIcon=\"lucide:trash-2\" label=\"Delete\" />\n </div>\n </div>\n\n <!-- Navigation -->\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\">Navigation</p>\n <div class=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n active\n activeColor=\"primary\"\n activeVariant=\"soft\"\n href=\"/button\"\n label=\"Dashboard\"\n leadingIcon=\"lucide:home\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n href=\"/link\"\n label=\"Links\"\n leadingIcon=\"lucide:link\"\n />\n <Button\n variant=\"ghost\"\n color=\"secondary\"\n href=\"/settings\"\n label=\"Settings\"\n leadingIcon=\"lucide:settings\"\n />\n </div>\n </div>\n\n <!-- Social Buttons -->\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\">Social buttons</p>\n <div class=\"flex items-center gap-2\">\n <Button variant=\"soft\" color=\"info\" leadingIcon=\"mdi:twitter\" label=\"Tweet\" />\n <Button variant=\"soft\" leadingIcon=\"mdi:facebook\" label=\"Share\" />\n <Button variant=\"soft\" color=\"tertiary\" leadingIcon=\"mdi:linkedin\" label=\"Post\" />\n </div>\n </div>\n\n <!-- Pagination -->\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\">Pagination</p>\n <div class=\"flex items-center gap-1\">\n <Button variant=\"ghost\" color=\"secondary\" icon=\"lucide:chevron-left\" square />\n <Button variant=\"ghost\" color=\"secondary\" label=\"1\" />\n <Button variant=\"solid\" label=\"2\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"3\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"...\" />\n <Button variant=\"ghost\" color=\"secondary\" label=\"10\" />\n <Button variant=\"ghost\" color=\"secondary\" icon=\"lucide:chevron-right\" square />\n </div>\n </div>\n </section>\n</div>\n",
77
86
  "code-block": "<script lang=\"ts\">\n import { CodeBlock } from '$lib/index.js'\n import { renderHighlightedCode } from '$lib/docs/code-block.js'\n\n const sampleCode = `<script lang=\"ts\">\n import { Button } from 'svelora';\n<` + `/script>\n\n<Button label=\"Hello\" />`\n\n const sampleTs = `type User = { _id: string; name: string }\n\nconst user: User = { _id: 'u_1', name: 'Jane' }\nconsole.log(user)`\n\n let shikiHtml = $state('')\n let isDarkMode = $state(true)\n\n $effect(() => {\n if (typeof document === 'undefined') return\n\n const root = document.documentElement\n const updateMode = () => {\n isDarkMode = root.classList.contains('dark')\n }\n\n updateMode()\n\n const observer = new MutationObserver(() => {\n updateMode()\n })\n\n observer.observe(root, { attributes: true, attributeFilter: ['class'] })\n return () => observer.disconnect()\n })\n\n $effect(() => {\n const darkMode = isDarkMode\n let cancelled = false\n\n void renderHighlightedCode(sampleCode, darkMode).then((html) => {\n if (cancelled) return\n shikiHtml = html\n })\n\n return () => {\n cancelled = true\n }\n })\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">CodeBlock</h1>\n <p class=\"text-on-surface-variant\">\n Display code snippets with a consistent header, copy button, and theme-aware styling.\n </p>\n </div>\n\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 <CodeBlock title=\"Code\" code={sampleTs} />\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">With highlighted HTML</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Provide pre-highlighted HTML (for example, from Shiki) via the <code>html</code> prop.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock code={sampleCode} html={shikiHtml} />\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Variants</h2>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"outline\" variant=\"outline\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"soft\" variant=\"soft\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"subtle\" variant=\"subtle\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"solid\" variant=\"solid\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"ghost\" variant=\"ghost\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"none\" variant=\"none\" code={sampleTs} />\n </div>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Sizes</h2>\n <div class=\"grid gap-3 md:grid-cols-3\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"sm\" size=\"sm\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"md\" size=\"md\" code={sampleTs} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"lg\" size=\"lg\" code={sampleTs} />\n </div>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Copy controls</h2>\n <div class=\"grid gap-3 md:grid-cols-2\">\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"copyable=false\" code={sampleTs} copyable={false} />\n </div>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <CodeBlock title=\"custom copyText\" code=\"Visible code\" copyText=\"Copied text\" />\n </div>\n </div>\n </section>\n</div>\n\n",
78
87
  "field-group": "<script lang=\"ts\">\n import { Button, Input, FieldGroup, Separator } from '$lib/index.js'\n\n const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const\n</script>\n\n<div class=\"space-y-8\">\n <h1 class=\"text-2xl font-bold text-on-surface\">FieldGroup</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 multiple <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >Button</code\n >\n within a\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >FieldGroup</code\n > to group them together.\n </p>\n <FieldGroup>\n <Button label=\"Action\" />\n <Button label=\"Action\" />\n <Button label=\"Action\" />\n </FieldGroup>\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=\"flex flex-wrap items-start gap-6\">\n <div class=\"space-y-2\">\n <p class=\"text-xs text-on-surface-variant\">horizontal (default)</p>\n <FieldGroup orientation=\"horizontal\">\n <Button label=\"Left\" />\n <Button label=\"Center\" />\n <Button label=\"Right\" />\n </FieldGroup>\n </div>\n <div class=\"space-y-2\">\n <p class=\"text-xs text-on-surface-variant\">vertical</p>\n <FieldGroup orientation=\"vertical\">\n <Button label=\"Top\" />\n <Button label=\"Middle\" />\n <Button label=\"Bottom\" />\n </FieldGroup>\n </div>\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 set the size of all child components.\n </p>\n <div class=\"flex flex-wrap items-end gap-6\">\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <FieldGroup {size}>\n <Button label=\"Action\" />\n <Button label=\"Action\" />\n <Button label=\"Action\" />\n </FieldGroup>\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\n </div>\n </section>\n\n <!-- Variants -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">With Variants</h2>\n <p class=\"text-sm text-on-surface-variant\">\n FieldGroup works with different button variants.\n </p>\n <div class=\"flex flex-wrap items-center gap-6\">\n <FieldGroup>\n <Button variant=\"outline\" color=\"surface\" label=\"Left\" />\n <Button variant=\"outline\" color=\"surface\" label=\"Center\" />\n <Button variant=\"outline\" color=\"surface\" label=\"Right\" />\n </FieldGroup>\n\n <FieldGroup>\n <Button variant=\"soft\" label=\"Left\" />\n <Button variant=\"soft\" label=\"Center\" />\n <Button variant=\"soft\" label=\"Right\" />\n </FieldGroup>\n\n <FieldGroup>\n <Button variant=\"subtle\" label=\"Left\" />\n <Button variant=\"subtle\" label=\"Center\" />\n <Button variant=\"subtle\" label=\"Right\" />\n </FieldGroup>\n </div>\n </section>\n\n <!-- With Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">With Icons</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Icon-only button groups for toolbar-style layouts.\n </p>\n <div class=\"flex flex-wrap items-center gap-6\">\n <FieldGroup>\n <Button icon=\"lucide:align-left\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:align-center\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:align-right\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:align-justify\" variant=\"outline\" color=\"surface\" />\n </FieldGroup>\n\n <FieldGroup>\n <Button icon=\"lucide:bold\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:italic\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:underline\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:strikethrough\" variant=\"outline\" color=\"surface\" />\n </FieldGroup>\n </div>\n </section>\n\n <!-- Vertical with Icons -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Vertical with Icons</h2>\n <div class=\"flex flex-wrap items-start gap-6\">\n <FieldGroup orientation=\"vertical\">\n <Button icon=\"lucide:zoom-in\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:zoom-out\" variant=\"outline\" color=\"surface\" />\n <Button icon=\"lucide:rotate-cw\" variant=\"outline\" color=\"surface\" />\n </FieldGroup>\n\n <FieldGroup orientation=\"vertical\">\n <Button leadingIcon=\"lucide:home\" label=\"Home\" variant=\"soft\" />\n <Button leadingIcon=\"lucide:settings\" label=\"Settings\" variant=\"soft\" />\n <Button leadingIcon=\"lucide:user\" label=\"Profile\" variant=\"soft\" />\n </FieldGroup>\n </div>\n </section>\n\n <!-- With Input -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">With Input</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Combine <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\"\n >Input</code\n >\n and\n <code class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">Button</code> in\n a FieldGroup.\n </p>\n <div class=\"flex flex-wrap items-start gap-6\">\n <FieldGroup>\n <Input leadingIcon=\"lucide:search\" placeholder=\"Search...\" />\n <Button label=\"Search\" />\n </FieldGroup>\n\n <FieldGroup>\n <Input placeholder=\"Enter email\" type=\"email\" />\n <Button label=\"Subscribe\" variant=\"solid\" color=\"primary\" />\n </FieldGroup>\n </div>\n </section>\n\n <!-- With Input (Vertical) -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">With Input (Vertical)</h2>\n <div class=\"flex flex-wrap items-start gap-6\">\n <div class=\"w-72\">\n <FieldGroup orientation=\"vertical\">\n <Input placeholder=\"First name\" />\n <Input placeholder=\"Last name\" />\n <Input placeholder=\"Email\" type=\"email\" />\n </FieldGroup>\n </div>\n\n <div class=\"w-72\">\n <FieldGroup orientation=\"vertical\">\n <Input leadingIcon=\"lucide:user\" placeholder=\"Username\" />\n <Input leadingIcon=\"lucide:lock\" placeholder=\"Password\" type=\"password\" />\n <Button label=\"Sign in\" block />\n </FieldGroup>\n </div>\n </div>\n </section>\n\n <!-- Input + Button Sizes -->\n <section class=\"space-y-4\">\n <h2 class=\"text-lg font-semibold text-on-surface\">Input + Button Sizes</h2>\n <p class=\"text-sm text-on-surface-variant\">\n FieldGroup propagates <code\n class=\"rounded bg-surface-container-highest px-1.5 py-0.5 text-xs\">size</code\n > to all children.\n </p>\n <div class=\"flex flex-wrap items-end gap-6\">\n {#each sizes as size (size)}\n <div class=\"flex flex-col items-center gap-2\">\n <FieldGroup {size}>\n <Input placeholder=\"Search...\" />\n <Button icon=\"lucide:search\" />\n </FieldGroup>\n <span class=\"text-xs text-on-surface-variant\">{size}</span>\n </div>\n {/each}\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\">Text editor toolbar</p>\n <div class=\"flex flex-wrap items-center gap-3\">\n <FieldGroup>\n <Button icon=\"lucide:bold\" variant=\"outline\" color=\"surface\" size=\"sm\" />\n <Button icon=\"lucide:italic\" variant=\"outline\" color=\"surface\" size=\"sm\" />\n <Button\n icon=\"lucide:underline\"\n variant=\"outline\"\n color=\"surface\"\n size=\"sm\"\n />\n </FieldGroup>\n <FieldGroup>\n <Button\n icon=\"lucide:align-left\"\n variant=\"outline\"\n color=\"surface\"\n size=\"sm\"\n />\n <Button\n icon=\"lucide:align-center\"\n variant=\"outline\"\n color=\"surface\"\n size=\"sm\"\n />\n <Button\n icon=\"lucide:align-right\"\n variant=\"outline\"\n color=\"surface\"\n size=\"sm\"\n />\n </FieldGroup>\n <FieldGroup>\n <Button icon=\"lucide:list\" variant=\"outline\" color=\"surface\" size=\"sm\" />\n <Button\n icon=\"lucide:list-ordered\"\n variant=\"outline\"\n color=\"surface\"\n size=\"sm\"\n />\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 class=\"w-80\">\n <FieldGroup orientation=\"vertical\">\n <Input leadingIcon=\"lucide:mail\" placeholder=\"Email\" type=\"email\" />\n <Input leadingIcon=\"lucide:lock\" placeholder=\"Password\" type=\"password\" />\n <Button label=\"Sign in\" variant=\"solid\" color=\"primary\" block />\n </FieldGroup>\n </div>\n </div>\n\n <div>\n <p class=\"mb-2 text-xs text-on-surface-variant\">Search with filter</p>\n <FieldGroup>\n <Button label=\"All\" variant=\"outline\" color=\"surface\" />\n <Input leadingIcon=\"lucide:search\" placeholder=\"Search products...\" />\n <Button icon=\"lucide:sliders-horizontal\" variant=\"outline\" color=\"surface\" />\n </FieldGroup>\n </div>\n </div>\n </section>\n</div>\n",
79
88
  "fonts": "<script lang=\"ts\">\n import { Card, CodeBlock, Fonts, defaultFontFamilies, fontsDefaults } from '$lib/index.js'\n import { renderHighlightedCode } from '$lib/docs/code-block.js'\n import type { FontDefinition } from '$lib/index.js'\n\n const googleFamilies: FontDefinition[] = [\n {\n name: 'Inter',\n variable: '--font-sans-family',\n weights: [400, 500, 600, 700]\n },\n {\n name: 'Poppins',\n variable: '--font-heading-family',\n weights: [600, 700]\n },\n {\n name: 'JetBrains Mono',\n variable: '--font-mono-family',\n weights: [400, 500, 700]\n }\n ]\n\n const defaultPresetCode = `<Fonts />`\n\n const defaultPresetItems = [\n {\n variable: '--font-sans-family',\n family: 'Inter',\n weights: '400, 500, 600, 700',\n utility: 'font-sans'\n },\n {\n variable: '--font-heading-family',\n family: 'Inter',\n weights: '400, 500, 600, 700',\n utility: 'font-heading'\n },\n {\n variable: '--font-mono-family',\n family: 'JetBrains Mono',\n weights: '400, 500, 700',\n utility: 'font-mono'\n }\n ] as const\n\n const localSetupSteps = [\n 'Add your files under `static/fonts/*` so they are served from the app root.',\n 'Map each file with `provider: \"local\"` and `sources` entries for the weights/styles you need.',\n 'Bind the family to a CSS variable such as `--font-sarabun-family`.',\n 'Use the mapped variable through utility classes like `font-sarabun`, `font-heading`, or `font-mono`.'\n ] as const\n\n const localLayoutCode = `<script lang=\"ts\">\n import 'svelora/theme.css';\n import { Fonts } from 'svelora';\n\n let { children } = $props();\n<` + `/script>\n\n<Fonts\n families={[\n {\n provider: 'local',\n name: 'Sarabun',\n variable: '--font-sarabun-family',\n sources: [\n { src: '/fonts/Sarabun-Regular.woff2', format: 'woff2', weight: 400 },\n { src: '/fonts/Sarabun-Medium.woff2', format: 'woff2', weight: 500 },\n { src: '/fonts/Sarabun-Bold.woff2', format: 'woff2', weight: 700 }\n ]\n }\n ]}\n/>\n\n<main class=\"font-sarabun\">\n {@render children?.()}\n</main>`\n\n const familyReference = [\n {\n name: 'provider',\n type: `'google' | 'local'`,\n description: 'Selects whether the family loads from Google Fonts or local assets.'\n },\n {\n name: 'name',\n type: 'string',\n description: 'Font family name used in the request or `@font-face` declaration.'\n },\n {\n name: 'variable',\n type: '`--${string}`',\n description: 'CSS custom property that stores the generated font-family stack.'\n },\n {\n name: 'fallback',\n type: 'string',\n description: 'Optional fallback stack appended after the primary font family.'\n },\n {\n name: 'weights',\n type: 'number[]',\n description: 'Google-only list of weights to request.'\n },\n {\n name: 'styles',\n type: `('normal' | 'italic')[]`,\n description: 'Google-only styles to request.'\n },\n {\n name: 'sources',\n type: 'LocalFontSource[]',\n description: 'Local-only file definitions used to build `@font-face` rules.'\n }\n ] as const\n\n const optionsReference = [\n {\n name: 'families',\n type: 'FontDefinition[]',\n description: 'Complete list of families managed by the provider.'\n },\n {\n name: 'display',\n type: `'auto' | 'block' | 'swap' | 'fallback' | 'optional'`,\n description: 'Controls `font-display` for Google requests and local `@font-face` output.'\n },\n {\n name: 'preconnect',\n type: 'boolean',\n description: 'Adds preconnect links for Google Fonts when enabled.'\n }\n ] as const\n\n const localSourceReference = [\n {\n name: 'src',\n type: 'string',\n description: 'Public URL to the local font file, for example `/fonts/Sarabun-Regular.woff2`.'\n },\n {\n name: 'format',\n type: `'woff2' | 'woff' | 'truetype' | 'opentype' | 'embedded-opentype' | 'svg'`,\n description: 'Optional format hint used in `@font-face`.'\n },\n {\n name: 'weight',\n type: 'number | `${number} ${number}`',\n description: 'Single weight or variable font range.'\n },\n {\n name: 'style',\n type: `'normal' | 'italic'`,\n description: 'Font style for the source entry.'\n },\n {\n name: 'unicodeRange',\n type: 'string',\n description: 'Optional unicode-range for partial subsets.'\n }\n ] as const\n\n const localProviderCode = `<Fonts\n families={[\n {\n provider: 'local',\n name: 'Sarabun',\n variable: '--font-sarabun-family',\n sources: [\n { src: '/fonts/Sarabun-Regular.woff2', format: 'woff2', weight: 400 },\n { src: '/fonts/Sarabun-Bold.woff2', format: 'woff2', weight: 700 }\n ]\n }\n ]}\n/>`\n\n const mixedProviderCode = `<Fonts\n families={[\n { name: 'Inter', variable: '--font-sans-family', weights: [400, 500, 600, 700] },\n { name: 'Poppins', variable: '--font-heading-family', weights: [600, 700] },\n {\n provider: 'local',\n name: 'Sarabun',\n variable: '--font-sarabun-family',\n sources: [{ src: '/fonts/Sarabun-Regular.woff2', format: 'woff2', weight: 400 }]\n }\n ]}\n/>`\n\n const configCode = `import { defineConfig } from 'svelora';\n\ndefineConfig({\n fonts: {\n families: [\n { name: 'Inter', variable: '--font-sans-family', weights: [400, 500, 600, 700] },\n { name: 'Poppins', variable: '--font-heading-family', weights: [600, 700] },\n {\n provider: 'local',\n name: 'Sarabun',\n variable: '--font-sarabun-family',\n sources: [\n { src: '/fonts/Sarabun-Regular.woff2', format: 'woff2', weight: 400 },\n { src: '/fonts/Sarabun-Bold.woff2', format: 'woff2', weight: 700 }\n ]\n }\n ]\n }\n});`\n\n const layoutCode = `<script lang=\"ts\">\n import 'svelora/theme.css';\n import { Fonts, ModeWatcher } from 'svelora';\n\n let { children } = $props();\n<` + `/script>\n\n<Fonts />\n<ModeWatcher />\n{@render children?.()}`\n\n const disableDefaultsCode = `import { defineConfig } from 'svelora';\n\ndefineConfig({\n fonts: false\n});`\n\n let isDarkMode = $state(true)\n let defaultPresetHtml = $state('')\n let localProviderHtml = $state('')\n let localLayoutHtml = $state('')\n let mixedProviderHtml = $state('')\n let configHtml = $state('')\n let layoutHtml = $state('')\n let disableDefaultsHtml = $state('')\n\n $effect(() => {\n if (typeof document === 'undefined') return\n\n const root = document.documentElement\n const updateMode = () => {\n isDarkMode = root.classList.contains('dark')\n }\n\n updateMode()\n\n const observer = new MutationObserver(() => {\n updateMode()\n })\n\n observer.observe(root, { attributes: true, attributeFilter: ['class'] })\n return () => observer.disconnect()\n })\n\n $effect(() => {\n const darkMode = isDarkMode\n let cancelled = false\n\n const tasks = [\n renderHighlightedCode(defaultPresetCode, darkMode).then((html) => {\n if (!cancelled) defaultPresetHtml = html\n }),\n renderHighlightedCode(localProviderCode, darkMode).then((html) => {\n if (!cancelled) localProviderHtml = html\n }),\n renderHighlightedCode(localLayoutCode, darkMode).then((html) => {\n if (!cancelled) localLayoutHtml = html\n }),\n renderHighlightedCode(mixedProviderCode, darkMode).then((html) => {\n if (!cancelled) mixedProviderHtml = html\n }),\n renderHighlightedCode(configCode, darkMode).then((html) => {\n if (!cancelled) configHtml = html\n }),\n renderHighlightedCode(layoutCode, darkMode).then((html) => {\n if (!cancelled) layoutHtml = html\n }),\n renderHighlightedCode(disableDefaultsCode, darkMode).then((html) => {\n if (!cancelled) disableDefaultsHtml = html\n })\n ]\n\n void Promise.all(tasks)\n\n return () => {\n cancelled = true\n }\n })\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">Fonts</h1>\n <p class=\"text-on-surface-variant\">\n A unified font provider for Svelora that supports both Google Fonts and local font files\n through the same `fonts.families` API.\n </p>\n </div>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Default Preset</h2>\n <p class=\"text-sm text-on-surface-variant\">\n If you render `<Fonts />` without props or custom config, Svelora uses the built-in preset\n below. The current default display is `{fontsDefaults.display}` with preconnect set to\n `{fontsDefaults.preconnect ? 'true' : 'false'}`.\n </p>\n <CodeBlock title=\"Default Preset\" code={defaultPresetCode} html={defaultPresetHtml} />\n <div class=\"grid gap-4 md:grid-cols-3\">\n {#each defaultPresetItems as item (item.variable)}\n <Card class=\"border border-outline-variant/70\">\n <div class=\"space-y-2\">\n <p class=\"font-mono text-xs text-on-surface-variant\">{item.variable}</p>\n <h3 class=\"text-base font-semibold\">{item.family}</h3>\n <p class=\"text-sm text-on-surface-variant\">Weights: {item.weights}</p>\n <p class=\"text-sm text-on-surface-variant\">Utility: `{item.utility}`</p>\n </div>\n </Card>\n {/each}\n </div>\n <p class=\"text-sm text-on-surface-variant\">\n The preset is sourced from `defaultFontFamilies` and currently includes\n `{defaultFontFamilies.length}` families.\n </p>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Google Provider</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Fonts families={googleFamilies} />\n <h3 class=\"font-heading text-2xl font-semibold\">Poppins heading</h3>\n <p>Inter body copy is mapped to `--font-sans-family`.</p>\n <p class=\"font-mono mt-2 text-sm\">const provider = 'google';</p>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Local Provider</h2>\n <CodeBlock title=\"Local Provider\" code={localProviderCode} html={localProviderHtml} />\n <p class=\"text-sm text-on-surface-variant\">\n Put your font files under `static/fonts/*`, map them to a CSS variable, then use utility\n classes like `font-sarabun` across the project.\n </p>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Local Font Setup</h2>\n <div class=\"grid gap-3 md:grid-cols-2\">\n {#each localSetupSteps as step, index (`${index}-${step}`)}\n <Card class=\"border border-outline-variant/70\">\n <div class=\"space-y-2\">\n <p class=\"text-sm font-medium text-primary\">Step {index + 1}</p>\n <p class=\"text-sm text-on-surface-variant\">{step}</p>\n </div>\n </Card>\n {/each}\n </div>\n <CodeBlock title=\"Local Font Setup\" code={localLayoutCode} html={localLayoutHtml} />\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Mixed Providers</h2>\n <CodeBlock title=\"Mixed Providers\" code={mixedProviderCode} html={mixedProviderHtml} />\n <p class=\"text-sm text-on-surface-variant\">\n Mix Google fonts for global typography with local assets for brand or language-specific\n families in the same provider.\n </p>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Using Global Config</h2>\n <CodeBlock title=\"defineConfig()\" code={configCode} html={configHtml} />\n <CodeBlock title=\"+layout.svelte\" code={layoutCode} html={layoutHtml} />\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Disable Defaults</h2>\n <CodeBlock title=\"Disable Defaults\" code={disableDefaultsCode} html={disableDefaultsHtml} />\n <p class=\"text-sm text-on-surface-variant\">\n Set `fonts: false` when you want to fully manage font loading yourself.\n </p>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">API Reference</h2>\n <div class=\"grid gap-4 lg:grid-cols-2\">\n <Card class=\"border border-outline-variant/70\">\n <div class=\"space-y-4\">\n <h3 class=\"text-base font-semibold\">Fonts Options</h3>\n <div class=\"space-y-3\">\n {#each optionsReference as item (item.name)}\n <div class=\"border-b border-outline-variant/60 pb-3 last:border-b-0 last:pb-0\">\n <p class=\"font-mono text-xs text-on-surface-variant\">{item.name}</p>\n <p class=\"mt-1 text-sm font-medium\">{item.type}</p>\n <p class=\"mt-1 text-sm text-on-surface-variant\">{item.description}</p>\n </div>\n {/each}\n </div>\n </div>\n </Card>\n <Card class=\"border border-outline-variant/70\">\n <div class=\"space-y-4\">\n <h3 class=\"text-base font-semibold\">Font Definition</h3>\n <div class=\"space-y-3\">\n {#each familyReference as item (item.name)}\n <div class=\"border-b border-outline-variant/60 pb-3 last:border-b-0 last:pb-0\">\n <p class=\"font-mono text-xs text-on-surface-variant\">{item.name}</p>\n <p class=\"mt-1 text-sm font-medium\">{item.type}</p>\n <p class=\"mt-1 text-sm text-on-surface-variant\">{item.description}</p>\n </div>\n {/each}\n </div>\n </div>\n </Card>\n </div>\n <Card class=\"border border-outline-variant/70\">\n <div class=\"space-y-4\">\n <h3 class=\"text-base font-semibold\">Local Font Source</h3>\n <div class=\"grid gap-3 md:grid-cols-2\">\n {#each localSourceReference as item (item.name)}\n <div class=\"rounded-xl border border-outline-variant/60 p-4\">\n <p class=\"font-mono text-xs text-on-surface-variant\">{item.name}</p>\n <p class=\"mt-1 text-sm font-medium\">{item.type}</p>\n <p class=\"mt-1 text-sm text-on-surface-variant\">{item.description}</p>\n </div>\n {/each}\n </div>\n </div>\n </Card>\n </section>\n</div>\n",
@@ -135,6 +144,15 @@
135
144
  "use-click-outside": "<script lang=\"ts\">\n import { useClickOutside } from '$lib/index.js'\n import { Button, Badge, Card } from '$lib/index.js'\n\n let dropdownOpen = $state(false)\n let clickCount = $state(0)\n\n let editMode = $state(false)\n let editValue = $state('Click to edit this text')\n\n let popupOpen = $state(false)\n let enabled = $state(true)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useClickOutside</h1>\n <p class=\"text-on-surface-variant\">\n Svelte action that detects clicks outside an element. Use it with\n <code class=\"rounded bg-surface-container px-1\">use:useClickOutside</code>.\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 Click inside the blue box — nothing happens. Click outside — counter increments.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex items-center gap-4\">\n <div\n use:useClickOutside={{ handler: () => clickCount++ }}\n class=\"flex items-center justify-center rounded-lg border-2 border-dashed border-primary bg-primary/10 p-8\"\n >\n <span class=\"text-sm font-medium\">Click outside me</span>\n </div>\n <Badge\n label=\"Outside clicks: {clickCount}\"\n color={clickCount > 0 ? 'primary' : 'surface'}\n variant=\"subtle\"\n />\n </div>\n </div>\n </section>\n\n <!-- Dropdown -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Dropdown</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"relative inline-block\">\n <Button onclick={() => (dropdownOpen = !dropdownOpen)}>\n {dropdownOpen ? 'Close Menu' : 'Open Menu'}\n </Button>\n\n {#if dropdownOpen}\n <div\n use:useClickOutside={{ handler: () => (dropdownOpen = false) }}\n class=\"absolute top-full left-0 z-10 mt-2 w-48 rounded-lg border border-outline-variant bg-surface-container p-1 shadow-lg\"\n >\n {#each ['Profile', 'Settings', 'Logout'] as item (item)}\n <button\n class=\"w-full rounded-md px-3 py-2 text-left text-sm hover:bg-surface-container-high\"\n onclick={() => (dropdownOpen = false)}\n >\n {item}\n </button>\n {/each}\n </div>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- Inline Edit -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Inline Edit</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Click the text to edit. Click outside to save.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n {#if editMode}\n <div use:useClickOutside={{ handler: () => (editMode = false) }}>\n <input\n bind:value={editValue}\n class=\"w-full max-w-sm rounded-md border border-primary bg-surface px-3 py-2 text-sm ring-2 ring-primary/30 outline-none\"\n />\n </div>\n {:else}\n <button\n class=\"rounded-md px-3 py-2 text-sm hover:bg-surface-container\"\n onclick={() => (editMode = true)}\n >\n {editValue}\n <span class=\"ml-2 text-xs text-on-surface-variant\">(click to edit)</span>\n </button>\n {/if}\n </div>\n </section>\n\n <!-- Enabled/Disabled -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Enable / Disable</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Toggle the listener on and off with the <code class=\"rounded bg-surface-container px-1\"\n >enabled</code\n > option.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex items-center gap-4\">\n <Button variant=\"outline\" size=\"sm\" onclick={() => (enabled = !enabled)}>\n Listener: {enabled ? 'ON' : 'OFF'}\n </Button>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n onclick={() => (popupOpen = true)}\n disabled={popupOpen}\n >\n Show Popup\n </Button>\n </div>\n\n {#if popupOpen}\n <Card class=\"mt-3 inline-block p-4\">\n <div\n use:useClickOutside={{\n handler: () => (popupOpen = false),\n enabled\n }}\n >\n <p class=\"text-sm font-medium\">Popup Content</p>\n <p class=\"text-xs text-on-surface-variant\">\n {enabled\n ? 'Click outside to close'\n : 'Outside click disabled — use button'}\n </p>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n class=\"mt-2\"\n onclick={() => (popupOpen = false)}\n >\n Close manually\n </Button>\n </div>\n </Card>\n {/if}\n </div>\n </section>\n</div>\n",
136
145
  "use-infinite-scroll": "<script lang=\"ts\">\n import { useInfiniteScroll } from '$lib/index.js'\n import { Badge, Button, Skeleton, Table, type TableColumn } from '$lib/index.js'\n\n // ==================== Basic List ====================\n\n let items = $state<{ id: number; title: string }[]>(\n Array.from({ length: 20 }, (_, i) => ({ id: i + 1, title: `Item ${i + 1}` }))\n )\n let hasMore = $state(true)\n let loadCount = $state(0)\n\n async function fetchMore() {\n await new Promise((r) => setTimeout(r, 800))\n const start = items.length\n const next = Array.from({ length: 20 }, (_, i) => ({\n id: start + i + 1,\n title: `Item ${start + i + 1}`\n }))\n items.push(...next)\n loadCount++\n if (items.length >= 100) hasMore = false\n }\n\n const scroll = useInfiniteScroll({\n onLoad: fetchMore,\n threshold: 150,\n enabled: () => hasMore\n })\n\n function reset() {\n items = Array.from({ length: 20 }, (_, i) => ({ id: i + 1, title: `Item ${i + 1}` }))\n hasMore = true\n loadCount = 0\n }\n\n // ==================== Table ====================\n\n interface User {\n id: number\n name: string\n email: string\n role: string\n status: 'active' | 'inactive' | 'pending'\n }\n\n const roles = ['Admin', 'Editor', 'Viewer', 'Moderator']\n const statuses = ['active', 'inactive', 'pending'] as const\n const firstNames = [\n 'Alice',\n 'Bob',\n 'Charlie',\n 'Diana',\n 'Eve',\n 'Frank',\n 'Grace',\n 'Henry',\n 'Iris',\n 'Jack'\n ]\n const lastNames = [\n 'Johnson',\n 'Smith',\n 'Brown',\n 'Prince',\n 'Davis',\n 'Wilson',\n 'Taylor',\n 'Clark',\n 'Lee',\n 'Hall'\n ]\n\n function generateUsers(start: number, count: number): User[] {\n return Array.from({ length: count }, (_, i) => {\n const id = start + i + 1\n const first = firstNames[id % firstNames.length]\n const last = lastNames[Math.floor(id / firstNames.length) % lastNames.length]\n return {\n id,\n name: `${first} ${last}`,\n email: `${first.toLowerCase()}.${last.toLowerCase()}${id}@example.com`,\n role: roles[id % roles.length],\n status: statuses[id % statuses.length]\n }\n })\n }\n\n let users = $state<User[]>(generateUsers(0, 30))\n let tableHasMore = $state(true)\n let tableLoadCount = $state(0)\n\n async function fetchMoreUsers() {\n await new Promise((r) => setTimeout(r, 1000))\n const next = generateUsers(users.length, 30)\n users.push(...next)\n tableLoadCount++\n if (users.length >= 150) tableHasMore = false\n }\n\n const tableScroll = useInfiniteScroll({\n onLoad: fetchMoreUsers,\n threshold: 200,\n enabled: () => tableHasMore\n })\n\n const columns: TableColumn<User>[] = [\n { key: 'id', label: '#', width: 60, align: 'center' },\n { key: 'name', label: 'Name', sortable: true },\n { key: 'email', label: 'Email' },\n { key: 'role', label: 'Role' },\n { key: 'status', label: 'Status' }\n ]\n\n function resetTable() {\n users = generateUsers(0, 30)\n tableHasMore = true\n tableLoadCount = 0\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useInfiniteScroll</h1>\n <p class=\"text-on-surface-variant\">\n Reactive infinite scroll hook with Svelte action. Triggers a callback when the user\n scrolls near the bottom of a container.\n </p>\n </div>\n\n <!-- Basic List -->\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 Scroll down inside the container to load more items. Stops at 100 items.\n </p>\n <div class=\"flex flex-wrap items-center gap-3\">\n <Badge label=\"Items: {items.length}\" color=\"primary\" variant=\"subtle\" />\n <Badge label=\"Loads: {loadCount}\" color=\"info\" variant=\"subtle\" />\n <Badge\n label={hasMore ? 'Has more' : 'All loaded'}\n color={hasMore ? 'success' : 'surface'}\n variant=\"subtle\"\n />\n <Button size=\"xs\" variant=\"outline\" onclick={reset}>Reset</Button>\n </div>\n <div\n use:scroll.action\n class=\"h-80 space-y-2 overflow-y-auto rounded-lg bg-surface-container-high p-4\"\n >\n {#each items as item (item.id)}\n <div\n class=\"flex items-center justify-between rounded-md bg-surface-container px-4 py-3\"\n >\n <span class=\"text-sm\">{item.title}</span>\n <Badge label=\"#{item.id}\" color=\"surface\" variant=\"outline\" size=\"sm\" />\n </div>\n {/each}\n\n {#if scroll.loading}\n <div class=\"space-y-2 pt-2\">\n <Skeleton class=\"h-11 w-full rounded-md\" />\n <Skeleton class=\"h-11 w-full rounded-md\" />\n <Skeleton class=\"h-11 w-full rounded-md\" />\n </div>\n {/if}\n\n {#if !hasMore}\n <p class=\"py-4 text-center text-sm text-on-surface-variant\">All items loaded</p>\n {/if}\n </div>\n </section>\n\n <!-- Table + Infinite Scroll -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Table + Infinite Scroll</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Combine with the Table component for paginated data loading. Scroll the table to\n automatically load more rows. Stops at 150 users.\n </p>\n <div class=\"flex flex-wrap items-center gap-3\">\n <Badge label=\"Users: {users.length}\" color=\"primary\" variant=\"subtle\" />\n <Badge label=\"Loads: {tableLoadCount}\" color=\"info\" variant=\"subtle\" />\n <Badge\n label={tableHasMore ? 'Has more' : 'All loaded'}\n color={tableHasMore ? 'success' : 'surface'}\n variant=\"subtle\"\n />\n <Button size=\"xs\" variant=\"outline\" onclick={resetTable}>Reset</Button>\n </div>\n <Table\n data={users}\n {columns}\n rowKey=\"id\"\n manualPagination\n total={users.length}\n pageSize={users.length}\n sticky=\"header\"\n hoverable\n loading={tableScroll.loading}\n action={tableScroll.action}\n class=\"h-112 overflow-y-auto\"\n >\n {#snippet cellSlot({ column, value })}\n {@const cellValue = String(value ?? '')}\n {#if column.key === 'status'}\n <Badge\n label={cellValue}\n color={cellValue === 'active'\n ? 'success'\n : cellValue === 'pending'\n ? 'warning'\n : 'surface'}\n variant=\"soft\"\n size=\"sm\"\n />\n {:else if column.key === 'role'}\n <Badge label={cellValue} color=\"info\" variant=\"subtle\" size=\"sm\" />\n {:else}\n {cellValue}\n {/if}\n {/snippet}\n\n {#snippet bodyBottomSlot()}\n {#if !tableHasMore}\n <tr>\n <td\n colspan={columns.length}\n class=\"py-4 text-center text-sm text-on-surface-variant\"\n >\n All users loaded\n </td>\n </tr>\n {/if}\n {/snippet}\n </Table>\n </section>\n</div>\n",
137
146
  "use-escape-keydown": "<script lang=\"ts\">\n import { useEscapeKeydown } from '$lib/index.js'\n import { Button, Badge, Card } from '$lib/index.js'\n\n let escCount = $state(0)\n\n let panelOpen = $state(false)\n let confirmOpen = $state(false)\n\n let enabled = $state(true)\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\">useEscapeKeydown</h1>\n <p class=\"text-on-surface-variant\">\n Svelte action that listens for the Escape key. Lightweight alternative to\n <code class=\"rounded bg-surface-container px-1\">useKbd</code> when you only need Escape.\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\">Press Escape anywhere on this page.</p>\n <div\n use:useEscapeKeydown={{ handler: () => escCount++ }}\n class=\"flex items-center gap-4 rounded-lg bg-surface-container-high p-4\"\n >\n <Badge\n label=\"Escape pressed: {escCount} time{escCount === 1 ? '' : 's'}\"\n color={escCount > 0 ? 'primary' : 'surface'}\n variant=\"subtle\"\n size=\"md\"\n />\n <Button size=\"xs\" variant=\"ghost\" onclick={() => (escCount = 0)}>Reset</Button>\n </div>\n </section>\n\n <!-- Dismiss Panel -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Dismiss Panel</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <Button onclick={() => (panelOpen = true)} disabled={panelOpen}>Show Panel</Button>\n\n {#if panelOpen}\n <Card class=\"mt-3 p-4\">\n <div use:useEscapeKeydown={{ handler: () => (panelOpen = false) }}>\n <p class=\"text-sm font-medium\">Info Panel</p>\n <p class=\"text-xs text-on-surface-variant\">\n Press <kbd\n class=\"rounded border border-outline-variant bg-surface-container px-1.5 py-0.5 font-mono text-xs\"\n >Esc</kbd\n > to close this panel.\n </p>\n </div>\n </Card>\n {/if}\n </div>\n </section>\n\n <!-- Confirmation Dialog -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Confirmation Dialog</h2>\n <p class=\"text-sm text-on-surface-variant\">Press Escape to cancel the confirmation.</p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n {#if !confirmOpen}\n <Button color=\"error\" variant=\"soft\" onclick={() => (confirmOpen = true)}>\n Delete Item\n </Button>\n {:else}\n <Card class=\"inline-block p-4\">\n <div use:useEscapeKeydown={{ handler: () => (confirmOpen = false) }}>\n <p class=\"text-sm font-medium\">Are you sure?</p>\n <p class=\"mb-3 text-xs text-on-surface-variant\">\n Press Escape to cancel, or click Confirm.\n </p>\n <div class=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => (confirmOpen = false)}\n >\n Cancel\n </Button>\n <Button size=\"sm\" color=\"error\" onclick={() => (confirmOpen = false)}>\n Confirm\n </Button>\n </div>\n </div>\n </Card>\n {/if}\n </div>\n </section>\n\n <!-- Enable/Disable -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Enable / Disable</h2>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex items-center gap-4\">\n <Button variant=\"outline\" size=\"sm\" onclick={() => (enabled = !enabled)}>\n Listener: {enabled ? 'ON' : 'OFF'}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onclick={() => (controlledOpen = true)}\n disabled={controlledOpen}\n >\n Show Box\n </Button>\n </div>\n\n {#if controlledOpen}\n <Card class=\"mt-3 inline-block p-4\">\n <div\n use:useEscapeKeydown={{ handler: () => (controlledOpen = false), enabled }}\n >\n <p class=\"text-sm font-medium\">Controlled Box</p>\n <p class=\"text-xs text-on-surface-variant\">\n {enabled ? 'Press Escape to close' : 'Escape disabled — close manually'}\n </p>\n <Button\n size=\"xs\"\n variant=\"ghost\"\n class=\"mt-2\"\n onclick={() => (controlledOpen = false)}\n >\n Close manually\n </Button>\n </div>\n </Card>\n {/if}\n </div>\n </section>\n</div>\n",
138
- "use-debounce": "<script lang=\"ts\">\n import { useDebounce } from '$lib/index.js'\n import { Button, Input, Badge, Card, Icon } from '$lib/index.js'\n\n // ==================== Basic ====================\n let searchQuery = $state('')\n let searchResult = $state('')\n let searchCount = $state(0)\n const searchDebounce = useDebounce({ delay: 500 })\n\n function handleSearch(e: Event) {\n searchQuery = (e.currentTarget as HTMLInputElement).value\n searchDebounce.run(() => {\n searchResult = searchQuery\n searchCount++\n })\n }\n\n // ==================== Auto-save ====================\n let noteText = $state('Start typing to auto-save...')\n let savedText = $state('')\n let saveCount = $state(0)\n const saveDebounce = useDebounce({ delay: 1000 })\n\n function handleNoteInput(e: Event) {\n noteText = (e.currentTarget as HTMLTextAreaElement).value\n saveDebounce.run(() => {\n savedText = noteText\n saveCount++\n })\n }\n\n // ==================== API Simulation ====================\n interface UserResult {\n id: number\n name: string\n }\n\n let apiQuery = $state('')\n let apiResults = $state<UserResult[]>([])\n let apiLoading = $state(false)\n const apiDebounce = useDebounce({ delay: 400 })\n\n const allUsers: UserResult[] = [\n { id: 1, name: 'Alice Johnson' },\n { id: 2, name: 'Bob Smith' },\n { id: 3, name: 'Charlie Brown' },\n { id: 4, name: 'Diana Prince' },\n { id: 5, name: 'Eve Davis' },\n { id: 6, name: 'Frank Wilson' },\n { id: 7, name: 'Grace Taylor' },\n { id: 8, name: 'Henry Clark' }\n ]\n\n function handleApiSearch(e: Event) {\n apiQuery = (e.currentTarget as HTMLInputElement).value\n if (!apiQuery.trim()) {\n apiDebounce.cancel()\n apiResults = []\n apiLoading = false\n return\n }\n apiDebounce.run(async () => {\n apiLoading = true\n await new Promise((r) => setTimeout(r, 300))\n const q = apiQuery.toLowerCase()\n apiResults = allUsers.filter((u) => u.name.toLowerCase().includes(q))\n apiLoading = false\n })\n }\n\n // ==================== Cancel & Flush ====================\n let controlValue = $state('')\n let controlResult = $state('')\n const controlDebounce = useDebounce({ delay: 2000 })\n\n function handleControlInput(e: Event) {\n controlValue = (e.currentTarget as HTMLInputElement).value\n controlDebounce.run(() => {\n controlResult = controlValue\n })\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useDebounce</h1>\n <p class=\"text-on-surface-variant\">\n Reactive debounce hook. Delays execution until a pause in calls. Tracks pending state\n and supports cancel/flush.\n </p>\n </div>\n\n <!-- Basic Search -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic: Search</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Type in the input — the search only fires after 500ms of inactivity.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Input\n value={searchQuery}\n oninput={handleSearch}\n placeholder=\"Search something...\"\n leadingIcon=\"lucide:search\"\n />\n <div class=\"flex flex-wrap items-center gap-3\">\n <Badge\n label={searchDebounce.pending ? 'Typing...' : 'Idle'}\n color={searchDebounce.pending ? 'warning' : 'surface'}\n variant=\"soft\"\n />\n <Badge label=\"Executed: {searchCount}x\" color=\"info\" variant=\"subtle\" />\n {#if searchResult}\n <span class=\"text-sm text-on-surface-variant\">\n Last search: <strong>{searchResult}</strong>\n </span>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- Auto-save -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Auto-save</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Content auto-saves 1 second after you stop typing.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <textarea\n value={noteText}\n oninput={handleNoteInput}\n rows=\"3\"\n class=\"w-full rounded-md border border-outline-variant bg-surface px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary/30\"\n ></textarea>\n <div class=\"flex items-center gap-3\">\n {#if saveDebounce.pending}\n <Badge label=\"Unsaved changes...\" color=\"warning\" variant=\"soft\" />\n {:else}\n <Badge\n label={saveCount > 0 ? 'Saved' : 'No changes'}\n color={saveCount > 0 ? 'success' : 'surface'}\n variant=\"soft\"\n />\n {/if}\n {#if savedText}\n <span class=\"text-xs text-on-surface-variant\">\n Last saved: {savedText.length} chars\n </span>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- API Search -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World: API Search</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Debounced API call with loading state. Try typing \"alice\", \"bob\", or \"grace\".\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Input\n value={apiQuery}\n oninput={handleApiSearch}\n placeholder=\"Search users...\"\n leadingIcon=\"lucide:users\"\n loading={apiLoading}\n />\n\n {#if apiResults.length > 0}\n <div class=\"space-y-1\">\n {#each apiResults as user (user.id)}\n <div\n class=\"flex items-center gap-3 rounded-md bg-surface-container px-3 py-2\"\n >\n <Icon name=\"lucide:user\" size=\"16\" class=\"text-on-surface-variant\" />\n <span class=\"text-sm\">{user.name}</span>\n </div>\n {/each}\n </div>\n {:else if apiQuery && !apiLoading && !apiDebounce.pending}\n <p class=\"text-sm text-on-surface-variant\">No users found.</p>\n {/if}\n </div>\n </section>\n\n <!-- Cancel & Flush -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Cancel & Flush</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Delay is 2 seconds. Use <strong>Cancel</strong> to discard, or <strong>Flush</strong> to execute\n immediately.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Input\n value={controlValue}\n oninput={handleControlInput}\n placeholder=\"Type and use buttons below...\"\n />\n <div class=\"flex flex-wrap items-center gap-3\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => controlDebounce.cancel()}\n disabled={!controlDebounce.pending}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n variant=\"soft\"\n onclick={() => controlDebounce.flush(() => (controlResult = controlValue))}\n disabled={!controlDebounce.pending}\n >\n Flush Now\n </Button>\n <Badge\n label={controlDebounce.pending ? 'Pending (2s)...' : 'Idle'}\n color={controlDebounce.pending ? 'warning' : 'surface'}\n variant=\"soft\"\n />\n </div>\n {#if controlResult}\n <Card class=\"p-3\">\n <p class=\"text-sm\">\n Result: <strong>{controlResult}</strong>\n </p>\n </Card>\n {/if}\n </div>\n </section>\n</div>\n"
147
+ "use-debounce": "<script lang=\"ts\">\n import { useDebounce } from '$lib/index.js'\n import { Button, Input, Badge, Card, Icon } from '$lib/index.js'\n\n // ==================== Basic ====================\n let searchQuery = $state('')\n let searchResult = $state('')\n let searchCount = $state(0)\n const searchDebounce = useDebounce({ delay: 500 })\n\n function handleSearch(e: Event) {\n searchQuery = (e.currentTarget as HTMLInputElement).value\n searchDebounce.run(() => {\n searchResult = searchQuery\n searchCount++\n })\n }\n\n // ==================== Auto-save ====================\n let noteText = $state('Start typing to auto-save...')\n let savedText = $state('')\n let saveCount = $state(0)\n const saveDebounce = useDebounce({ delay: 1000 })\n\n function handleNoteInput(e: Event) {\n noteText = (e.currentTarget as HTMLTextAreaElement).value\n saveDebounce.run(() => {\n savedText = noteText\n saveCount++\n })\n }\n\n // ==================== API Simulation ====================\n interface UserResult {\n id: number\n name: string\n }\n\n let apiQuery = $state('')\n let apiResults = $state<UserResult[]>([])\n let apiLoading = $state(false)\n const apiDebounce = useDebounce({ delay: 400 })\n\n const allUsers: UserResult[] = [\n { id: 1, name: 'Alice Johnson' },\n { id: 2, name: 'Bob Smith' },\n { id: 3, name: 'Charlie Brown' },\n { id: 4, name: 'Diana Prince' },\n { id: 5, name: 'Eve Davis' },\n { id: 6, name: 'Frank Wilson' },\n { id: 7, name: 'Grace Taylor' },\n { id: 8, name: 'Henry Clark' }\n ]\n\n function handleApiSearch(e: Event) {\n apiQuery = (e.currentTarget as HTMLInputElement).value\n if (!apiQuery.trim()) {\n apiDebounce.cancel()\n apiResults = []\n apiLoading = false\n return\n }\n apiDebounce.run(async () => {\n apiLoading = true\n await new Promise((r) => setTimeout(r, 300))\n const q = apiQuery.toLowerCase()\n apiResults = allUsers.filter((u) => u.name.toLowerCase().includes(q))\n apiLoading = false\n })\n }\n\n // ==================== Cancel & Flush ====================\n let controlValue = $state('')\n let controlResult = $state('')\n const controlDebounce = useDebounce({ delay: 2000 })\n\n function handleControlInput(e: Event) {\n controlValue = (e.currentTarget as HTMLInputElement).value\n controlDebounce.run(() => {\n controlResult = controlValue\n })\n }\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useDebounce</h1>\n <p class=\"text-on-surface-variant\">\n Reactive debounce hook. Delays execution until a pause in calls. Tracks pending state\n and supports cancel/flush.\n </p>\n </div>\n\n <!-- Basic Search -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Basic: Search</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Type in the input — the search only fires after 500ms of inactivity.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Input\n value={searchQuery}\n oninput={handleSearch}\n placeholder=\"Search something...\"\n leadingIcon=\"lucide:search\"\n />\n <div class=\"flex flex-wrap items-center gap-3\">\n <Badge\n label={searchDebounce.pending ? 'Typing...' : 'Idle'}\n color={searchDebounce.pending ? 'warning' : 'surface'}\n variant=\"soft\"\n />\n <Badge label=\"Executed: {searchCount}x\" color=\"info\" variant=\"subtle\" />\n {#if searchResult}\n <span class=\"text-sm text-on-surface-variant\">\n Last search: <strong>{searchResult}</strong>\n </span>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- Auto-save -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Auto-save</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Content auto-saves 1 second after you stop typing.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <textarea\n value={noteText}\n oninput={handleNoteInput}\n rows=\"3\"\n class=\"w-full rounded-md border border-outline-variant bg-surface px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-primary/30\"\n ></textarea>\n <div class=\"flex items-center gap-3\">\n {#if saveDebounce.pending}\n <Badge label=\"Unsaved changes...\" color=\"warning\" variant=\"soft\" />\n {:else}\n <Badge\n label={saveCount > 0 ? 'Saved' : 'No changes'}\n color={saveCount > 0 ? 'success' : 'surface'}\n variant=\"soft\"\n />\n {/if}\n {#if savedText}\n <span class=\"text-xs text-on-surface-variant\">\n Last saved: {savedText.length} chars\n </span>\n {/if}\n </div>\n </div>\n </section>\n\n <!-- API Search -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Real World: API Search</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Debounced API call with loading state. Try typing \"alice\", \"bob\", or \"grace\".\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Input\n value={apiQuery}\n oninput={handleApiSearch}\n placeholder=\"Search users...\"\n leadingIcon=\"lucide:users\"\n loading={apiLoading}\n />\n\n {#if apiResults.length > 0}\n <div class=\"space-y-1\">\n {#each apiResults as user (user.id)}\n <div\n class=\"flex items-center gap-3 rounded-md bg-surface-container px-3 py-2\"\n >\n <Icon name=\"lucide:user\" size=\"16\" class=\"text-on-surface-variant\" />\n <span class=\"text-sm\">{user.name}</span>\n </div>\n {/each}\n </div>\n {:else if apiQuery && !apiLoading && !apiDebounce.pending}\n <p class=\"text-sm text-on-surface-variant\">No users found.</p>\n {/if}\n </div>\n </section>\n\n <!-- Cancel & Flush -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Cancel & Flush</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Delay is 2 seconds. Use <strong>Cancel</strong> to discard, or <strong>Flush</strong> to execute\n immediately.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <Input\n value={controlValue}\n oninput={handleControlInput}\n placeholder=\"Type and use buttons below...\"\n />\n <div class=\"flex flex-wrap items-center gap-3\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onclick={() => controlDebounce.cancel()}\n disabled={!controlDebounce.pending}\n >\n Cancel\n </Button>\n <Button\n size=\"sm\"\n variant=\"soft\"\n onclick={() => controlDebounce.flush(() => (controlResult = controlValue))}\n disabled={!controlDebounce.pending}\n >\n Flush Now\n </Button>\n <Badge\n label={controlDebounce.pending ? 'Pending (2s)...' : 'Idle'}\n color={controlDebounce.pending ? 'warning' : 'surface'}\n variant=\"soft\"\n />\n </div>\n {#if controlResult}\n <Card class=\"p-3\">\n <p class=\"text-sm\">\n Result: <strong>{controlResult}</strong>\n </p>\n </Card>\n {/if}\n </div>\n </section>\n</div>\n",
148
+ "use-debounced-state": "<script lang=\"ts\">\n import { useDebouncedState } from '$lib/index.js'\n import { Input, Badge, Card, Button } from '$lib/index.js'\n\n const fruits = [\n 'Apple',\n 'Banana',\n 'Blueberry',\n 'Cherry',\n 'Grape',\n 'Mango',\n 'Orange',\n 'Peach',\n 'Pear',\n 'Pineapple',\n 'Strawberry',\n 'Watermelon'\n ]\n\n const search = useDebouncedState('', 300)\n\n const filtered = $derived(\n search.debounced.trim() === ''\n ? fruits\n : fruits.filter((f) => f.toLowerCase().includes(search.debounced.trim().toLowerCase()))\n )\n</script>\n\n<div class=\"space-y-8\">\n <div>\n <h1 class=\"text-2xl font-bold text-on-surface\">useDebouncedState</h1>\n <p class=\"mt-1 text-on-surface-variant\">\n Reactive state whose <code>debounced</code> mirror lags behind <code>current</code> by a\n delay — write <code>current</code> from an input and derive from <code>debounced</code>,\n with no manual two-state wiring.\n </p>\n </div>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">Debounced filter (delay: 300ms)</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n <code>current</code> updates on every keystroke; the list re-filters only once typing settles.\n </p>\n\n <Input placeholder=\"Filter fruits…\" bind:value={search.current} />\n\n <div class=\"mt-4 flex flex-wrap items-center gap-3\">\n <Badge color=\"secondary\">current: \"{search.current}\"</Badge>\n <Badge color=\"primary\">debounced: \"{search.debounced}\"</Badge>\n <Button size=\"sm\" variant=\"ghost\" onclick={() => search.setImmediate('')}>Clear</Button>\n </div>\n\n <ul class=\"mt-4 flex flex-wrap gap-2\">\n {#each filtered as fruit (fruit)}\n <li class=\"rounded-md bg-surface-container px-3 py-1 text-sm text-on-surface\">\n {fruit}\n </li>\n {:else}\n <li class=\"text-sm text-on-surface-variant\">No matches.</li>\n {/each}\n </ul>\n </Card>\n</div>\n",
149
+ "use-event-listener": "<script lang=\"ts\">\n import { useEventListener } from '$lib/index.js'\n import { Badge, Card, Icon } from '$lib/index.js'\n\n // ==================== Window resize (value/getter target) ====================\n let width = $state(0)\n let height = $state(0)\n\n $effect(() => {\n width = window.innerWidth\n height = window.innerHeight\n })\n\n useEventListener(\n () => window,\n 'resize',\n () => {\n width = window.innerWidth\n height = window.innerHeight\n }\n )\n\n // ==================== Window keydown (multiple-target demo) ====================\n let lastKey = $state('—')\n let keyCount = $state(0)\n\n useEventListener(\n () => window,\n 'keydown',\n (e) => {\n lastKey = e.key === ' ' ? 'Space' : e.key\n keyCount++\n }\n )\n\n // ==================== Element pointermove (reactive getter target) ====================\n let box = $state<HTMLElement>()\n let pos = $state({ x: 0, y: 0 })\n let inside = $state(false)\n\n useEventListener(\n () => box,\n 'pointermove',\n (e) => {\n const rect = box!.getBoundingClientRect()\n pos = { x: Math.round(e.clientX - rect.left), y: Math.round(e.clientY - rect.top) }\n }\n )\n useEventListener(\n () => box,\n ['pointerenter', 'pointerleave'],\n (e) => {\n inside = e.type === 'pointerenter'\n }\n )\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useEventListener</h1>\n <p class=\"text-on-surface-variant\">\n Attach event listener(s) to a target with automatic cleanup. The target may be a value\n or a reactive getter, accepts one or many event types, and forwards listener options.\n SSR-safe — a nullish target is a no-op.\n </p>\n </div>\n\n <!-- Window resize -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Window resize</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Listens to <code>resize</code> on <code>window</code>. Resize the browser window to see\n it update.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"{width} × {height}\" color=\"primary\" variant=\"soft\" size=\"lg\" />\n <span class=\"text-sm text-on-surface-variant\">live viewport size</span>\n </div>\n </section>\n\n <!-- Keydown -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Keyboard</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Listens to <code>keydown</code> on <code>window</code>. Press any key.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Badge label=\"Last key: {lastKey}\" color=\"info\" variant=\"soft\" />\n <Badge label=\"Pressed: {keyCount}x\" color=\"surface\" variant=\"subtle\" />\n </div>\n </section>\n\n <!-- Pointer position -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Pointer position (reactive element target)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Listens to <code>pointermove</code> / <code>pointerenter</code> /\n <code>pointerleave</code> on an element resolved via a <code>() => box</code> getter, so the\n listener binds once the element mounts. Move your cursor inside the box.\n </p>\n <div\n bind:this={box}\n class=\"relative flex h-48 items-center justify-center rounded-lg border-2 border-dashed border-outline-variant bg-surface-container-high transition-colors\"\n class:border-primary={inside}\n >\n {#if inside}\n <div\n class=\"pointer-events-none absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary\"\n style=\"left: {pos.x}px; top: {pos.y}px;\"\n ></div>\n {/if}\n <Card class=\"p-3\">\n <div class=\"flex items-center gap-2 text-sm\">\n <Icon name=\"lucide:move\" size=\"16\" class=\"text-on-surface-variant\" />\n <span>x: <strong>{pos.x}</strong>, y: <strong>{pos.y}</strong></span>\n </div>\n </Card>\n </div>\n </section>\n</div>\n",
150
+ "use-resize-observer": "<script lang=\"ts\">\n import { useElementSize, useResizeObserver } from '$lib/index.js'\n import { Badge, Icon } from '$lib/index.js'\n\n // ==================== useElementSize ====================\n let box = $state<HTMLElement>()\n const size = useElementSize(() => box)\n\n // ==================== useResizeObserver (lower-level) ====================\n let box2 = $state<HTMLElement>()\n let breakpoint = $state('—')\n\n useResizeObserver(\n () => box2,\n ([entry]) => {\n const w = entry.contentRect.width\n breakpoint = w < 260 ? 'sm' : w < 420 ? 'md' : 'lg'\n }\n )\n\n const breakpointColor = $derived(\n breakpoint === 'sm' ? 'warning' : breakpoint === 'md' ? 'info' : 'success'\n )\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useResizeObserver / useElementSize</h1>\n <p class=\"text-on-surface-variant\">\n Observe an element's size with a <code>ResizeObserver</code> and automatic cleanup.\n <code>useElementSize</code> returns the reactive content-box size; the target may be a value\n or a reactive getter. SSR-safe.\n </p>\n </div>\n\n <!-- useElementSize -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">useElementSize</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Drag the bottom-right corner of the box — the size updates live.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div\n bind:this={box}\n class=\"flex min-h-32 max-w-full min-w-48 resize items-center justify-center overflow-auto rounded-lg border-2 border-dashed border-outline-variant bg-surface p-4\"\n >\n <Badge\n label=\"{Math.round(size.width)} × {Math.round(size.height)}\"\n color=\"primary\"\n variant=\"soft\"\n size=\"lg\"\n />\n </div>\n </div>\n </section>\n\n <!-- useResizeObserver -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">useResizeObserver (lower-level callback)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The raw callback reads each entry's <code>contentRect</code>. Here it maps the width to\n a responsive breakpoint — resize horizontally to see it change.\n </p>\n <div class=\"rounded-lg bg-surface-container-high p-4\">\n <div\n bind:this={box2}\n class=\"flex min-h-24 max-w-full min-w-44 resize-x items-center justify-center gap-2 overflow-auto rounded-lg border-2 border-dashed border-outline-variant bg-surface p-4\"\n >\n <Icon name=\"lucide:scaling\" size=\"16\" class=\"text-on-surface-variant\" />\n <Badge label=\"breakpoint: {breakpoint}\" color={breakpointColor} variant=\"soft\" />\n </div>\n </div>\n </section>\n</div>\n",
151
+ "use-intersection-observer": "<script lang=\"ts\">\n import { useIntersectionObserver } from '$lib/index.js'\n import { Badge, Card, Icon } from '$lib/index.js'\n\n let target = $state<HTMLElement>()\n let enterCount = $state(0)\n let loaded = $state(false)\n\n const io = useIntersectionObserver(\n () => target,\n (entry) => {\n if (entry.isIntersecting) {\n enterCount++\n loaded = true\n }\n },\n { threshold: 0.25 }\n )\n</script>\n\n<div class=\"space-y-8\">\n <div>\n <h1 class=\"text-2xl font-bold text-on-surface\">useIntersectionObserver</h1>\n <p class=\"mt-1 text-on-surface-variant\">\n Observe an element's intersection with the viewport (or a root), with automatic cleanup.\n Ideal for lazy-loading, reveal-on-scroll, and visibility tracking.\n </p>\n </div>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">Live visibility</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n This badge tracks the target card far below. Scroll down and watch it flip as the target\n enters and leaves the viewport.\n </p>\n <div class=\"flex flex-wrap items-center gap-3\">\n <Badge color={io.isIntersecting ? 'success' : 'secondary'}>\n {io.isIntersecting ? 'In view' : 'Out of view'}\n </Badge>\n <Badge color=\"primary\">Entered: {enterCount}×</Badge>\n </div>\n </Card>\n\n <div\n class=\"flex h-[70vh] items-center justify-center gap-2 text-on-surface-variant\"\n aria-hidden=\"true\"\n >\n <Icon name=\"lucide:arrow-down\" size=\"18\" />\n Scroll down to the target\n </div>\n\n <Card>\n <h2 class=\"mb-3 font-semibold text-on-surface\">Lazy target (threshold: 0.25)</h2>\n <div\n bind:this={target}\n class=\"flex h-32 items-center justify-center rounded-lg border border-dashed transition-colors duration-500\"\n class:border-success={loaded}\n class:bg-success-container={loaded}\n class:border-outline={!loaded}\n class:bg-surface-container-low={!loaded}\n >\n {#if loaded}\n <span class=\"flex items-center gap-2 text-on-success-container\">\n <Icon name=\"lucide:check\" size=\"20\" /> Loaded — became visible!\n </span>\n {:else}\n <span class=\"flex items-center gap-2\">\n <Icon name=\"lucide:loader\" size=\"20\" /> Waiting to enter view…\n </span>\n {/if}\n </div>\n </Card>\n</div>\n",
152
+ "use-scroll-lock": "<script lang=\"ts\">\n import { useScrollLock } from '$lib/index.js'\n import { Button, Badge, Icon } from '$lib/index.js'\n\n // ==================== Element target ====================\n let box = $state<HTMLElement>()\n let boxLocked = $state(false)\n useScrollLock(\n () => boxLocked,\n () => box\n )\n\n // ==================== Page (body) target ====================\n let pageLocked = $state(false)\n useScrollLock(() => pageLocked)\n\n const rows = Array.from({ length: 24 }, (_, i) => i + 1)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useScrollLock</h1>\n <p class=\"text-on-surface-variant\">\n Lock scroll on an element (default <code>document.body</code>) while a reactive\n <code>locked</code> is true. Compensates for the scrollbar width to avoid layout jump, reference-counts\n nested locks, and restores the original styles on release. SSR-safe.\n </p>\n </div>\n\n <!-- Element target -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Lock an element</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Toggle to lock scrolling inside the box below. Try scrolling it while locked.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex items-center gap-3\">\n <Button\n size=\"sm\"\n variant={boxLocked ? 'solid' : 'outline'}\n color={boxLocked ? 'error' : 'primary'}\n onclick={() => (boxLocked = !boxLocked)}\n >\n {boxLocked ? 'Unlock' : 'Lock'} box\n </Button>\n <Badge\n label={boxLocked ? 'Locked' : 'Scrollable'}\n color={boxLocked ? 'error' : 'success'}\n variant=\"soft\"\n />\n </div>\n <div\n bind:this={box}\n class=\"h-48 space-y-1 overflow-auto rounded-lg border border-outline-variant bg-surface p-3\"\n >\n {#each rows as n (n)}\n <div class=\"rounded-md bg-surface-container px-3 py-2 text-sm\">Row {n}</div>\n {/each}\n </div>\n </div>\n </section>\n\n <!-- Page target -->\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Lock the page (body)</h2>\n <p class=\"text-sm text-on-surface-variant\">\n The default target is <code>document.body</code> — this is what Modal / Slideover / Drawer\n use to freeze the page behind an overlay. Toggle and try scrolling the page.\n </p>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button\n size=\"sm\"\n variant={pageLocked ? 'solid' : 'outline'}\n color={pageLocked ? 'error' : 'primary'}\n onclick={() => (pageLocked = !pageLocked)}\n >\n <Icon name={pageLocked ? 'lucide:lock' : 'lucide:lock-open'} size=\"16\" />\n {pageLocked ? 'Unlock' : 'Lock'} page\n </Button>\n <Badge\n label={pageLocked ? 'Page locked' : 'Page scrollable'}\n color={pageLocked ? 'error' : 'success'}\n variant=\"soft\"\n />\n </div>\n </section>\n</div>\n",
153
+ "use-focus-trap": "<script lang=\"ts\">\n import { useFocusTrap } from '$lib/index.js'\n import { Button, Badge, Input } from '$lib/index.js'\n\n let active = $state(false)\n let panel = $state<HTMLElement>()\n\n useFocusTrap(() => panel, { active: () => active })\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useFocusTrap</h1>\n <p class=\"text-on-surface-variant\">\n Trap keyboard focus within an element while active, then restore focus on exit. Cycles\n Tab / Shift+Tab among the focusable descendants. Target and <code>active</code> accept a value\n or reactive getter; SSR-safe.\n </p>\n <p class=\"text-sm text-on-surface-variant\">\n Note: components built on bits-ui (Modal, Slideover, Drawer, Popover) already trap focus\n — use this for your own custom focus-scoped UI.\n </p>\n </div>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Toggle a focus trap</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Activate, then press <kbd class=\"rounded bg-surface-container-high px-1\">Tab</kbd> — focus\n stays inside the panel and wraps around. Closing returns focus to the Activate button.\n </p>\n <div class=\"space-y-3 rounded-lg bg-surface-container-high p-4\">\n <div class=\"flex items-center gap-3\">\n <Button\n variant={active ? 'soft' : 'solid'}\n disabled={active}\n onclick={() => (active = true)}\n >\n Activate trap\n </Button>\n <Badge\n label={active ? 'Trapped' : 'Free'}\n color={active ? 'success' : 'surface'}\n variant=\"soft\"\n />\n </div>\n\n <div\n bind:this={panel}\n class=\"space-y-3 rounded-lg border-2 border-dashed p-4 transition-colors {active\n ? 'border-primary'\n : 'border-outline-variant'}\"\n >\n <p class=\"text-sm font-medium\">Trapped panel</p>\n <Input placeholder=\"First field\" />\n <Input placeholder=\"Second field\" />\n <div class=\"flex gap-2\">\n <Button size=\"sm\" variant=\"outline\">Action</Button>\n <Button size=\"sm\" color=\"error\" variant=\"soft\" onclick={() => (active = false)}>\n Close\n </Button>\n </div>\n </div>\n\n <p class=\"text-xs text-on-surface-variant\">\n Try tabbing past the last button — it loops back to the first field.\n </p>\n </div>\n </section>\n</div>\n",
154
+ "use-local-storage": "<script lang=\"ts\">\n import { useLocalStorage } from '$lib/index.js'\n import { Button, Input, Badge } from '$lib/index.js'\n\n const note = useLocalStorage('svelora-demo-note', '')\n const count = useLocalStorage('svelora-demo-count', 0)\n</script>\n\n<div class=\"space-y-8\">\n <div class=\"space-y-2\">\n <h1 class=\"text-2xl font-bold\">useLocalStorage</h1>\n <p class=\"text-on-surface-variant\">\n Reactive <code>localStorage</code>-backed value. Reads on mount, writes through on\n change, and syncs across tabs via the <code>storage</code> event. SSR-safe; parse/quota errors\n are tolerated.\n </p>\n </div>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Persisted text</h2>\n <p class=\"text-sm text-on-surface-variant\">\n Type below, then reload the page — the value persists. Open this page in a second tab to\n see live cross-tab sync.\n </p>\n <div class=\"space-y-2 rounded-lg bg-surface-container-high p-4\">\n <Input bind:value={note.current} placeholder=\"Persists across reloads & tabs...\" />\n <p class=\"text-xs text-on-surface-variant\">\n Stored under key <code>svelora-demo-note</code>.\n </p>\n </div>\n </section>\n\n <section class=\"space-y-3\">\n <h2 class=\"text-lg font-semibold\">Persisted counter</h2>\n <div class=\"flex flex-wrap items-center gap-3 rounded-lg bg-surface-container-high p-4\">\n <Button size=\"sm\" variant=\"outline\" onclick={() => (count.current -= 1)}>−</Button>\n <Badge label={count.current} color=\"primary\" variant=\"soft\" size=\"lg\" />\n <Button size=\"sm\" variant=\"outline\" onclick={() => (count.current += 1)}>+</Button>\n <Button size=\"sm\" variant=\"soft\" color=\"error\" onclick={() => (count.current = 0)}>\n Reset\n </Button>\n </div>\n </section>\n</div>\n",
155
+ "use-throttle": "<script lang=\"ts\">\n import { useThrottle } from '$lib/index.js'\n import { Button, Badge, Card, Icon, Input } from '$lib/index.js'\n\n // ==================== Throttled input ====================\n let keystrokes = $state(0)\n let queryUpdates = $state(0)\n let throttledQuery = $state('')\n const searchThrottle = useThrottle({ delay: 300 })\n\n function handleSearch(e: Event) {\n keystrokes++\n const value = (e.currentTarget as HTMLInputElement).value\n searchThrottle.run(() => {\n throttledQuery = value\n queryUpdates++\n })\n }\n\n // ==================== Mousemove rate ====================\n let rawMoves = $state(0)\n let throttledRuns = $state(0)\n const moveThrottle = useThrottle({ delay: 100 })\n\n function handleMove() {\n rawMoves++\n moveThrottle.run(() => throttledRuns++)\n }\n\n function resetMove() {\n rawMoves = 0\n throttledRuns = 0\n }\n\n // ==================== Rapid clicks (leading + trailing) ====================\n let log = $state<{ id: number; text: string }[]>([])\n let logId = 0\n const clickThrottle = useThrottle({ delay: 600 })\n\n function handleClick() {\n clickThrottle.run(() => {\n log = [\n { id: ++logId, text: `Fired at ${new Date().toLocaleTimeString()}` },\n ...log\n ].slice(0, 6)\n })\n }\n</script>\n\n<div class=\"space-y-8\">\n <div>\n <h1 class=\"text-2xl font-bold text-on-surface\">useThrottle</h1>\n <p class=\"mt-1 text-on-surface-variant\">\n Cap a callback to at most once per <code>delay</code>, with leading and trailing\n invocation. The companion to <code>useDebounce</code> — ideal for scroll, resize, mousemove,\n and drag handlers.\n </p>\n </div>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">Throttled input (delay: 300ms)</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n Type quickly. Unlike <code>useDebounce</code> (which waits for a pause), throttle\n updates the query at a steady rate <em>while</em> you type — good for live filtering.\n </p>\n\n <Input placeholder=\"Type to search…\" oninput={handleSearch} />\n\n <div class=\"mt-4 flex flex-wrap items-center gap-4\">\n <Badge color=\"secondary\">Keystrokes: {keystrokes}</Badge>\n <Badge color=\"primary\">Query updates: {queryUpdates}</Badge>\n <span class=\"text-sm text-on-surface-variant\">\n Throttled value: <span class=\"font-medium text-on-surface\"\n >{throttledQuery || '—'}</span\n >\n </span>\n </div>\n </Card>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">Mousemove rate (delay: 100ms)</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n Move your cursor across the box. Raw events fire on every pixel; the throttled callback\n runs at most ~10×/second.\n </p>\n\n <!-- svelte-ignore a11y_no_static_element_interactions -->\n <div\n onmousemove={handleMove}\n class=\"flex h-40 items-center justify-center rounded-lg border border-dashed border-outline bg-surface-container-low text-on-surface-variant\"\n >\n <Icon name=\"lucide:move\" size=\"20\" />\n <span class=\"ml-2\">Move here</span>\n </div>\n\n <div class=\"mt-4 flex items-center gap-4\">\n <Badge color=\"secondary\">Raw events: {rawMoves}</Badge>\n <Badge color=\"primary\">Throttled runs: {throttledRuns}</Badge>\n <Button size=\"sm\" variant=\"ghost\" onclick={resetMove}>Reset</Button>\n </div>\n </Card>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">Rapid clicks (delay: 600ms)</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n Click fast: the first click fires immediately (leading), then bursts collapse into one\n trailing call per window.\n </p>\n\n <div class=\"flex items-center gap-3\">\n <Button color=\"primary\" onclick={handleClick}>Click me fast</Button>\n {#if clickThrottle.pending}\n <Badge color=\"warning\">trailing pending…</Badge>\n {/if}\n </div>\n\n {#if log.length}\n <ul class=\"mt-4 space-y-1 text-sm text-on-surface-variant\">\n {#each log as entry (entry.id)}\n <li class=\"flex items-center gap-2\">\n <Icon name=\"lucide:check\" size=\"14\" class=\"text-success\" />\n {entry.text}\n </li>\n {/each}\n </ul>\n {/if}\n </Card>\n</div>\n",
156
+ "use-timers": "<script lang=\"ts\">\n import { useInterval, useTimeout } from '$lib/index.js'\n import { Button, Badge, Card } from '$lib/index.js'\n\n // ==================== useInterval ====================\n let count = $state(0)\n const ticker = useInterval(() => count++, 1000)\n\n // ==================== useTimeout ====================\n let visible = $state(true)\n const dismiss = useTimeout(() => (visible = false), 3000)\n\n function show() {\n visible = true\n dismiss.restart()\n }\n</script>\n\n<div class=\"space-y-8\">\n <div>\n <h1 class=\"text-2xl font-bold text-on-surface\">useTimeout / useInterval</h1>\n <p class=\"mt-1 text-on-surface-variant\">\n Timers with proper runes teardown — cleared automatically on unmount, with a reactive\n delay, pause/resume, and restart/cancel.\n </p>\n </div>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">useInterval (1000ms)</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n Ticks every second. Pause and resume without leaking the timer.\n </p>\n\n <div class=\"flex flex-wrap items-center gap-4\">\n <span class=\"text-3xl font-bold text-primary tabular-nums\">{count}</span>\n <Badge color={ticker.active ? 'success' : 'secondary'}>\n {ticker.active ? 'ticking' : 'paused'}\n </Badge>\n {#if ticker.active}\n <Button size=\"sm\" variant=\"ghost\" onclick={ticker.pause}>Pause</Button>\n {:else}\n <Button size=\"sm\" variant=\"ghost\" onclick={ticker.resume}>Resume</Button>\n {/if}\n <Button size=\"sm\" variant=\"ghost\" onclick={() => (count = 0)}>Reset</Button>\n </div>\n </Card>\n\n <Card>\n <h2 class=\"mb-1 font-semibold text-on-surface\">useTimeout (3000ms)</h2>\n <p class=\"mb-4 text-sm text-on-surface-variant\">\n The message auto-dismisses after 3 seconds. <code>restart()</code> brings it back;\n <code>cancel()</code> keeps it.\n </p>\n\n <div class=\"flex items-center gap-3\">\n {#if visible}\n <Badge color=\"info\">Visible — dismissing in 3s…</Badge>\n <Button size=\"sm\" variant=\"ghost\" onclick={dismiss.cancel}>Keep it</Button>\n {:else}\n <span class=\"text-sm text-on-surface-variant\">Dismissed.</span>\n <Button size=\"sm\" color=\"primary\" onclick={show}>Show again</Button>\n {/if}\n </div>\n </Card>\n</div>\n"
139
157
  }
140
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelora",
3
- "version": "3.0.4",
3
+ "version": "3.0.6",
4
4
  "description": "Modern primitive-based UI component library for Svelte 5",
5
5
  "packageManager": "bun@1.3.14",
6
6
  "author": "asphum",
@@ -224,9 +224,9 @@
224
224
  "@types/node": "^26.0.0",
225
225
  "@vitest/browser-playwright": "^4.1.9",
226
226
  "joi": "^18.2.3",
227
- "playwright": "^1.61.0",
227
+ "playwright": "^1.61.1",
228
228
  "publint": "^0.3.21",
229
- "svelte": "^5.56.3",
229
+ "svelte": "^5.56.4",
230
230
  "svelte-check": "^4.7.0",
231
231
  "tailwindcss": "^4.3.1",
232
232
  "tiptap-markdown": "^0.9.0",