@mrintel/villain-ui 0.2.0 → 0.3.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 (133) hide show
  1. package/dist/components/buttons/Button.svelte +33 -0
  2. package/dist/components/buttons/Button.svelte.d.ts +11 -0
  3. package/dist/components/buttons/ButtonGroup.svelte +30 -0
  4. package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
  5. package/dist/components/buttons/FloatingActionButton.svelte +44 -0
  6. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +11 -0
  7. package/dist/components/buttons/IconButton.svelte +53 -0
  8. package/dist/components/buttons/IconButton.svelte.d.ts +13 -0
  9. package/dist/components/buttons/LinkButton.svelte +37 -0
  10. package/dist/components/buttons/LinkButton.svelte.d.ts +12 -0
  11. package/dist/components/buttons/buttonClasses.d.ts +10 -0
  12. package/dist/components/buttons/buttonClasses.js +10 -0
  13. package/dist/components/buttons/index.d.ts +5 -0
  14. package/dist/components/buttons/index.js +5 -0
  15. package/dist/components/cards/Card.svelte +46 -0
  16. package/dist/components/cards/Card.svelte.d.ts +11 -0
  17. package/dist/components/cards/Container.svelte +33 -0
  18. package/dist/components/cards/Container.svelte.d.ts +10 -0
  19. package/dist/components/cards/Divider.svelte +52 -0
  20. package/dist/components/cards/Divider.svelte.d.ts +9 -0
  21. package/dist/components/cards/Grid.svelte +44 -0
  22. package/dist/components/cards/Grid.svelte.d.ts +10 -0
  23. package/dist/components/cards/Panel.svelte +32 -0
  24. package/dist/components/cards/Panel.svelte.d.ts +10 -0
  25. package/dist/components/cards/SectionHeader.svelte +38 -0
  26. package/dist/components/cards/SectionHeader.svelte.d.ts +11 -0
  27. package/dist/components/cards/index.d.ts +6 -0
  28. package/dist/components/cards/index.js +6 -0
  29. package/dist/components/data/Avatar.svelte +67 -0
  30. package/dist/components/data/Avatar.svelte.d.ts +10 -0
  31. package/dist/components/data/Badge.svelte +32 -0
  32. package/dist/components/data/Badge.svelte.d.ts +8 -0
  33. package/dist/components/data/CodeBlock.svelte +121 -0
  34. package/dist/components/data/CodeBlock.svelte.d.ts +32 -0
  35. package/dist/components/data/List.svelte +64 -0
  36. package/dist/components/data/List.svelte.d.ts +8 -0
  37. package/dist/components/data/Pagination.svelte +123 -0
  38. package/dist/components/data/Pagination.svelte.d.ts +9 -0
  39. package/dist/components/data/Stat.svelte +103 -0
  40. package/dist/components/data/Stat.svelte.d.ts +11 -0
  41. package/dist/components/data/Table.svelte +76 -0
  42. package/dist/components/data/Table.svelte.d.ts +9 -0
  43. package/dist/components/data/Tag.svelte +53 -0
  44. package/dist/components/data/Tag.svelte.d.ts +9 -0
  45. package/dist/components/data/index.d.ts +8 -0
  46. package/dist/components/data/index.js +8 -0
  47. package/dist/components/forms/Checkbox.svelte +51 -0
  48. package/dist/components/forms/Checkbox.svelte.d.ts +10 -0
  49. package/dist/components/forms/FileUpload.svelte +164 -0
  50. package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
  51. package/dist/components/forms/Input.svelte +57 -0
  52. package/dist/components/forms/Input.svelte.d.ts +13 -0
  53. package/dist/components/forms/InputGroup.svelte +7 -0
  54. package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
  55. package/dist/components/forms/RadioGroup.svelte +87 -0
  56. package/dist/components/forms/RadioGroup.svelte.d.ts +15 -0
  57. package/dist/components/forms/RangeSlider.svelte +116 -0
  58. package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
  59. package/dist/components/forms/Select.svelte +71 -0
  60. package/dist/components/forms/Select.svelte.d.ts +16 -0
  61. package/dist/components/forms/Switch.svelte +56 -0
  62. package/dist/components/forms/Switch.svelte.d.ts +10 -0
  63. package/dist/components/forms/Textarea.svelte +57 -0
  64. package/dist/components/forms/Textarea.svelte.d.ts +13 -0
  65. package/dist/components/forms/index.d.ts +9 -0
  66. package/dist/components/forms/index.js +9 -0
  67. package/dist/components/navigation/Breadcrumbs.svelte +59 -0
  68. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +14 -0
  69. package/dist/components/navigation/ContextMenu.svelte +83 -0
  70. package/dist/components/navigation/ContextMenu.svelte.d.ts +11 -0
  71. package/dist/components/navigation/DropdownMenu.svelte +80 -0
  72. package/dist/components/navigation/DropdownMenu.svelte.d.ts +10 -0
  73. package/dist/components/navigation/Menu.svelte +48 -0
  74. package/dist/components/navigation/Menu.svelte.d.ts +15 -0
  75. package/dist/components/navigation/Navbar.svelte +32 -0
  76. package/dist/components/navigation/Navbar.svelte.d.ts +9 -0
  77. package/dist/components/navigation/Sidebar.svelte +35 -0
  78. package/dist/components/navigation/Sidebar.svelte.d.ts +10 -0
  79. package/dist/components/navigation/Tabs.svelte +54 -0
  80. package/dist/components/navigation/Tabs.svelte.d.ts +15 -0
  81. package/dist/components/navigation/index.d.ts +7 -0
  82. package/dist/components/navigation/index.js +7 -0
  83. package/dist/components/overlays/Alert.svelte +99 -0
  84. package/dist/components/overlays/Alert.svelte.d.ts +11 -0
  85. package/dist/components/overlays/CommandPalette.svelte +217 -0
  86. package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
  87. package/dist/components/overlays/Drawer.svelte +167 -0
  88. package/dist/components/overlays/Drawer.svelte.d.ts +14 -0
  89. package/dist/components/overlays/Dropdown.svelte +30 -0
  90. package/dist/components/overlays/Dropdown.svelte.d.ts +9 -0
  91. package/dist/components/overlays/Modal.svelte +130 -0
  92. package/dist/components/overlays/Modal.svelte.d.ts +13 -0
  93. package/dist/components/overlays/Popover.svelte +131 -0
  94. package/dist/components/overlays/Popover.svelte.d.ts +11 -0
  95. package/dist/components/overlays/ProgressBar.svelte +45 -0
  96. package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
  97. package/dist/components/overlays/SkeletonLoader.svelte +82 -0
  98. package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
  99. package/dist/components/overlays/Spinner.svelte +43 -0
  100. package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
  101. package/dist/components/overlays/Toast.svelte +140 -0
  102. package/dist/components/overlays/Toast.svelte.d.ts +13 -0
  103. package/dist/components/overlays/Tooltip.svelte +115 -0
  104. package/dist/components/overlays/Tooltip.svelte.d.ts +10 -0
  105. package/dist/components/overlays/index.d.ts +11 -0
  106. package/dist/components/overlays/index.js +11 -0
  107. package/dist/components/typography/Code.svelte +14 -0
  108. package/dist/components/typography/Code.svelte.d.ts +6 -0
  109. package/dist/components/typography/Heading.svelte +22 -0
  110. package/dist/components/typography/Heading.svelte.d.ts +9 -0
  111. package/dist/components/typography/Text.svelte +24 -0
  112. package/dist/components/typography/Text.svelte.d.ts +9 -0
  113. package/dist/components/typography/index.d.ts +3 -0
  114. package/dist/components/typography/index.js +3 -0
  115. package/dist/components/utilities/Accordion.svelte +67 -0
  116. package/dist/components/utilities/Accordion.svelte.d.ts +14 -0
  117. package/dist/components/utilities/Carousel.svelte +152 -0
  118. package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
  119. package/dist/components/utilities/Collapse.svelte +60 -0
  120. package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
  121. package/dist/components/utilities/Portal.svelte +72 -0
  122. package/dist/components/utilities/Portal.svelte.d.ts +21 -0
  123. package/dist/components/utilities/ScrollArea.svelte +41 -0
  124. package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
  125. package/dist/components/utilities/index.d.ts +5 -0
  126. package/dist/components/utilities/index.js +5 -0
  127. package/dist/index.d.ts +15 -175
  128. package/dist/index.js +24 -4560
  129. package/dist/lib/internal/id.d.ts +12 -0
  130. package/dist/lib/internal/id.js +15 -0
  131. package/dist/theme.css +218 -0
  132. package/package.json +14 -7
  133. package/dist/index.css +0 -1
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import { createId } from '../../lib/internal/id.js';
3
+
4
+ interface Props {
5
+ value?: string;
6
+ options: Array<{ value: string; label: string }>;
7
+ placeholder?: string;
8
+ disabled?: boolean;
9
+ error?: boolean;
10
+ label?: string;
11
+ id?: string;
12
+ onchange?: (event: Event) => void;
13
+ }
14
+
15
+ let {
16
+ value = $bindable(''),
17
+ options,
18
+ placeholder,
19
+ disabled = false,
20
+ error = false,
21
+ label,
22
+ id = createId('select'),
23
+ onchange
24
+ }: Props = $props();
25
+
26
+ const chevronIcon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' stroke='%23ADADAD' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E";
27
+
28
+ const baseClasses = `glass-panel rounded-lg px-4 py-3 pr-10 font-body text-text transition-all duration-300 ease-luxe w-full appearance-none bg-no-repeat bg-[right_1rem_center] bg-[length:12px]`;
29
+ const focusClasses = 'focus:outline-none focus:border-accent focus:accent-glow';
30
+ const errorClasses = error ? 'border-error' : '';
31
+ const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
32
+ </script>
33
+
34
+ {#if label}
35
+ <div>
36
+ <label for={id} class="text-text-soft text-sm mb-2 block">
37
+ {label}
38
+ </label>
39
+ <select
40
+ {id}
41
+ {disabled}
42
+ bind:value
43
+ onchange={onchange}
44
+ class="{baseClasses} {focusClasses} {errorClasses} {disabledClasses}"
45
+ style={`background-image: url('${chevronIcon}')`}
46
+ >
47
+ {#if placeholder}
48
+ <option disabled value="">{placeholder}</option>
49
+ {/if}
50
+ {#each options as option}
51
+ <option value={option.value}>{option.label}</option>
52
+ {/each}
53
+ </select>
54
+ </div>
55
+ {:else}
56
+ <select
57
+ {id}
58
+ {disabled}
59
+ bind:value
60
+ onchange={onchange}
61
+ class="{baseClasses} {focusClasses} {errorClasses} {disabledClasses}"
62
+ style={`background-image: url('${chevronIcon}')`}
63
+ >
64
+ {#if placeholder}
65
+ <option disabled value="">{placeholder}</option>
66
+ {/if}
67
+ {#each options as option}
68
+ <option value={option.value}>{option.label}</option>
69
+ {/each}
70
+ </select>
71
+ {/if}
@@ -0,0 +1,16 @@
1
+ interface Props {
2
+ value?: string;
3
+ options: Array<{
4
+ value: string;
5
+ label: string;
6
+ }>;
7
+ placeholder?: string;
8
+ disabled?: boolean;
9
+ error?: boolean;
10
+ label?: string;
11
+ id?: string;
12
+ onchange?: (event: Event) => void;
13
+ }
14
+ declare const Select: import("svelte").Component<Props, {}, "value">;
15
+ type Select = ReturnType<typeof Select>;
16
+ export default Select;
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import { createId } from '../../lib/internal/id.js';
3
+
4
+ interface Props {
5
+ checked?: boolean;
6
+ disabled?: boolean;
7
+ label?: string;
8
+ id?: string;
9
+ onchange?: (event: Event) => void;
10
+ }
11
+
12
+ let {
13
+ checked = $bindable(false),
14
+ disabled = false,
15
+ label,
16
+ id = createId('switch'),
17
+ onchange
18
+ }: Props = $props();
19
+ </script>
20
+
21
+ <label for={id} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''}">
22
+ <input
23
+ type="checkbox"
24
+ role="switch"
25
+ aria-checked={checked}
26
+ {id}
27
+ {disabled}
28
+ bind:checked
29
+ onchange={onchange}
30
+ class="w-11 h-6 rounded-pill bg-base-3 border border-border appearance-none transition-all duration-300 ease-luxe cursor-pointer checked:bg-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {disabled ? 'cursor-not-allowed' : ''}"
31
+ />
32
+ {#if label}
33
+ <span class="text-text text-sm select-none">
34
+ {label}
35
+ </span>
36
+ {/if}
37
+ </label>
38
+
39
+ <style>
40
+ input[type="checkbox"]::after {
41
+ content: '';
42
+ position: absolute;
43
+ left: 0.25rem;
44
+ top: 50%;
45
+ transform: translateY(-50%);
46
+ width: 1rem;
47
+ height: 1rem;
48
+ border-radius: var(--radius-pill);
49
+ background: var(--color-text);
50
+ transition: all 0.3s var(--ease-luxe);
51
+ }
52
+
53
+ input[type="checkbox"]:checked::after {
54
+ transform: translate(1.25rem, -50%);
55
+ }
56
+ </style>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ checked?: boolean;
3
+ disabled?: boolean;
4
+ label?: string;
5
+ id?: string;
6
+ onchange?: (event: Event) => void;
7
+ }
8
+ declare const Switch: import("svelte").Component<Props, {}, "checked">;
9
+ type Switch = ReturnType<typeof Switch>;
10
+ export default Switch;
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import { createId } from '../../lib/internal/id.js';
3
+
4
+ interface Props {
5
+ value?: string;
6
+ placeholder?: string;
7
+ rows?: number;
8
+ disabled?: boolean;
9
+ error?: boolean;
10
+ label?: string;
11
+ id?: string;
12
+ oninput?: (event: Event) => void;
13
+ }
14
+
15
+ let {
16
+ value = $bindable(''),
17
+ placeholder,
18
+ rows = 4,
19
+ disabled = false,
20
+ error = false,
21
+ label,
22
+ id = createId('textarea'),
23
+ oninput
24
+ }: Props = $props();
25
+
26
+ const baseClasses = 'glass-panel rounded-lg px-4 py-3 font-body text-text placeholder:text-text-muted transition-all duration-300 ease-luxe w-full resize-y min-h-[100px]';
27
+ const focusClasses = 'focus:outline-none focus:border-accent focus:accent-glow';
28
+ const errorClasses = error ? 'border-error' : '';
29
+ const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
30
+ </script>
31
+
32
+ {#if label}
33
+ <div>
34
+ <label for={id} class="text-text-soft text-sm mb-2 block">
35
+ {label}
36
+ </label>
37
+ <textarea
38
+ {id}
39
+ {placeholder}
40
+ {disabled}
41
+ {rows}
42
+ bind:value
43
+ oninput={oninput}
44
+ class="{baseClasses} {focusClasses} {errorClasses} {disabledClasses}"
45
+ ></textarea>
46
+ </div>
47
+ {:else}
48
+ <textarea
49
+ {id}
50
+ {placeholder}
51
+ {disabled}
52
+ {rows}
53
+ bind:value
54
+ oninput={oninput}
55
+ class="{baseClasses} {focusClasses} {errorClasses} {disabledClasses}"
56
+ ></textarea>
57
+ {/if}
@@ -0,0 +1,13 @@
1
+ interface Props {
2
+ value?: string;
3
+ placeholder?: string;
4
+ rows?: number;
5
+ disabled?: boolean;
6
+ error?: boolean;
7
+ label?: string;
8
+ id?: string;
9
+ oninput?: (event: Event) => void;
10
+ }
11
+ declare const Textarea: import("svelte").Component<Props, {}, "value">;
12
+ type Textarea = ReturnType<typeof Textarea>;
13
+ export default Textarea;
@@ -0,0 +1,9 @@
1
+ export { default as Input } from './Input.svelte';
2
+ export { default as Textarea } from './Textarea.svelte';
3
+ export { default as Select } from './Select.svelte';
4
+ export { default as Checkbox } from './Checkbox.svelte';
5
+ export { default as Switch } from './Switch.svelte';
6
+ export { default as RadioGroup } from './RadioGroup.svelte';
7
+ export { default as RangeSlider } from './RangeSlider.svelte';
8
+ export { default as FileUpload } from './FileUpload.svelte';
9
+ export { default as InputGroup } from './InputGroup.svelte';
@@ -0,0 +1,9 @@
1
+ export { default as Input } from './Input.svelte';
2
+ export { default as Textarea } from './Textarea.svelte';
3
+ export { default as Select } from './Select.svelte';
4
+ export { default as Checkbox } from './Checkbox.svelte';
5
+ export { default as Switch } from './Switch.svelte';
6
+ export { default as RadioGroup } from './RadioGroup.svelte';
7
+ export { default as RangeSlider } from './RangeSlider.svelte';
8
+ export { default as FileUpload } from './FileUpload.svelte';
9
+ export { default as InputGroup } from './InputGroup.svelte';
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface BreadcrumbItem {
5
+ label: string;
6
+ href?: string;
7
+ onclick?: () => void;
8
+ }
9
+
10
+ interface Props {
11
+ items: BreadcrumbItem[];
12
+ separator?: string;
13
+ separatorIcon?: Snippet;
14
+ }
15
+
16
+ let {
17
+ items,
18
+ separator = '/',
19
+ separatorIcon
20
+ }: Props = $props();
21
+ </script>
22
+
23
+ <nav role="navigation" aria-label="Breadcrumb">
24
+ <ol class="flex items-center gap-2">
25
+ {#each items as item, index}
26
+ <li class="flex items-center gap-2">
27
+ {#if item.href}
28
+ <a
29
+ href={item.href}
30
+ class="text-sm font-[var(--font-body)] text-[var(--color-text-soft)] hover:text-[var(--color-accent)] hover:underline transition-colors duration-200 ease-[var(--ease-luxe)]"
31
+ >
32
+ {item.label}
33
+ </a>
34
+ {:else if item.onclick}
35
+ <button
36
+ onclick={item.onclick}
37
+ class="text-sm font-[var(--font-body)] text-[var(--color-text-soft)] hover:text-[var(--color-accent)] hover:underline transition-colors duration-200 ease-[var(--ease-luxe)] cursor-pointer"
38
+ >
39
+ {item.label}
40
+ </button>
41
+ {:else}
42
+ <span class="text-sm font-[var(--font-body)] {index === items.length - 1 ? 'text-[var(--color-text)]' : 'text-[var(--color-text-soft)]'}">
43
+ {item.label}
44
+ </span>
45
+ {/if}
46
+
47
+ {#if index < items.length - 1}
48
+ <span class="text-[var(--color-text-muted)] text-sm" aria-hidden="true">
49
+ {#if separatorIcon}
50
+ {@render separatorIcon()}
51
+ {:else}
52
+ {separator}
53
+ {/if}
54
+ </span>
55
+ {/if}
56
+ </li>
57
+ {/each}
58
+ </ol>
59
+ </nav>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ onclick?: () => void;
6
+ }
7
+ interface Props {
8
+ items: BreadcrumbItem[];
9
+ separator?: string;
10
+ separatorIcon?: Snippet;
11
+ }
12
+ declare const Breadcrumbs: import("svelte").Component<Props, {}, "">;
13
+ type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
14
+ export default Breadcrumbs;
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ open?: boolean;
6
+ x?: number;
7
+ y?: number;
8
+ trigger?: Snippet;
9
+ children?: Snippet;
10
+ }
11
+
12
+ let {
13
+ open = $bindable(false),
14
+ x = $bindable(0),
15
+ y = $bindable(0),
16
+ trigger,
17
+ children
18
+ }: Props = $props();
19
+
20
+ let menuElement = $state<HTMLDivElement>();
21
+
22
+ function handleContextMenu(event: MouseEvent) {
23
+ event.preventDefault();
24
+ x = event.clientX;
25
+ y = event.clientY;
26
+ open = true;
27
+
28
+ // Adjust position if menu would overflow viewport
29
+ requestAnimationFrame(() => {
30
+ if (menuElement) {
31
+ const menuRect = menuElement.getBoundingClientRect();
32
+ const viewportWidth = window.innerWidth;
33
+ const viewportHeight = window.innerHeight;
34
+
35
+ if (x + menuRect.width > viewportWidth) {
36
+ x = viewportWidth - menuRect.width - 10;
37
+ }
38
+
39
+ if (y + menuRect.height > viewportHeight) {
40
+ y = viewportHeight - menuRect.height - 10;
41
+ }
42
+ }
43
+ });
44
+ }
45
+
46
+ function handleClickOutside(event: MouseEvent) {
47
+ if (menuElement && !menuElement.contains(event.target as Node)) {
48
+ open = false;
49
+ }
50
+ }
51
+
52
+ function handleEscape(event: KeyboardEvent) {
53
+ if (event.key === 'Escape') {
54
+ open = false;
55
+ }
56
+ }
57
+
58
+ $effect(() => {
59
+ if (open) {
60
+ document.addEventListener('click', handleClickOutside);
61
+ document.addEventListener('keydown', handleEscape);
62
+
63
+ return () => {
64
+ document.removeEventListener('click', handleClickOutside);
65
+ document.removeEventListener('keydown', handleEscape);
66
+ };
67
+ }
68
+ });
69
+ </script>
70
+
71
+ <div oncontextmenu={handleContextMenu} role="presentation" class="contents">
72
+ {@render trigger?.()}
73
+ </div>
74
+
75
+ {#if open}
76
+ <div
77
+ bind:this={menuElement}
78
+ class="fixed z-50 glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-in_0.15s_var(--ease-luxe)]"
79
+ style="left: {x}px; top: {y}px;"
80
+ >
81
+ {@render children?.()}
82
+ </div>
83
+ {/if}
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ open?: boolean;
4
+ x?: number;
5
+ y?: number;
6
+ trigger?: Snippet;
7
+ children?: Snippet;
8
+ }
9
+ declare const ContextMenu: import("svelte").Component<Props, {}, "open" | "x" | "y">;
10
+ type ContextMenu = ReturnType<typeof ContextMenu>;
11
+ export default ContextMenu;
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ open?: boolean;
6
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
7
+ trigger?: Snippet;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let {
12
+ open = $bindable(false),
13
+ placement = 'bottom-start',
14
+ trigger,
15
+ children
16
+ }: Props = $props();
17
+
18
+ let menuElement: HTMLDivElement;
19
+ let wrapperElement: HTMLDivElement;
20
+
21
+ const placementClasses = {
22
+ 'bottom-start': 'top-full left-0 mt-2',
23
+ 'bottom-end': 'top-full right-0 mt-2',
24
+ 'top-start': 'bottom-full left-0 mb-2',
25
+ 'top-end': 'bottom-full right-0 mb-2'
26
+ };
27
+
28
+ function toggleMenu() {
29
+ open = !open;
30
+ }
31
+
32
+ function handleClickOutside(event: MouseEvent) {
33
+ if (menuElement && !wrapperElement.contains(event.target as Node)) {
34
+ open = false;
35
+ }
36
+ }
37
+
38
+ function handleEscape(event: KeyboardEvent) {
39
+ if (event.key === 'Escape') {
40
+ open = false;
41
+ }
42
+ }
43
+
44
+ $effect(() => {
45
+ if (open) {
46
+ document.addEventListener('click', handleClickOutside);
47
+ document.addEventListener('keydown', handleEscape);
48
+
49
+ return () => {
50
+ document.removeEventListener('click', handleClickOutside);
51
+ document.removeEventListener('keydown', handleEscape);
52
+ };
53
+ }
54
+ });
55
+ </script>
56
+
57
+ <div bind:this={wrapperElement} class="relative">
58
+ <button
59
+ type="button"
60
+ onclick={toggleMenu}
61
+ onkeydown={(e) => {
62
+ if (e.key === 'Enter' || e.key === ' ') {
63
+ e.preventDefault();
64
+ toggleMenu();
65
+ }
66
+ }}
67
+ class="bg-transparent border-none p-0 cursor-pointer"
68
+ >
69
+ {@render trigger?.()}
70
+ </button>
71
+
72
+ {#if open}
73
+ <div
74
+ bind:this={menuElement}
75
+ class="absolute {placementClasses[placement]} z-50 glass-panel rounded-[var(--radius-lg)] shadow-[var(--shadow-deep)] min-w-[12rem] animate-[fade-up_0.2s_var(--ease-luxe)]"
76
+ >
77
+ {@render children?.()}
78
+ </div>
79
+ {/if}
80
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ open?: boolean;
4
+ placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
5
+ trigger?: Snippet;
6
+ children?: Snippet;
7
+ }
8
+ declare const DropdownMenu: import("svelte").Component<Props, {}, "open">;
9
+ type DropdownMenu = ReturnType<typeof DropdownMenu>;
10
+ export default DropdownMenu;
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface MenuItem {
5
+ id: string;
6
+ label: string;
7
+ icon?: Snippet;
8
+ disabled?: boolean;
9
+ onclick?: () => void;
10
+ }
11
+
12
+ interface Props {
13
+ items?: MenuItem[];
14
+ children?: Snippet;
15
+ }
16
+
17
+ let {
18
+ items,
19
+ children
20
+ }: Props = $props();
21
+
22
+ function handleItemClick(item: MenuItem) {
23
+ if (!item.disabled && item.onclick) {
24
+ item.onclick();
25
+ }
26
+ }
27
+ </script>
28
+
29
+ <div role="menu" class="glass-panel rounded-[var(--radius-lg)] p-2 shadow-[var(--shadow-deep)]">
30
+ {#if children}
31
+ {@render children()}
32
+ {:else if items}
33
+ {#each items as item}
34
+ <button
35
+ role="menuitem"
36
+ aria-disabled={item.disabled}
37
+ onclick={() => handleItemClick(item)}
38
+ disabled={item.disabled}
39
+ class="flex items-center gap-2 w-full px-3 py-2 rounded-[var(--radius-md)] text-[var(--color-text)] text-sm font-[var(--font-body)] hover:bg-[var(--color-base-3)] transition-all duration-200 ease-[var(--ease-luxe)] {item.disabled ? 'opacity-50 pointer-events-none' : 'cursor-pointer'}"
40
+ >
41
+ {#if item.icon}
42
+ {@render item.icon()}
43
+ {/if}
44
+ <span>{item.label}</span>
45
+ </button>
46
+ {/each}
47
+ {/if}
48
+ </div>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface MenuItem {
3
+ id: string;
4
+ label: string;
5
+ icon?: Snippet;
6
+ disabled?: boolean;
7
+ onclick?: () => void;
8
+ }
9
+ interface Props {
10
+ items?: MenuItem[];
11
+ children?: Snippet;
12
+ }
13
+ declare const Menu: import("svelte").Component<Props, {}, "">;
14
+ type Menu = ReturnType<typeof Menu>;
15
+ export default Menu;
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ position?: 'sticky' | 'fixed';
6
+ height?: 'sm' | 'md' | 'lg';
7
+ children?: Snippet;
8
+ }
9
+
10
+ let {
11
+ position = 'sticky',
12
+ height = 'md',
13
+ children
14
+ }: Props = $props();
15
+
16
+ const positionClasses = {
17
+ sticky: 'sticky top-0',
18
+ fixed: 'fixed top-0 left-0 right-0'
19
+ };
20
+
21
+ const heightClasses = {
22
+ sm: 'h-14',
23
+ md: 'h-16',
24
+ lg: 'h-20'
25
+ };
26
+
27
+ const baseClasses = 'z-40 glass-panel flex items-center justify-between px-4 md:px-6 lg:px-8 transition-all duration-300 ease-[var(--ease-luxe)]';
28
+ </script>
29
+
30
+ <nav class="{baseClasses} {positionClasses[position]} {heightClasses[height]}">
31
+ {@render children?.()}
32
+ </nav>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ position?: 'sticky' | 'fixed';
4
+ height?: 'sm' | 'md' | 'lg';
5
+ children?: Snippet;
6
+ }
7
+ declare const Navbar: import("svelte").Component<Props, {}, "">;
8
+ type Navbar = ReturnType<typeof Navbar>;
9
+ export default Navbar;
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ open?: boolean;
6
+ side?: 'left' | 'right';
7
+ width?: 'sm' | 'md' | 'lg';
8
+ children?: Snippet;
9
+ }
10
+
11
+ let {
12
+ open = $bindable(true),
13
+ side = 'left',
14
+ width = 'md',
15
+ children
16
+ }: Props = $props();
17
+
18
+ const widthClasses = {
19
+ sm: { open: 'w-56', collapsed: 'w-14' },
20
+ md: { open: 'w-64', collapsed: 'w-16' },
21
+ lg: { open: 'w-80', collapsed: 'w-20' }
22
+ };
23
+
24
+ const sideClasses = {
25
+ left: 'left-0',
26
+ right: 'right-0'
27
+ };
28
+
29
+ const currentWidth = $derived(open ? widthClasses[width].open : widthClasses[width].collapsed);
30
+ const baseClasses = 'fixed top-0 bottom-0 z-30 glass-panel overflow-y-auto overflow-x-hidden transition-all duration-300 ease-[var(--ease-luxe)]';
31
+ </script>
32
+
33
+ <aside class="{baseClasses} {sideClasses[side]} {currentWidth}">
34
+ {@render children?.()}
35
+ </aside>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ open?: boolean;
4
+ side?: 'left' | 'right';
5
+ width?: 'sm' | 'md' | 'lg';
6
+ children?: Snippet;
7
+ }
8
+ declare const Sidebar: import("svelte").Component<Props, {}, "open">;
9
+ type Sidebar = ReturnType<typeof Sidebar>;
10
+ export default Sidebar;