ui-svelte 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/dist/charts/ArcChart.svelte +320 -0
  4. package/dist/charts/ArcChart.svelte.d.ts +26 -0
  5. package/dist/charts/AreaChart.svelte +495 -0
  6. package/dist/charts/AreaChart.svelte.d.ts +32 -0
  7. package/dist/charts/BarChart.svelte +504 -0
  8. package/dist/charts/BarChart.svelte.d.ts +38 -0
  9. package/dist/charts/Candlestick.svelte +527 -0
  10. package/dist/charts/Candlestick.svelte.d.ts +38 -0
  11. package/dist/charts/LineChart.svelte +365 -0
  12. package/dist/charts/LineChart.svelte.d.ts +36 -0
  13. package/dist/charts/PieChart.svelte +311 -0
  14. package/dist/charts/PieChart.svelte.d.ts +28 -0
  15. package/dist/charts/css/arc-chart.css +237 -0
  16. package/dist/charts/css/area-chart.css +289 -0
  17. package/dist/charts/css/bar-chart.css +167 -0
  18. package/dist/charts/css/candlestick.css +197 -0
  19. package/dist/charts/css/line-chart.css +202 -0
  20. package/dist/charts/css/pie-chart.css +199 -0
  21. package/dist/control/Audio.svelte +212 -0
  22. package/dist/control/Audio.svelte.d.ts +8 -0
  23. package/dist/control/Button.svelte +116 -0
  24. package/dist/control/Button.svelte.d.ts +22 -0
  25. package/dist/control/IconButton.svelte +104 -0
  26. package/dist/control/IconButton.svelte.d.ts +17 -0
  27. package/dist/control/Record.svelte +430 -0
  28. package/dist/control/Record.svelte.d.ts +11 -0
  29. package/dist/control/ToggleTheme.svelte +21 -0
  30. package/dist/control/ToggleTheme.svelte.d.ts +8 -0
  31. package/dist/control/Video.svelte +222 -0
  32. package/dist/control/Video.svelte.d.ts +10 -0
  33. package/dist/control/css/btn.css +206 -0
  34. package/dist/control/css/media.css +78 -0
  35. package/dist/control/css/video.css +58 -0
  36. package/dist/css/animations.css +27 -0
  37. package/dist/css/base.css +192 -0
  38. package/dist/css/utilities.css +136 -0
  39. package/dist/display/Accordion.svelte +98 -0
  40. package/dist/display/Accordion.svelte.d.ts +20 -0
  41. package/dist/display/Alert.svelte +65 -0
  42. package/dist/display/Alert.svelte.d.ts +15 -0
  43. package/dist/display/Avatar.svelte +80 -0
  44. package/dist/display/Avatar.svelte.d.ts +13 -0
  45. package/dist/display/Badge.svelte +46 -0
  46. package/dist/display/Badge.svelte.d.ts +11 -0
  47. package/dist/display/Card.svelte +94 -0
  48. package/dist/display/Card.svelte.d.ts +21 -0
  49. package/dist/display/Carousel.svelte +359 -0
  50. package/dist/display/Carousel.svelte.d.ts +25 -0
  51. package/dist/display/ChatBox.svelte +249 -0
  52. package/dist/display/ChatBox.svelte.d.ts +18 -0
  53. package/dist/display/Chip.svelte +67 -0
  54. package/dist/display/Chip.svelte.d.ts +17 -0
  55. package/dist/display/Code.svelte +56 -0
  56. package/dist/display/Code.svelte.d.ts +9 -0
  57. package/dist/display/Collapsible.svelte +71 -0
  58. package/dist/display/Collapsible.svelte.d.ts +15 -0
  59. package/dist/display/Divider.svelte +32 -0
  60. package/dist/display/Divider.svelte.d.ts +10 -0
  61. package/dist/display/Empty.svelte +462 -0
  62. package/dist/display/Empty.svelte.d.ts +11 -0
  63. package/dist/display/Icon.svelte +20 -0
  64. package/dist/display/Icon.svelte.d.ts +11 -0
  65. package/dist/display/Item.svelte +119 -0
  66. package/dist/display/Item.svelte.d.ts +24 -0
  67. package/dist/display/Loading.svelte +8 -0
  68. package/dist/display/Loading.svelte.d.ts +26 -0
  69. package/dist/display/Marquee.svelte +164 -0
  70. package/dist/display/Marquee.svelte.d.ts +21 -0
  71. package/dist/display/Section.svelte +63 -0
  72. package/dist/display/Section.svelte.d.ts +16 -0
  73. package/dist/display/Table.svelte +407 -0
  74. package/dist/display/Table.svelte.d.ts +32 -0
  75. package/dist/display/TypeWriter.svelte +23 -0
  76. package/dist/display/TypeWriter.svelte.d.ts +11 -0
  77. package/dist/display/User.svelte +0 -0
  78. package/dist/display/User.svelte.d.ts +26 -0
  79. package/dist/display/css/accordion.css +98 -0
  80. package/dist/display/css/alert.css +51 -0
  81. package/dist/display/css/avatar.css +158 -0
  82. package/dist/display/css/badge.css +47 -0
  83. package/dist/display/css/card.css +231 -0
  84. package/dist/display/css/carousel.css +156 -0
  85. package/dist/display/css/chat-box.css +188 -0
  86. package/dist/display/css/chip.css +91 -0
  87. package/dist/display/css/code.css +19 -0
  88. package/dist/display/css/collapsible.css +86 -0
  89. package/dist/display/css/divider.css +54 -0
  90. package/dist/display/css/empty.css +8 -0
  91. package/dist/display/css/item.css +149 -0
  92. package/dist/display/css/listbox.css +24 -0
  93. package/dist/display/css/marquee.css +138 -0
  94. package/dist/display/css/section.css +85 -0
  95. package/dist/display/css/table.css +361 -0
  96. package/dist/form/Checkbox.svelte +45 -0
  97. package/dist/form/Checkbox.svelte.d.ts +13 -0
  98. package/dist/form/ComboBox.svelte +448 -0
  99. package/dist/form/ComboBox.svelte.d.ts +29 -0
  100. package/dist/form/CsvField.svelte +389 -0
  101. package/dist/form/CsvField.svelte.d.ts +21 -0
  102. package/dist/form/DateField.svelte +292 -0
  103. package/dist/form/DateField.svelte.d.ts +18 -0
  104. package/dist/form/Dropzone.svelte +196 -0
  105. package/dist/form/Dropzone.svelte.d.ts +30 -0
  106. package/dist/form/ImageCropper.svelte +254 -0
  107. package/dist/form/ImageCropper.svelte.d.ts +14 -0
  108. package/dist/form/PasswordField.svelte +170 -0
  109. package/dist/form/PasswordField.svelte.d.ts +28 -0
  110. package/dist/form/PhoneField.svelte +485 -0
  111. package/dist/form/PhoneField.svelte.d.ts +25 -0
  112. package/dist/form/PinField.svelte +139 -0
  113. package/dist/form/PinField.svelte.d.ts +17 -0
  114. package/dist/form/RadioGroup.svelte +70 -0
  115. package/dist/form/RadioGroup.svelte.d.ts +19 -0
  116. package/dist/form/Select.svelte +350 -0
  117. package/dist/form/Select.svelte.d.ts +26 -0
  118. package/dist/form/Slider.svelte +60 -0
  119. package/dist/form/Slider.svelte.d.ts +15 -0
  120. package/dist/form/TextField.svelte +154 -0
  121. package/dist/form/TextField.svelte.d.ts +31 -0
  122. package/dist/form/Textarea.svelte +137 -0
  123. package/dist/form/Textarea.svelte.d.ts +27 -0
  124. package/dist/form/Toggle.svelte +45 -0
  125. package/dist/form/Toggle.svelte.d.ts +13 -0
  126. package/dist/form/css/checkbox.css +46 -0
  127. package/dist/form/css/combo-box.css +69 -0
  128. package/dist/form/css/control.css +177 -0
  129. package/dist/form/css/csv-field.css +0 -0
  130. package/dist/form/css/date.css +56 -0
  131. package/dist/form/css/dropzone.css +133 -0
  132. package/dist/form/css/field.css +17 -0
  133. package/dist/form/css/image-cropper.css +155 -0
  134. package/dist/form/css/password.css +35 -0
  135. package/dist/form/css/radio-group.css +57 -0
  136. package/dist/form/css/select.css +18 -0
  137. package/dist/form/css/slider.css +80 -0
  138. package/dist/form/css/textarea.css +130 -0
  139. package/dist/form/css/toggle.css +27 -0
  140. package/dist/form/js/countries.d.ts +13 -0
  141. package/dist/form/js/countries.js +307 -0
  142. package/dist/form/js/phone-examples.d.ts +248 -0
  143. package/dist/form/js/phone-examples.js +247 -0
  144. package/dist/hooks/use-auth.svelte.d.ts +11 -0
  145. package/dist/hooks/use-auth.svelte.js +59 -0
  146. package/dist/hooks/use-chat.svelte.d.ts +40 -0
  147. package/dist/hooks/use-chat.svelte.js +265 -0
  148. package/dist/hooks/use-clipboard.svelte.d.ts +9 -0
  149. package/dist/hooks/use-clipboard.svelte.js +52 -0
  150. package/dist/hooks/use-fetch.svelte.d.ts +11 -0
  151. package/dist/hooks/use-fetch.svelte.js +38 -0
  152. package/dist/hooks/use-form.svelte.d.ts +31 -0
  153. package/dist/hooks/use-form.svelte.js +110 -0
  154. package/dist/hooks/use-localstorage.svelte.d.ts +3 -0
  155. package/dist/hooks/use-localstorage.svelte.js +26 -0
  156. package/dist/hooks/use-scroll.svelte.d.ts +6 -0
  157. package/dist/hooks/use-scroll.svelte.js +34 -0
  158. package/dist/hooks/use-search.svelte.d.ts +49 -0
  159. package/dist/hooks/use-search.svelte.js +229 -0
  160. package/dist/hooks/use-table.svelte.d.ts +85 -0
  161. package/dist/hooks/use-table.svelte.js +362 -0
  162. package/dist/hooks/use-websocket.svelte.d.ts +18 -0
  163. package/dist/hooks/use-websocket.svelte.js +79 -0
  164. package/dist/icons/index.d.ts +132 -0
  165. package/dist/icons/index.js +132 -0
  166. package/dist/index.css +115 -0
  167. package/dist/index.d.ts +76 -0
  168. package/dist/index.js +76 -0
  169. package/dist/layout/AppBar.svelte +94 -0
  170. package/dist/layout/AppBar.svelte.d.ts +17 -0
  171. package/dist/layout/Footer.svelte +94 -0
  172. package/dist/layout/Footer.svelte.d.ts +17 -0
  173. package/dist/layout/FooterLinks.svelte +28 -0
  174. package/dist/layout/FooterLinks.svelte.d.ts +11 -0
  175. package/dist/layout/Provider.svelte +52 -0
  176. package/dist/layout/Provider.svelte.d.ts +10 -0
  177. package/dist/layout/Scaffold.svelte +46 -0
  178. package/dist/layout/Scaffold.svelte.d.ts +15 -0
  179. package/dist/layout/Sidebar.svelte +40 -0
  180. package/dist/layout/Sidebar.svelte.d.ts +13 -0
  181. package/dist/layout/css/app-bar.css +35 -0
  182. package/dist/layout/css/bottom-bar.css +12 -0
  183. package/dist/layout/css/footer-links.css +17 -0
  184. package/dist/layout/css/footer.css +35 -0
  185. package/dist/layout/css/scaffold.css +15 -0
  186. package/dist/layout/css/sidebar.css +17 -0
  187. package/dist/navigation/BottomNav.svelte +0 -0
  188. package/dist/navigation/BottomNav.svelte.d.ts +26 -0
  189. package/dist/navigation/NavMenu.svelte +254 -0
  190. package/dist/navigation/SideNav.svelte +249 -0
  191. package/dist/navigation/Tabs.svelte +79 -0
  192. package/dist/navigation/Tabs.svelte.d.ts +19 -0
  193. package/dist/navigation/css/bottom-nav.css +0 -0
  194. package/dist/navigation/css/nav-menu.css +168 -0
  195. package/dist/navigation/css/side-nav.css +244 -0
  196. package/dist/navigation/css/tabs.css +118 -0
  197. package/dist/overlay/AlertDialog.svelte +0 -0
  198. package/dist/overlay/AlertDialog.svelte.d.ts +26 -0
  199. package/dist/overlay/Command.svelte +0 -0
  200. package/dist/overlay/Command.svelte.d.ts +26 -0
  201. package/dist/overlay/Drawer.svelte +129 -0
  202. package/dist/overlay/Drawer.svelte.d.ts +20 -0
  203. package/dist/overlay/Dropdown.svelte +140 -0
  204. package/dist/overlay/Modal.svelte +102 -0
  205. package/dist/overlay/Modal.svelte.d.ts +19 -0
  206. package/dist/overlay/PopoverStack.svelte +0 -0
  207. package/dist/overlay/PopoverStack.svelte.d.ts +26 -0
  208. package/dist/overlay/Toast.svelte +83 -0
  209. package/dist/overlay/Toast.svelte.d.ts +9 -0
  210. package/dist/overlay/Tooltip.svelte +140 -0
  211. package/dist/overlay/Tooltip.svelte.d.ts +12 -0
  212. package/dist/overlay/css/drawer.css +75 -0
  213. package/dist/overlay/css/dropdown.css +24 -0
  214. package/dist/overlay/css/hovercard.css +11 -0
  215. package/dist/overlay/css/modal.css +51 -0
  216. package/dist/overlay/css/toast.css +80 -0
  217. package/dist/overlay/css/tooltip.css +89 -0
  218. package/dist/stores/i18n.svelte.d.ts +16 -0
  219. package/dist/stores/i18n.svelte.js +137 -0
  220. package/dist/stores/theme.svelte.d.ts +5 -0
  221. package/dist/stores/theme.svelte.js +55 -0
  222. package/dist/stores/toast.svelte.d.ts +19 -0
  223. package/dist/stores/toast.svelte.js +38 -0
  224. package/dist/types.d.ts +75 -0
  225. package/dist/types.js +1 -0
  226. package/dist/utils/charts.d.ts +27 -0
  227. package/dist/utils/charts.js +140 -0
  228. package/dist/utils/class-names.d.ts +1 -0
  229. package/dist/utils/class-names.js +3 -0
  230. package/dist/utils/click-outside.d.ts +3 -0
  231. package/dist/utils/click-outside.js +9 -0
  232. package/dist/utils/popover.d.ts +3 -0
  233. package/dist/utils/popover.js +17 -0
  234. package/dist/utils/ulid.d.ts +1 -0
  235. package/dist/utils/ulid.js +22 -0
  236. package/dist/utils/validate-schema.d.ts +2 -0
  237. package/dist/utils/validate-schema.js +97 -0
  238. package/package.json +69 -0
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { cn } from '../utils/class-names.js';
4
+
5
+ type FooterLink = {
6
+ label: string;
7
+ href: string;
8
+ };
9
+
10
+ type Props = {
11
+ links: FooterLink[];
12
+ class?: string;
13
+ };
14
+
15
+ const { links = [], class: className }: Props = $props();
16
+
17
+ function isLinkActive(href: string): boolean {
18
+ return page.url.pathname === href;
19
+ }
20
+ </script>
21
+
22
+ <div class={cn('footer-links', className)}>
23
+ {#each links as link}
24
+ <a href={link.href} class={cn('footer-link', isLinkActive(link.href) && 'is-active')}>
25
+ {link.label}
26
+ </a>
27
+ {/each}
28
+ </div>
@@ -0,0 +1,11 @@
1
+ type FooterLink = {
2
+ label: string;
3
+ href: string;
4
+ };
5
+ type Props = {
6
+ links: FooterLink[];
7
+ class?: string;
8
+ };
9
+ declare const FooterLinks: import("svelte").Component<Props, {}, "">;
10
+ type FooterLinks = ReturnType<typeof FooterLinks>;
11
+ export default FooterLinks;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import { onNavigate } from '$app/navigation';
3
+ import { Toast } from '../index.js';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ type Props = {
7
+ children: Snippet;
8
+ toastSolid?: boolean;
9
+ toastIcon?: boolean;
10
+ toastPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
11
+ };
12
+
13
+ const { children, toastSolid, toastPosition, toastIcon }: Props = $props();
14
+
15
+ onNavigate((navigation) => {
16
+ if (!document.startViewTransition) return;
17
+ return new Promise((resolve) => {
18
+ document.startViewTransition(async () => {
19
+ resolve();
20
+ await navigation.complete;
21
+ });
22
+ });
23
+ });
24
+ </script>
25
+
26
+ <svelte:head>
27
+ <meta
28
+ name="viewport"
29
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content"
30
+ />
31
+ <script>
32
+ let themeState = 'light';
33
+
34
+ if (typeof window !== 'undefined' && localStorage) {
35
+ const storedTheme = localStorage.getItem('theme-preference');
36
+ if (storedTheme) {
37
+ themeState = storedTheme;
38
+ } else {
39
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
40
+ themeState = prefersDark ? '' : 'light';
41
+ localStorage.setItem('theme-preference', themeState);
42
+ }
43
+ }
44
+
45
+ if (themeState === 'dark') {
46
+ document.documentElement.classList.add('dark');
47
+ }
48
+ </script>
49
+ </svelte:head>
50
+
51
+ {@render children()}
52
+ <Toast position={toastPosition} showIcon={toastIcon} isSolid={toastSolid} />
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ children: Snippet;
4
+ toastSolid?: boolean;
5
+ toastIcon?: boolean;
6
+ toastPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
7
+ };
8
+ declare const Provider: import("svelte").Component<Props, {}, "">;
9
+ type Provider = ReturnType<typeof Provider>;
10
+ export default Provider;
@@ -0,0 +1,46 @@
1
+ <script lang="ts">
2
+ import { cn } from '../utils/class-names.js';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ type Props = {
6
+ children: Snippet;
7
+ appBar?: Snippet;
8
+ start?: Snippet;
9
+ end?: Snippet;
10
+ bottomBar?: Snippet;
11
+ bodyClass?: string;
12
+ mainClass?: string;
13
+ startClass?: string;
14
+ endClass?: string;
15
+ };
16
+
17
+ const {
18
+ children,
19
+ appBar,
20
+ start,
21
+ end,
22
+ bottomBar,
23
+ bodyClass,
24
+ mainClass,
25
+ startClass,
26
+ endClass
27
+ }: Props = $props();
28
+ </script>
29
+
30
+ {@render appBar?.()}
31
+ <div class={cn('scaffold-body', bodyClass)}>
32
+ {#if start}
33
+ <aside class={cn('scaffold-start', startClass)}>
34
+ {@render start()}
35
+ </aside>
36
+ {/if}
37
+ <div class={cn('scaffold-main', mainClass)}>
38
+ {@render children()}
39
+ </div>
40
+ {#if end}
41
+ <aside class={cn('scaffold-end', endClass)}>
42
+ {@render end()}
43
+ </aside>
44
+ {/if}
45
+ </div>
46
+ {@render bottomBar?.()}
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ children: Snippet;
4
+ appBar?: Snippet;
5
+ start?: Snippet;
6
+ end?: Snippet;
7
+ bottomBar?: Snippet;
8
+ bodyClass?: string;
9
+ mainClass?: string;
10
+ startClass?: string;
11
+ endClass?: string;
12
+ };
13
+ declare const Scaffold: import("svelte").Component<Props, {}, "">;
14
+ type Scaffold = ReturnType<typeof Scaffold>;
15
+ export default Scaffold;
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import { cn } from '../utils/class-names.js';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ type Props = {
6
+ children: Snippet;
7
+ header?: Snippet;
8
+ footer?: Snippet;
9
+ class?: string;
10
+ contentClass?: string;
11
+ headerClass?: string;
12
+ footerClass?: string;
13
+ };
14
+
15
+ const {
16
+ children,
17
+ header,
18
+ footer,
19
+ class: className,
20
+ contentClass,
21
+ headerClass,
22
+ footerClass
23
+ }: Props = $props();
24
+ </script>
25
+
26
+ <aside class={cn('sidebar', className)}>
27
+ {#if header}
28
+ <div class={cn('sidebar-header', headerClass)}>
29
+ {@render header()}
30
+ </div>
31
+ {/if}
32
+ <div class={cn('sidebar-body', contentClass)}>
33
+ {@render children()}
34
+ </div>
35
+ {#if footer}
36
+ <div class={cn('sidebar-footer', footerClass)}>
37
+ {@render footer()}
38
+ </div>
39
+ {/if}
40
+ </aside>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ children: Snippet;
4
+ header?: Snippet;
5
+ footer?: Snippet;
6
+ class?: string;
7
+ contentClass?: string;
8
+ headerClass?: string;
9
+ footerClass?: string;
10
+ };
11
+ declare const Sidebar: import("svelte").Component<Props, {}, "">;
12
+ type Sidebar = ReturnType<typeof Sidebar>;
13
+ export default Sidebar;
@@ -0,0 +1,35 @@
1
+ @layer components {
2
+ .appbar {
3
+ @apply w-full fixed top-0 z-30 bg-background;
4
+ @apply transition-all duration-300 ease-in-out;
5
+
6
+ &.is-blurred {
7
+ @apply backdrop-blur-lg bg-transparent;
8
+ }
9
+
10
+ &.is-bordered {
11
+ @apply border-b border-muted;
12
+ }
13
+
14
+ &.is-hidden {
15
+ @apply -translate-y-full;
16
+ }
17
+
18
+ .appbar-content {
19
+ @apply flex flex-nowrap items-center justify-between relative;
20
+ @apply w-full px-2 h-full min-h-12;
21
+
22
+ .appbar-start {
23
+ @apply flex flex-nowrap justify-start items-center p-2 md:p-3 gap-2 md:gap-3 relative;
24
+ }
25
+
26
+ .appbar-center {
27
+ @apply flex flex-1 flex-nowrap justify-center items-center p-2 md:p-3 gap-2 md:gap-3 relative;
28
+ }
29
+
30
+ .appbar-end {
31
+ @apply flex flex-nowrap justify-end items-center p-2 md:p-3 gap-2 md:gap-3 relative;
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,12 @@
1
+ @layer components {
2
+ .dock {
3
+ @apply w-full fixed bottom-0 z-30;
4
+ @apply transition-all duration-300 ease-in-out;
5
+
6
+ .dock-content {
7
+ @apply w-full px-2 gap-4 relative;
8
+ @apply flex flex-row flex-nowrap items-center justify-between;
9
+ @apply overflow-hidden;
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,17 @@
1
+ @layer components {
2
+ .footer-links {
3
+ @apply column md:row items-start md:items-center gap-3 md:gap-6;
4
+
5
+ .footer-link {
6
+ @apply text-sm opacity-70;
7
+ @apply transition-all duration-200;
8
+ @apply hover:opacity-100;
9
+ @apply focus:outline-none;
10
+ @apply cursor-pointer select-none;
11
+
12
+ &.is-active {
13
+ @apply opacity-100 font-medium;
14
+ }
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,35 @@
1
+ @layer components {
2
+ .footer {
3
+ @apply w-full bg-background mt-auto;
4
+ @apply transition-all duration-300 ease-in-out;
5
+
6
+ &.is-blurred {
7
+ @apply backdrop-blur-lg bg-transparent;
8
+ }
9
+
10
+ &.is-bordered {
11
+ @apply border-t border-muted;
12
+ }
13
+
14
+ &.is-hidden {
15
+ @apply translate-y-full;
16
+ }
17
+
18
+ .footer-content {
19
+ @apply column md:row flex-nowrap items-center justify-between relative;
20
+ @apply w-full px-2 h-full min-h-12;
21
+
22
+ .footer-start {
23
+ @apply flex flex-nowrap justify-start items-center p-2 md:p-3 gap-2 md:gap-3 relative;
24
+ }
25
+
26
+ .footer-center {
27
+ @apply flex flex-1 flex-nowrap justify-center items-center p-2 md:p-3 gap-2 md:gap-3 relative;
28
+ }
29
+
30
+ .footer-end {
31
+ @apply flex flex-nowrap justify-end items-center p-2 md:p-3 gap-2 md:gap-3 relative;
32
+ }
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,15 @@
1
+ @layer components {
2
+ .scaffold-body {
3
+ @apply flex h-full w-full relative;
4
+ @apply bg-background text-on-background;
5
+ .scaffold-main {
6
+ @apply relative flex-1 flex flex-col min-w-0;
7
+ }
8
+ .scaffold-start {
9
+ @apply fixed z-10;
10
+ }
11
+ .scaffold-end {
12
+ @apply fixed right-0 z-10;
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ @layer components {
2
+ .sidebar {
3
+ @apply flex h-full flex-col bg-background text-on-background;
4
+
5
+ .sidebar-header {
6
+ @apply flex flex-col gap-3 p-3;
7
+ }
8
+
9
+ .sidebar-footer {
10
+ @apply flex flex-col gap-3 p-3;
11
+ }
12
+
13
+ .sidebar-body {
14
+ @apply flex min-h-0 flex-1 flex-col gap-3 p-3 overflow-auto;
15
+ }
16
+ }
17
+ }
File without changes
@@ -0,0 +1,26 @@
1
+ export default BottomNav;
2
+ type BottomNav = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const BottomNav: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
@@ -0,0 +1,254 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { ChevronDown24RegularIcon } from '../icons/index.js';
4
+ import { Icon } from '../index.js';
5
+ import { cn } from '../utils/class-names.js';
6
+ import type { Snippet } from 'svelte';
7
+ import { onMount } from 'svelte';
8
+
9
+ type SubmenuItem = {
10
+ label: string;
11
+ description?: string;
12
+ href?: string;
13
+ onclick?: (item: SubmenuItem) => void;
14
+ };
15
+
16
+ type MenuItem = {
17
+ label: string;
18
+ description?: string;
19
+ href?: string;
20
+ onclick?: (item: MenuItem) => void;
21
+ subitems?: SubmenuItem[];
22
+ megamenu?: Snippet;
23
+ type?: 'item' | 'submenu' | 'megamenu';
24
+ };
25
+
26
+ type Props = {
27
+ items: MenuItem[];
28
+ variant?: 'primary' | 'secondary' | 'muted';
29
+ size?: 'sm' | 'md' | 'lg';
30
+ class?: string;
31
+ };
32
+
33
+ const { items = [], class: className, variant = 'primary', size = 'md' }: Props = $props();
34
+
35
+ let openSubmenuIndex = $state<number | null>(null);
36
+ let triggerElements = $state<Record<number, HTMLElement>>({});
37
+ let popoverElement = $state<HTMLElement>();
38
+ let position = $state({
39
+ top: 0,
40
+ left: 0,
41
+ width: 'auto',
42
+ isBottomHalf: false,
43
+ isMegamenu: false
44
+ });
45
+
46
+ const variantClasses = {
47
+ primary: 'navmenu-primary',
48
+ secondary: 'navmenu-secondary',
49
+ muted: 'navmenu-muted'
50
+ };
51
+
52
+ const sizeClasses = {
53
+ sm: 'is-sm',
54
+ md: 'is-md',
55
+ lg: 'is-lg'
56
+ };
57
+
58
+ const style = $derived(
59
+ `top: ${position.top}px; left: ${position.left}px; ${position.width !== 'auto' ? `width: ${position.width}px;` : ''} transform-origin: ${position.isBottomHalf ? 'bottom' : 'top'} center;`
60
+ );
61
+
62
+ function isItemActive(href?: string): boolean {
63
+ if (!href) return false;
64
+ return page.url.pathname === href || page.url.pathname.startsWith(href + '/');
65
+ }
66
+
67
+ function hasActiveSubmenu(subitems?: SubmenuItem[]): boolean {
68
+ if (!subitems) return false;
69
+ return subitems.some((item) => isItemActive(item.href));
70
+ }
71
+
72
+ function updatePosition(index: number) {
73
+ const triggerEl = triggerElements[index];
74
+ if (!triggerEl || !popoverElement) return;
75
+
76
+ const rect = triggerEl.getBoundingClientRect();
77
+ const windowHeight = window.innerHeight;
78
+ const popoverHeight = popoverElement.getBoundingClientRect().height;
79
+ const isBottomHalf = rect.bottom + popoverHeight > windowHeight;
80
+ const isMegamenu = items[index]?.type === 'megamenu';
81
+
82
+ let left = rect.left + window.scrollX;
83
+ let width: number | 'auto' = 'auto';
84
+
85
+ if (isMegamenu) {
86
+ const popoverWidth = popoverElement.getBoundingClientRect().width;
87
+ left = rect.left + rect.width / 2 - popoverWidth / 2 + window.scrollX;
88
+
89
+ if (left < 8) left = 8;
90
+ if (left + popoverWidth > window.innerWidth - 8) {
91
+ left = window.innerWidth - popoverWidth - 8;
92
+ }
93
+ }
94
+
95
+ position = {
96
+ top: isBottomHalf ? rect.top - popoverHeight - 8 : rect.bottom + 8,
97
+ left,
98
+ width,
99
+ isBottomHalf,
100
+ isMegamenu
101
+ };
102
+ }
103
+
104
+ function toggleSubmenu(index: number) {
105
+ if (openSubmenuIndex === index) {
106
+ stopEventListeners();
107
+ openSubmenuIndex = null;
108
+ } else {
109
+ openSubmenuIndex = index;
110
+ startEventListeners();
111
+ requestAnimationFrame(() => updatePosition(index));
112
+ }
113
+ }
114
+
115
+ function handleClickOutside(event: Event) {
116
+ const target = event.target as Node;
117
+ const isInsideTrigger = Object.values(triggerElements).some((el) => el?.contains(target));
118
+ const isInsidePopover = popoverElement?.contains(target);
119
+
120
+ if (!isInsideTrigger && !isInsidePopover) {
121
+ openSubmenuIndex = null;
122
+ stopEventListeners();
123
+ }
124
+ }
125
+
126
+ function startEventListeners() {
127
+ window.addEventListener('resize', () => {
128
+ if (openSubmenuIndex !== null) updatePosition(openSubmenuIndex);
129
+ });
130
+ window.addEventListener(
131
+ 'scroll',
132
+ () => {
133
+ if (openSubmenuIndex !== null) updatePosition(openSubmenuIndex);
134
+ },
135
+ true
136
+ );
137
+ document.addEventListener('click', handleClickOutside);
138
+ }
139
+
140
+ function stopEventListeners() {
141
+ window.removeEventListener('resize', () => {
142
+ if (openSubmenuIndex !== null) updatePosition(openSubmenuIndex);
143
+ });
144
+ window.removeEventListener(
145
+ 'scroll',
146
+ () => {
147
+ if (openSubmenuIndex !== null) updatePosition(openSubmenuIndex);
148
+ },
149
+ true
150
+ );
151
+ document.removeEventListener('click', handleClickOutside);
152
+ }
153
+
154
+ function handleItemClick(item: MenuItem, index: number) {
155
+ if (item.subitems || item.megamenu) {
156
+ toggleSubmenu(index);
157
+ } else if (item.onclick) {
158
+ item.onclick(item);
159
+ }
160
+ }
161
+
162
+ function handleSubmenuItemClick(item: SubmenuItem) {
163
+ if (item.onclick) {
164
+ item.onclick(item);
165
+ }
166
+ openSubmenuIndex = null;
167
+ stopEventListeners();
168
+ }
169
+
170
+ onMount(() => {
171
+ return () => stopEventListeners();
172
+ });
173
+ </script>
174
+
175
+ <nav class={cn('navmenu', variantClasses[variant], sizeClasses[size], className)}>
176
+ {#each items as item, index}
177
+ {#if item.href && !item.subitems && !item.megamenu}
178
+ <a href={item.href} class={cn('navmenu-item', isItemActive(item.href) && 'is-active')}>
179
+ <span class="navmenu-label">{item.label}</span>
180
+ </a>
181
+ {:else}
182
+ <button
183
+ type="button"
184
+ class={cn(
185
+ 'navmenu-item',
186
+ openSubmenuIndex === index && 'is-active',
187
+ hasActiveSubmenu(item.subitems) && 'is-active'
188
+ )}
189
+ bind:this={triggerElements[index]}
190
+ onclick={() => handleItemClick(item, index)}
191
+ >
192
+ <span class="navmenu-label">{item.label}</span>
193
+ {#if item.subitems || item.megamenu}
194
+ <Icon
195
+ icon={ChevronDown24RegularIcon}
196
+ class={cn('navmenu-arrow', openSubmenuIndex === index && 'is-open')}
197
+ />
198
+ {/if}
199
+ </button>
200
+ {/if}
201
+ {/each}
202
+ </nav>
203
+
204
+ {#if openSubmenuIndex !== null}
205
+ {@const currentItem = items[openSubmenuIndex]}
206
+ <div
207
+ class={cn(
208
+ 'navmenu-popover',
209
+ openSubmenuIndex !== null && 'is-active',
210
+ position.isMegamenu && 'is-megamenu'
211
+ )}
212
+ bind:this={popoverElement}
213
+ {style}
214
+ >
215
+ {#if currentItem?.megamenu}
216
+ {@render currentItem.megamenu()}
217
+ {:else if currentItem?.subitems}
218
+ <div class="navmenu-popover-content">
219
+ {#each currentItem.subitems as subitem}
220
+ {#if subitem.href}
221
+ <a
222
+ href={subitem.href}
223
+ class={cn('navmenu-submenu-item', isItemActive(subitem.href) && 'is-active')}
224
+ onclick={() => {
225
+ openSubmenuIndex = null;
226
+ stopEventListeners();
227
+ }}
228
+ >
229
+ <div class="navmenu-submenu-content">
230
+ <div class="navmenu-submenu-label">{subitem.label}</div>
231
+ {#if subitem.description}
232
+ <div class="navmenu-submenu-description">{subitem.description}</div>
233
+ {/if}
234
+ </div>
235
+ </a>
236
+ {:else}
237
+ <button
238
+ type="button"
239
+ class="navmenu-submenu-item"
240
+ onclick={() => handleSubmenuItemClick(subitem)}
241
+ >
242
+ <div class="navmenu-submenu-content">
243
+ <div class="navmenu-submenu-label">{subitem.label}</div>
244
+ {#if subitem.description}
245
+ <div class="navmenu-submenu-description">{subitem.description}</div>
246
+ {/if}
247
+ </div>
248
+ </button>
249
+ {/if}
250
+ {/each}
251
+ </div>
252
+ {/if}
253
+ </div>
254
+ {/if}