yaxa-svelte 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/assets/favicon.svg +15 -0
  4. package/dist/components/elements/Avatar.svelte +106 -0
  5. package/dist/components/elements/Avatar.svelte.d.ts +40 -0
  6. package/dist/components/elements/AvatarGroup.svelte +18 -0
  7. package/dist/components/elements/AvatarGroup.svelte.d.ts +8 -0
  8. package/dist/components/elements/Badge.svelte +202 -0
  9. package/dist/components/elements/Badge.svelte.d.ts +75 -0
  10. package/dist/components/elements/Button.svelte +300 -0
  11. package/dist/components/elements/Button.svelte.d.ts +109 -0
  12. package/dist/components/elements/ButtonGroup.svelte +22 -0
  13. package/dist/components/elements/ButtonGroup.svelte.d.ts +9 -0
  14. package/dist/components/elements/Chip.svelte +117 -0
  15. package/dist/components/elements/Chip.svelte.d.ts +15 -0
  16. package/dist/components/elements/DataTable.svelte +222 -0
  17. package/dist/components/elements/DataTable.svelte.d.ts +43 -0
  18. package/dist/components/elements/Icon.svelte +87 -0
  19. package/dist/components/elements/Icon.svelte.d.ts +10 -0
  20. package/dist/components/elements/Kbd.svelte +37 -0
  21. package/dist/components/elements/Kbd.svelte.d.ts +32 -0
  22. package/dist/components/elements/Link.svelte +63 -0
  23. package/dist/components/elements/Link.svelte.d.ts +15 -0
  24. package/dist/components/elements/Logo.svelte +77 -0
  25. package/dist/components/elements/Logo.svelte.d.ts +9 -0
  26. package/dist/components/elements/Meter.svelte +87 -0
  27. package/dist/components/elements/Meter.svelte.d.ts +14 -0
  28. package/dist/components/elements/Progress.svelte +63 -0
  29. package/dist/components/elements/Progress.svelte.d.ts +49 -0
  30. package/dist/components/elements/Skeleton.svelte +12 -0
  31. package/dist/components/elements/Skeleton.svelte.d.ts +6 -0
  32. package/dist/components/elements/Spinner.svelte +39 -0
  33. package/dist/components/elements/Spinner.svelte.d.ts +7 -0
  34. package/dist/components/forms/Checkbox.svelte +77 -0
  35. package/dist/components/forms/Checkbox.svelte.d.ts +17 -0
  36. package/dist/components/forms/ColorPicker.svelte +142 -0
  37. package/dist/components/forms/ColorPicker.svelte.d.ts +10 -0
  38. package/dist/components/forms/Form.svelte +84 -0
  39. package/dist/components/forms/Form.svelte.d.ts +14 -0
  40. package/dist/components/forms/FormField.svelte +56 -0
  41. package/dist/components/forms/FormField.svelte.d.ts +14 -0
  42. package/dist/components/forms/Input.svelte +138 -0
  43. package/dist/components/forms/Input.svelte.d.ts +58 -0
  44. package/dist/components/forms/RadioGroup.svelte +74 -0
  45. package/dist/components/forms/RadioGroup.svelte.d.ts +17 -0
  46. package/dist/components/forms/Select.svelte +91 -0
  47. package/dist/components/forms/Select.svelte.d.ts +50 -0
  48. package/dist/components/forms/Slider.svelte +37 -0
  49. package/dist/components/forms/Slider.svelte.d.ts +12 -0
  50. package/dist/components/forms/Switch.svelte +81 -0
  51. package/dist/components/forms/Switch.svelte.d.ts +53 -0
  52. package/dist/components/forms/Textarea.svelte +73 -0
  53. package/dist/components/forms/Textarea.svelte.d.ts +64 -0
  54. package/dist/components/layout/Card.svelte +89 -0
  55. package/dist/components/layout/Card.svelte.d.ts +64 -0
  56. package/dist/components/layout/Container.svelte +36 -0
  57. package/dist/components/layout/Container.svelte.d.ts +37 -0
  58. package/dist/components/layout/Divider.svelte +45 -0
  59. package/dist/components/layout/Divider.svelte.d.ts +11 -0
  60. package/dist/components/layout/Footer.svelte +96 -0
  61. package/dist/components/layout/Footer.svelte.d.ts +10 -0
  62. package/dist/components/layout/Header.svelte +175 -0
  63. package/dist/components/layout/Header.svelte.d.ts +12 -0
  64. package/dist/components/layout/Section.svelte +62 -0
  65. package/dist/components/layout/Section.svelte.d.ts +14 -0
  66. package/dist/components/layout/YaxaApp.svelte +48 -0
  67. package/dist/components/layout/YaxaApp.svelte.d.ts +12 -0
  68. package/dist/components/navigation/Breadcrumb.svelte +58 -0
  69. package/dist/components/navigation/Breadcrumb.svelte.d.ts +13 -0
  70. package/dist/components/navigation/CommandPalette.svelte +201 -0
  71. package/dist/components/navigation/CommandPalette.svelte.d.ts +19 -0
  72. package/dist/components/navigation/DropdownMenu.svelte +75 -0
  73. package/dist/components/navigation/DropdownMenu.svelte.d.ts +20 -0
  74. package/dist/components/navigation/Pagination.svelte +106 -0
  75. package/dist/components/navigation/Pagination.svelte.d.ts +10 -0
  76. package/dist/components/navigation/Tabs.svelte +91 -0
  77. package/dist/components/navigation/Tabs.svelte.d.ts +18 -0
  78. package/dist/components/overlays/Accordion.svelte +54 -0
  79. package/dist/components/overlays/Accordion.svelte.d.ts +16 -0
  80. package/dist/components/overlays/Alert.svelte +110 -0
  81. package/dist/components/overlays/Alert.svelte.d.ts +37 -0
  82. package/dist/components/overlays/ContextMenu.svelte +67 -0
  83. package/dist/components/overlays/ContextMenu.svelte.d.ts +21 -0
  84. package/dist/components/overlays/Modal.svelte +99 -0
  85. package/dist/components/overlays/Modal.svelte.d.ts +40 -0
  86. package/dist/components/overlays/Popover.svelte +33 -0
  87. package/dist/components/overlays/Popover.svelte.d.ts +11 -0
  88. package/dist/components/overlays/Slideover.svelte +102 -0
  89. package/dist/components/overlays/Slideover.svelte.d.ts +37 -0
  90. package/dist/components/overlays/Tooltip.svelte +39 -0
  91. package/dist/components/overlays/Tooltip.svelte.d.ts +12 -0
  92. package/dist/components/saas/AuthCard.svelte +273 -0
  93. package/dist/components/saas/AuthCard.svelte.d.ts +16 -0
  94. package/dist/components/saas/PricingCard.svelte +166 -0
  95. package/dist/components/saas/PricingCard.svelte.d.ts +19 -0
  96. package/dist/components/saas/PricingTable.svelte +139 -0
  97. package/dist/components/saas/PricingTable.svelte.d.ts +11 -0
  98. package/dist/components/saas/SubscriptionCard.svelte +162 -0
  99. package/dist/components/saas/SubscriptionCard.svelte.d.ts +16 -0
  100. package/dist/components/saas/UserMenu.svelte +137 -0
  101. package/dist/components/saas/UserMenu.svelte.d.ts +19 -0
  102. package/dist/components/seo/Favicons.svelte +22 -0
  103. package/dist/components/seo/Favicons.svelte.d.ts +8 -0
  104. package/dist/components/seo/OgImage.svelte +61 -0
  105. package/dist/components/seo/OgImage.svelte.d.ts +12 -0
  106. package/dist/components/seo/Seo.svelte +170 -0
  107. package/dist/components/seo/Seo.svelte.d.ts +21 -0
  108. package/dist/composables/useAuth.svelte.d.ts +23 -0
  109. package/dist/composables/useAuth.svelte.js +157 -0
  110. package/dist/composables/useClipboard.svelte.d.ts +9 -0
  111. package/dist/composables/useClipboard.svelte.js +36 -0
  112. package/dist/composables/useColorMode.svelte.d.ts +8 -0
  113. package/dist/composables/useColorMode.svelte.js +20 -0
  114. package/dist/composables/useDebounce.svelte.d.ts +3 -0
  115. package/dist/composables/useDebounce.svelte.js +17 -0
  116. package/dist/composables/useMediaQuery.svelte.d.ts +3 -0
  117. package/dist/composables/useMediaQuery.svelte.js +17 -0
  118. package/dist/composables/useShortcuts.svelte.d.ts +10 -0
  119. package/dist/composables/useShortcuts.svelte.js +50 -0
  120. package/dist/composables/useToast.d.ts +19 -0
  121. package/dist/composables/useToast.js +34 -0
  122. package/dist/index.d.ts +101 -0
  123. package/dist/index.js +92 -0
  124. package/dist/server/auth/hook.d.ts +14 -0
  125. package/dist/server/auth/hook.js +36 -0
  126. package/dist/server/auth/index.d.ts +39 -0
  127. package/dist/server/auth/index.js +69 -0
  128. package/dist/server/db/index.d.ts +18 -0
  129. package/dist/server/db/index.js +43 -0
  130. package/dist/server/db/schema-pg.d.ts +977 -0
  131. package/dist/server/db/schema-pg.js +83 -0
  132. package/dist/server/db/schema-sqlite.d.ts +1045 -0
  133. package/dist/server/db/schema-sqlite.js +83 -0
  134. package/dist/server/db/types.d.ts +44 -0
  135. package/dist/server/db/types.js +1 -0
  136. package/dist/server/email/index.d.ts +27 -0
  137. package/dist/server/email/index.js +79 -0
  138. package/dist/server/polar/index.d.ts +29 -0
  139. package/dist/server/polar/index.js +49 -0
  140. package/dist/server/polar/webhook.d.ts +16 -0
  141. package/dist/server/polar/webhook.js +118 -0
  142. package/dist/site/config.d.ts +63 -0
  143. package/dist/site/config.js +48 -0
  144. package/dist/site/context.d.ts +11 -0
  145. package/dist/site/context.js +23 -0
  146. package/dist/site/hook.d.ts +15 -0
  147. package/dist/site/hook.js +38 -0
  148. package/dist/site/manifest.d.ts +6 -0
  149. package/dist/site/manifest.js +32 -0
  150. package/dist/site/og.d.ts +14 -0
  151. package/dist/site/og.js +116 -0
  152. package/dist/site/robots.d.ts +10 -0
  153. package/dist/site/robots.js +54 -0
  154. package/dist/site/schema.d.ts +67 -0
  155. package/dist/site/schema.js +67 -0
  156. package/dist/site/seo-helpers.d.ts +36 -0
  157. package/dist/site/seo-helpers.js +21 -0
  158. package/dist/site/sitemap-xsl.d.ts +6 -0
  159. package/dist/site/sitemap-xsl.js +164 -0
  160. package/dist/site/sitemap.d.ts +20 -0
  161. package/dist/site/sitemap.js +61 -0
  162. package/dist/styles/yaxa.css +84 -0
  163. package/dist/theme/theme.svelte.d.ts +74 -0
  164. package/dist/theme/theme.svelte.js +421 -0
  165. package/dist/utils/cn.d.ts +3 -0
  166. package/dist/utils/cn.js +3 -0
  167. package/dist/vite/index.d.ts +20 -0
  168. package/dist/vite/index.js +29 -0
  169. package/package.json +108 -0
@@ -0,0 +1,175 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { SiteConfig, NavItem } from '../../site/config';
4
+ import { theme } from '../../theme/theme.svelte';
5
+ import Container from './Container.svelte';
6
+ import Button from '../elements/Button.svelte';
7
+ import Icon from '../elements/Icon.svelte';
8
+ import Kbd from '../elements/Kbd.svelte';
9
+ import Link from '../elements/Link.svelte';
10
+ import Logo from '../elements/Logo.svelte';
11
+ import { getSiteConfig } from '../../site/context';
12
+
13
+ interface Props {
14
+ config?: SiteConfig;
15
+ links?: NavItem[];
16
+ onOpenCommand?: () => void;
17
+ actions?: Snippet;
18
+ class?: string;
19
+ }
20
+
21
+ let { config, links, onOpenCommand, actions, class: className = '' }: Props = $props();
22
+
23
+ let currentConfig = $derived(config || getSiteConfig());
24
+ let resolvedLinks = $derived(links || currentConfig.nav || []);
25
+
26
+ let mobileMenuOpen = $state(false);
27
+ let isScrolled = $state(false);
28
+ </script>
29
+
30
+ <svelte:window
31
+ onscroll={() => {
32
+ if (typeof window !== 'undefined') {
33
+ isScrolled = window.scrollY > 15;
34
+ }
35
+ }}
36
+ />
37
+
38
+ <header
39
+ class="sticky top-0 z-40 w-full transition-all duration-200 {isScrolled
40
+ ? 'border-b border-neutral-200/80 bg-white/95 shadow-xs backdrop-blur-md dark:border-neutral-800/80 dark:bg-[#121212]/95'
41
+ : 'border-b border-transparent bg-white/60 backdrop-blur-sm dark:border-transparent dark:bg-[#121212]/60'} {className}"
42
+ >
43
+ <Container size="2xl">
44
+ <div class="flex h-16 items-center justify-between gap-4">
45
+ <!-- Logo, Brand & Version Pill -->
46
+ <div class="flex items-center gap-6">
47
+ <div class="flex items-center gap-2.5">
48
+ <a
49
+ href="/"
50
+ class="flex items-center gap-2.5 font-bold tracking-tight text-neutral-900 transition-opacity hover:opacity-90 dark:text-white"
51
+ >
52
+ <Logo size="sm" />
53
+ <span class="text-base font-extrabold tracking-tight">{currentConfig.name}</span>
54
+ </a>
55
+ </div>
56
+
57
+ <!-- Desktop Navigation Links -->
58
+ <nav class="hidden items-center gap-1 md:flex">
59
+ {#each resolvedLinks as item}
60
+ <Link
61
+ href={item.href}
62
+ class="rounded-md px-3 py-1.5 text-sm font-medium text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-300 dark:hover:text-white"
63
+ activeClass="text-primary-600 dark:text-primary-400 font-semibold"
64
+ >
65
+ {#if item.icon}
66
+ <Icon name={item.icon} size="xs" />
67
+ {/if}
68
+ {item.label}
69
+ {#if item.badge}
70
+ <span
71
+ class="ml-1 rounded bg-primary-100 px-1.5 py-0.5 text-xs font-semibold text-primary-700 dark:bg-primary-950 dark:text-primary-300"
72
+ >
73
+ {item.badge}
74
+ </span>
75
+ {/if}
76
+ </Link>
77
+ {/each}
78
+ </nav>
79
+ </div>
80
+
81
+ <!-- Right Section: Search, Theme Toggle, Actions, Socials -->
82
+ <div class="flex items-center gap-2">
83
+ {#if onOpenCommand}
84
+ <button
85
+ onclick={onOpenCommand}
86
+ type="button"
87
+ class="hidden items-center gap-2 rounded-lg border border-neutral-200/80 bg-neutral-100/60 px-3 py-1.5 text-sm text-neutral-500 transition-colors hover:border-neutral-300 hover:bg-neutral-100 sm:flex dark:border-neutral-800 dark:bg-neutral-900/60 dark:text-neutral-400 dark:hover:border-neutral-700 dark:hover:bg-neutral-800"
88
+ aria-label="Search or run command"
89
+ >
90
+ <Icon name="search" size="xs" />
91
+ <span>Search documentation...</span>
92
+ <Kbd value="CTRL K" size="xs" class="ml-3" />
93
+ </button>
94
+ {/if}
95
+
96
+ <!-- Theme Mode Switcher -->
97
+ <Button
98
+ variant="ghost"
99
+ color="neutral"
100
+ square
101
+ size="sm"
102
+ onclick={() => theme.toggle()}
103
+ aria-label="Toggle color mode"
104
+ >
105
+ {#if theme.resolvedTheme === 'dark'}
106
+ <Icon name="sun" size="sm" class="text-amber-400" />
107
+ {:else}
108
+ <Icon name="moon" size="sm" class="text-neutral-600" />
109
+ {/if}
110
+ </Button>
111
+
112
+ <!-- GitHub Link -->
113
+ {#if currentConfig.socials?.github}
114
+ <Button
115
+ href={currentConfig.socials.github}
116
+ variant="ghost"
117
+ color="neutral"
118
+ square
119
+ size="sm"
120
+ aria-label="GitHub Repository"
121
+ >
122
+ <Icon name="github" size="sm" />
123
+ </Button>
124
+ {/if}
125
+
126
+ {#if actions}
127
+ {@render actions()}
128
+ {/if}
129
+
130
+ <!-- Mobile menu button -->
131
+ <div class="flex md:hidden">
132
+ <Button
133
+ variant="ghost"
134
+ color="neutral"
135
+ square
136
+ size="sm"
137
+ onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
138
+ aria-label="Toggle navigation menu"
139
+ >
140
+ {#if mobileMenuOpen}
141
+ <Icon name="cross" size="sm" />
142
+ {:else}
143
+ <svg
144
+ class="h-5 w-5"
145
+ fill="none"
146
+ viewBox="0 0 24 24"
147
+ stroke="currentColor"
148
+ stroke-width="2"
149
+ >
150
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
151
+ </svg>
152
+ {/if}
153
+ </Button>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <!-- Mobile Navigation Dropdown -->
159
+ {#if mobileMenuOpen}
160
+ <div class="border-t border-neutral-200 py-4 md:hidden dark:border-neutral-800">
161
+ <div class="flex flex-col space-y-2">
162
+ {#each resolvedLinks as item}
163
+ <a
164
+ href={item.href}
165
+ onclick={() => (mobileMenuOpen = false)}
166
+ class="rounded-lg px-3 py-2 text-base font-medium text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
167
+ >
168
+ {item.label}
169
+ </a>
170
+ {/each}
171
+ </div>
172
+ </div>
173
+ {/if}
174
+ </Container>
175
+ </header>
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { SiteConfig, NavItem } from '../../site/config';
3
+ interface Props {
4
+ config?: SiteConfig;
5
+ links?: NavItem[];
6
+ onOpenCommand?: () => void;
7
+ actions?: Snippet;
8
+ class?: string;
9
+ }
10
+ declare const Header: import("svelte").Component<Props, {}, "">;
11
+ type Header = ReturnType<typeof Header>;
12
+ export default Header;
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Container from './Container.svelte';
4
+
5
+ interface Props {
6
+ id?: string;
7
+ title?: string;
8
+ description?: string;
9
+ badge?: string;
10
+ align?: 'left' | 'center';
11
+ class?: string;
12
+ header?: Snippet;
13
+ children?: Snippet;
14
+ }
15
+
16
+ let {
17
+ id,
18
+ title,
19
+ description,
20
+ badge,
21
+ align = 'center',
22
+ class: className = '',
23
+ header,
24
+ children
25
+ }: Props = $props();
26
+ </script>
27
+
28
+ <section {id} class="py-16 sm:py-24 {className}">
29
+ <Container>
30
+ {#if header}
31
+ {@render header()}
32
+ {:else if title || description || badge}
33
+ <div
34
+ class="mb-12 {align === 'center' ? 'mx-auto max-w-3xl text-center' : 'max-w-2xl text-left'}"
35
+ >
36
+ {#if badge}
37
+ <span
38
+ class="mb-4 inline-flex items-center rounded-full bg-primary-50 px-3 py-1 text-sm font-semibold text-primary-700 ring-1 ring-primary-500/20 dark:bg-primary-950/60 dark:text-primary-300 dark:ring-primary-500/30"
39
+ >
40
+ {badge}
41
+ </span>
42
+ {/if}
43
+ {#if title}
44
+ <h2
45
+ class="text-3xl font-extrabold tracking-tight text-neutral-900 sm:text-4xl dark:text-white"
46
+ >
47
+ {title}
48
+ </h2>
49
+ {/if}
50
+ {#if description}
51
+ <p class="mt-4 text-lg text-neutral-600 dark:text-neutral-400">
52
+ {description}
53
+ </p>
54
+ {/if}
55
+ </div>
56
+ {/if}
57
+
58
+ {#if children}
59
+ {@render children()}
60
+ {/if}
61
+ </Container>
62
+ </section>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ id?: string;
4
+ title?: string;
5
+ description?: string;
6
+ badge?: string;
7
+ align?: 'left' | 'center';
8
+ class?: string;
9
+ header?: Snippet;
10
+ children?: Snippet;
11
+ }
12
+ declare const Section: import("svelte").Component<Props, {}, "">;
13
+ type Section = ReturnType<typeof Section>;
14
+ export default Section;
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { Toaster } from 'svelte-sonner';
4
+ import { Tooltip } from 'bits-ui';
5
+ import { type SiteConfig, DEFAULT_SITE_CONFIG } from '../../site/config';
6
+ import { themeInitScript } from '../../theme/theme.svelte';
7
+ import Favicons from '../seo/Favicons.svelte';
8
+ import Seo from '../seo/Seo.svelte';
9
+ import { setSiteConfig } from '../../site/context';
10
+ import '../../styles/yaxa.css';
11
+
12
+ export interface YaxaAppProps {
13
+ config?: SiteConfig;
14
+ enableDefaultSeo?: boolean;
15
+ children?: Snippet;
16
+ }
17
+
18
+ export type Props = YaxaAppProps;
19
+
20
+ let { config = DEFAULT_SITE_CONFIG, enableDefaultSeo = true, children }: YaxaAppProps = $props();
21
+
22
+ // Provide config to all descendant components via Svelte 5 context
23
+ setSiteConfig(() => config);
24
+
25
+ const scriptTag = `<script>${themeInitScript}</` + `script>`;
26
+ </script>
27
+
28
+ <svelte:head>
29
+ {@html scriptTag}
30
+ </svelte:head>
31
+
32
+ {#if enableDefaultSeo}
33
+ <Seo {config} />
34
+ {/if}
35
+ <Favicons {config} />
36
+
37
+ <Tooltip.Provider delayDuration={200}>
38
+ <div
39
+ class="flex min-h-screen flex-col bg-white text-neutral-900 transition-colors dark:bg-neutral-950 dark:text-neutral-50"
40
+ >
41
+ {#if children}
42
+ {@render children()}
43
+ {/if}
44
+ </div>
45
+ </Tooltip.Provider>
46
+
47
+ <!-- Global Toast Container -->
48
+ <Toaster richColors position="top-right" closeButton />
@@ -0,0 +1,12 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type SiteConfig } from '../../site/config';
3
+ import '../../styles/yaxa.css';
4
+ export interface YaxaAppProps {
5
+ config?: SiteConfig;
6
+ enableDefaultSeo?: boolean;
7
+ children?: Snippet;
8
+ }
9
+ export type Props = YaxaAppProps;
10
+ declare const YaxaApp: import("svelte").Component<YaxaAppProps, {}, "">;
11
+ type YaxaApp = ReturnType<typeof YaxaApp>;
12
+ export default YaxaApp;
@@ -0,0 +1,58 @@
1
+ <script module lang="ts">
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ icon?: string;
6
+ }
7
+ </script>
8
+
9
+ <script lang="ts">
10
+ import Icon from '../elements/Icon.svelte';
11
+
12
+ interface Props {
13
+ items: BreadcrumbItem[];
14
+ separator?: string;
15
+ class?: string;
16
+ }
17
+
18
+ let { items, separator = 'chevron-right', class: className = '' }: Props = $props();
19
+ </script>
20
+
21
+ <nav aria-label="Breadcrumb" class="flex {className}">
22
+ <ol
23
+ class="inline-flex items-center space-x-1.5 text-sm text-neutral-500 md:space-x-2 dark:text-neutral-400"
24
+ >
25
+ {#each items as item, index}
26
+ {@const isLast = index === items.length - 1}
27
+ <li class="inline-flex items-center">
28
+ {#if index > 0}
29
+ <Icon name={separator} size="xs" class="mx-1 text-neutral-400" />
30
+ {/if}
31
+
32
+ {#if item.href && !isLast}
33
+ <a
34
+ href={item.href}
35
+ class="inline-flex items-center gap-1.5 transition-colors hover:text-neutral-900 dark:hover:text-white"
36
+ >
37
+ {#if item.icon}
38
+ <Icon name={item.icon} size="xs" />
39
+ {/if}
40
+ <span>{item.label}</span>
41
+ </a>
42
+ {:else}
43
+ <span
44
+ class="inline-flex items-center gap-1.5 font-medium {isLast
45
+ ? 'text-neutral-900 dark:text-white'
46
+ : ''}"
47
+ aria-current={isLast ? 'page' : undefined}
48
+ >
49
+ {#if item.icon}
50
+ <Icon name={item.icon} size="xs" />
51
+ {/if}
52
+ <span>{item.label}</span>
53
+ </span>
54
+ {/if}
55
+ </li>
56
+ {/each}
57
+ </ol>
58
+ </nav>
@@ -0,0 +1,13 @@
1
+ export interface BreadcrumbItem {
2
+ label: string;
3
+ href?: string;
4
+ icon?: string;
5
+ }
6
+ interface Props {
7
+ items: BreadcrumbItem[];
8
+ separator?: string;
9
+ class?: string;
10
+ }
11
+ declare const Breadcrumb: import("svelte").Component<Props, {}, "">;
12
+ type Breadcrumb = ReturnType<typeof Breadcrumb>;
13
+ export default Breadcrumb;
@@ -0,0 +1,201 @@
1
+ <script module lang="ts">
2
+ export interface CommandItem {
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ icon?: string;
7
+ group?: string;
8
+ shortcut?: string;
9
+ onSelect?: () => void;
10
+ href?: string;
11
+ }
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import { browser } from '$app/environment';
16
+ import Icon from '../elements/Icon.svelte';
17
+ import Kbd from '../elements/Kbd.svelte';
18
+
19
+ interface Props {
20
+ open?: boolean;
21
+ items?: CommandItem[];
22
+ placeholder?: string;
23
+ class?: string;
24
+ }
25
+
26
+ let {
27
+ open = $bindable(false),
28
+ items = [],
29
+ placeholder = 'Type a command or search...',
30
+ class: className = ''
31
+ }: Props = $props();
32
+
33
+ let query = $state('');
34
+ let selectedIndex = $state(0);
35
+
36
+ let filteredItems = $derived.by(() => {
37
+ if (!query.trim()) return items;
38
+ const q = query.toLowerCase();
39
+ return items.filter(
40
+ (item) =>
41
+ item.label.toLowerCase().includes(q) ||
42
+ item.description?.toLowerCase().includes(q) ||
43
+ item.group?.toLowerCase().includes(q)
44
+ );
45
+ });
46
+
47
+ let groupedItems = $derived.by(() => {
48
+ const groups: Record<string, CommandItem[]> = {};
49
+ filteredItems.forEach((item) => {
50
+ const groupName = item.group || 'General';
51
+ if (!groups[groupName]) groups[groupName] = [];
52
+ groups[groupName].push(item);
53
+ });
54
+ return groups;
55
+ });
56
+
57
+ function focusOnMount(node: HTMLElement) {
58
+ node.focus();
59
+ }
60
+
61
+ function handleKeydown(e: KeyboardEvent) {
62
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
63
+ e.preventDefault();
64
+ open = !open;
65
+ if (open) query = '';
66
+ }
67
+
68
+ if (!open) return;
69
+
70
+ if (e.key === 'Escape') {
71
+ open = false;
72
+ } else if (e.key === 'ArrowDown') {
73
+ e.preventDefault();
74
+ selectedIndex = (selectedIndex + 1) % Math.max(1, filteredItems.length);
75
+ } else if (e.key === 'ArrowUp') {
76
+ e.preventDefault();
77
+ selectedIndex =
78
+ (selectedIndex - 1 + filteredItems.length) % Math.max(1, filteredItems.length);
79
+ } else if (e.key === 'Enter') {
80
+ e.preventDefault();
81
+ const selected = filteredItems[selectedIndex];
82
+ if (selected) {
83
+ executeItem(selected);
84
+ }
85
+ }
86
+ }
87
+
88
+ function executeItem(item: CommandItem) {
89
+ open = false;
90
+ if (item.onSelect) item.onSelect();
91
+ if (item.href && browser) {
92
+ window.location.href = item.href;
93
+ }
94
+ }
95
+
96
+ $effect(() => {
97
+ if (browser) {
98
+ window.addEventListener('keydown', handleKeydown);
99
+ return () => window.removeEventListener('keydown', handleKeydown);
100
+ }
101
+ });
102
+ </script>
103
+
104
+ {#if open}
105
+ <div
106
+ class="fixed inset-0 z-50 flex items-start justify-center p-4 pt-[15vh] sm:p-6 sm:pt-[20vh]"
107
+ role="dialog"
108
+ aria-modal="true"
109
+ >
110
+ <!-- Backdrop -->
111
+ <button
112
+ type="button"
113
+ class="fixed inset-0 bg-neutral-900/60 backdrop-blur-xs transition-opacity"
114
+ onclick={() => (open = false)}
115
+ aria-label="Close command palette"
116
+ ></button>
117
+
118
+ <!-- Dialog Panel -->
119
+ <div
120
+ class="relative w-full max-w-xl overflow-hidden rounded-2xl border border-neutral-200 bg-white shadow-2xl transition-all dark:border-neutral-800 dark:bg-neutral-900 {className}"
121
+ >
122
+ <!-- Search Bar -->
123
+ <div class="flex items-center border-b border-neutral-200 px-4 dark:border-neutral-800">
124
+ <Icon name="search" size="sm" class="mr-3 text-neutral-400" />
125
+ <input
126
+ type="text"
127
+ bind:value={query}
128
+ {placeholder}
129
+ class="h-12 w-full bg-transparent text-sm text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white"
130
+ use:focusOnMount
131
+ />
132
+ <Kbd value="ESC" size="xs" />
133
+ </div>
134
+
135
+ <!-- List Items -->
136
+ <div class="max-h-80 overflow-y-auto p-2">
137
+ {#if filteredItems.length === 0}
138
+ <div class="py-10 text-center text-sm text-neutral-500">
139
+ No results found for "{query}".
140
+ </div>
141
+ {:else}
142
+ {#each Object.entries(groupedItems) as [groupName, groupList]}
143
+ <div class="mb-2">
144
+ <div
145
+ class="px-3 py-1.5 text-[11px] font-semibold tracking-wider text-neutral-400 uppercase dark:text-neutral-500"
146
+ >
147
+ {groupName}
148
+ </div>
149
+ {#each groupList as item}
150
+ {@const isHighlighted = filteredItems.indexOf(item) === selectedIndex}
151
+ <button
152
+ type="button"
153
+ onclick={() => executeItem(item)}
154
+ class="flex w-full items-center justify-between rounded-xl px-3 py-2 text-left text-sm transition-colors {isHighlighted
155
+ ? 'bg-primary-50 text-primary-900 dark:bg-primary-950/60 dark:text-primary-100'
156
+ : 'text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800/60'}"
157
+ >
158
+ <div class="flex items-center gap-3">
159
+ {#if item.icon}
160
+ <Icon
161
+ name={item.icon}
162
+ size="sm"
163
+ class={isHighlighted
164
+ ? 'text-primary-600 dark:text-primary-400'
165
+ : 'text-neutral-400'}
166
+ />
167
+ {/if}
168
+ <div>
169
+ <div class="font-medium">{item.label}</div>
170
+ {#if item.description}
171
+ <div class="text-sm text-neutral-500 dark:text-neutral-400">
172
+ {item.description}
173
+ </div>
174
+ {/if}
175
+ </div>
176
+ </div>
177
+
178
+ {#if item.shortcut}
179
+ <Kbd value={item.shortcut} size="xs" />
180
+ {/if}
181
+ </button>
182
+ {/each}
183
+ </div>
184
+ {/each}
185
+ {/if}
186
+ </div>
187
+
188
+ <!-- Footer -->
189
+ <div
190
+ class="flex items-center justify-between border-t border-neutral-100 bg-neutral-50/80 px-4 py-2 text-[11px] text-neutral-400 dark:border-neutral-800/80 dark:bg-neutral-900/50"
191
+ >
192
+ <span class="flex items-center gap-1.5">
193
+ <Kbd value="↑↓" size="xs" /> Navigate
194
+ </span>
195
+ <span class="flex items-center gap-1.5">
196
+ <Kbd value="↵" size="xs" /> Select
197
+ </span>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ {/if}
@@ -0,0 +1,19 @@
1
+ export interface CommandItem {
2
+ id: string;
3
+ label: string;
4
+ description?: string;
5
+ icon?: string;
6
+ group?: string;
7
+ shortcut?: string;
8
+ onSelect?: () => void;
9
+ href?: string;
10
+ }
11
+ interface Props {
12
+ open?: boolean;
13
+ items?: CommandItem[];
14
+ placeholder?: string;
15
+ class?: string;
16
+ }
17
+ declare const CommandPalette: import("svelte").Component<Props, {}, "open">;
18
+ type CommandPalette = ReturnType<typeof CommandPalette>;
19
+ export default CommandPalette;
@@ -0,0 +1,75 @@
1
+ <script module lang="ts">
2
+ export interface MenuItem {
3
+ id: string;
4
+ label: string;
5
+ icon?: string;
6
+ shortcut?: string;
7
+ disabled?: boolean;
8
+ separator?: boolean;
9
+ destructive?: boolean;
10
+ onSelect?: () => void;
11
+ }
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import { DropdownMenu } from 'bits-ui';
16
+ import type { Snippet } from 'svelte';
17
+ import Icon from '../elements/Icon.svelte';
18
+ import Kbd from '../elements/Kbd.svelte';
19
+
20
+ interface Props {
21
+ items: MenuItem[];
22
+ trigger?: Snippet;
23
+ class?: string;
24
+ }
25
+
26
+ let { items, trigger, class: className = '' }: Props = $props();
27
+ </script>
28
+
29
+ <DropdownMenu.Root>
30
+ <DropdownMenu.Trigger class="inline-flex">
31
+ {#if trigger}
32
+ {@render trigger()}
33
+ {:else}
34
+ <button
35
+ type="button"
36
+ class="inline-flex items-center gap-1 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm font-medium text-neutral-800 shadow-xs hover:bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:bg-neutral-800"
37
+ >
38
+ Options
39
+ <Icon name="chevron-down" size="xs" />
40
+ </button>
41
+ {/if}
42
+ </DropdownMenu.Trigger>
43
+
44
+ <DropdownMenu.Portal>
45
+ <DropdownMenu.Content
46
+ class="animate-in fade-in-0 zoom-in-95 z-50 min-w-48 overflow-hidden rounded-xl border border-neutral-200 bg-white p-1.5 text-neutral-900 shadow-xl dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100 {className}"
47
+ sideOffset={6}
48
+ >
49
+ {#each items as item}
50
+ {#if item.separator}
51
+ <DropdownMenu.Separator class="-mx-1 my-1 h-px bg-neutral-200 dark:bg-neutral-800" />
52
+ {:else}
53
+ <DropdownMenu.Item
54
+ disabled={item.disabled}
55
+ onSelect={item.onSelect}
56
+ class="flex cursor-pointer items-center justify-between rounded-lg px-2.5 py-1.5 text-sm transition-colors outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-neutral-100 data-highlighted:text-neutral-900 dark:data-highlighted:bg-neutral-800 dark:data-highlighted:text-neutral-100 {item.destructive
57
+ ? 'text-rose-600 data-highlighted:bg-rose-50 dark:text-rose-400 dark:data-highlighted:bg-rose-950/50'
58
+ : ''}"
59
+ >
60
+ <div class="flex items-center gap-2">
61
+ {#if item.icon}
62
+ <Icon name={item.icon} size="xs" />
63
+ {/if}
64
+ <span>{item.label}</span>
65
+ </div>
66
+
67
+ {#if item.shortcut}
68
+ <Kbd value={item.shortcut} size="xs" />
69
+ {/if}
70
+ </DropdownMenu.Item>
71
+ {/if}
72
+ {/each}
73
+ </DropdownMenu.Content>
74
+ </DropdownMenu.Portal>
75
+ </DropdownMenu.Root>