@nqmcreative/ui 0.2.1 → 0.2.3

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 (175) hide show
  1. package/README.md +7 -5
  2. package/dist/core/index.d.ts +1 -1
  3. package/dist/core/index.js +1 -1
  4. package/dist/core/trigger.d.ts +6 -0
  5. package/dist/core/trigger.js +13 -0
  6. package/dist/styles/matte/Button.svelte +4 -4
  7. package/dist/styles/matte/Card.svelte +3 -3
  8. package/dist/styles/matte/Combobox.svelte +2 -2
  9. package/dist/styles/matte/CommandPalette.svelte +32 -7
  10. package/dist/styles/matte/CommandPalette.svelte.d.ts +5 -0
  11. package/dist/styles/matte/DatePicker.svelte +2 -2
  12. package/dist/styles/matte/Drawer.svelte +2 -2
  13. package/dist/styles/matte/Dropzone.svelte +1 -1
  14. package/dist/styles/matte/FeatureCard.svelte +1 -1
  15. package/dist/styles/matte/Input.svelte +4 -4
  16. package/dist/styles/matte/MenuItem.svelte +1 -1
  17. package/dist/styles/matte/Modal.svelte +3 -3
  18. package/dist/styles/matte/NumberInput.svelte +2 -2
  19. package/dist/styles/matte/PasswordInput.svelte +2 -2
  20. package/dist/styles/matte/PricingCard.svelte +3 -3
  21. package/dist/styles/matte/SegmentedControl.svelte +1 -1
  22. package/dist/styles/matte/Select.svelte +1 -1
  23. package/dist/styles/matte/Sidebar.svelte +75 -26
  24. package/dist/styles/matte/Sidebar.svelte.d.ts +15 -1
  25. package/dist/styles/matte/StatsBand.svelte +1 -1
  26. package/dist/styles/matte/Table.svelte +3 -3
  27. package/dist/styles/matte/Testimonial.svelte +2 -2
  28. package/dist/styles/matte/Textarea.svelte +1 -1
  29. package/dist/styles/matte/index.d.ts +1 -1
  30. package/dist/styles/paper/CommandPalette.svelte +25 -2
  31. package/dist/styles/paper/CommandPalette.svelte.d.ts +5 -0
  32. package/dist/styles/paper/Modal.svelte +9 -1
  33. package/dist/styles/paper/Sidebar.svelte +75 -26
  34. package/dist/styles/paper/Sidebar.svelte.d.ts +15 -1
  35. package/dist/styles/paper/fonts.css +34 -7
  36. package/dist/styles/paper/index.d.ts +1 -1
  37. package/dist/styles/paper/theme.css +11 -8
  38. package/dist/styles/sprout/Accordion.svelte +17 -0
  39. package/dist/styles/sprout/Accordion.svelte.d.ts +8 -0
  40. package/dist/styles/sprout/AccordionItem.svelte +46 -0
  41. package/dist/styles/sprout/AccordionItem.svelte.d.ts +14 -0
  42. package/dist/styles/sprout/Alert.svelte +100 -0
  43. package/dist/styles/sprout/Alert.svelte.d.ts +18 -0
  44. package/dist/styles/sprout/Avatar.svelte +60 -0
  45. package/dist/styles/sprout/Avatar.svelte.d.ts +16 -0
  46. package/dist/styles/sprout/AvatarGroup.svelte +17 -0
  47. package/dist/styles/sprout/AvatarGroup.svelte.d.ts +8 -0
  48. package/dist/styles/sprout/Badge.svelte +58 -0
  49. package/dist/styles/sprout/Badge.svelte.d.ts +16 -0
  50. package/dist/styles/sprout/Breadcrumb.svelte +49 -0
  51. package/dist/styles/sprout/Breadcrumb.svelte.d.ts +14 -0
  52. package/dist/styles/sprout/Button.svelte +105 -0
  53. package/dist/styles/sprout/Button.svelte.d.ts +21 -0
  54. package/dist/styles/sprout/CTASection.svelte +77 -0
  55. package/dist/styles/sprout/CTASection.svelte.d.ts +18 -0
  56. package/dist/styles/sprout/Calendar.svelte +202 -0
  57. package/dist/styles/sprout/Calendar.svelte.d.ts +22 -0
  58. package/dist/styles/sprout/Card.svelte +82 -0
  59. package/dist/styles/sprout/Card.svelte.d.ts +21 -0
  60. package/dist/styles/sprout/Checkbox.svelte +76 -0
  61. package/dist/styles/sprout/Checkbox.svelte.d.ts +15 -0
  62. package/dist/styles/sprout/CheckboxGroup.svelte +112 -0
  63. package/dist/styles/sprout/CheckboxGroup.svelte.d.ts +30 -0
  64. package/dist/styles/sprout/Combobox.svelte +267 -0
  65. package/dist/styles/sprout/Combobox.svelte.d.ts +29 -0
  66. package/dist/styles/sprout/CommandPalette.svelte +233 -0
  67. package/dist/styles/sprout/CommandPalette.svelte.d.ts +37 -0
  68. package/dist/styles/sprout/ConfirmDialog.svelte +97 -0
  69. package/dist/styles/sprout/ConfirmDialog.svelte.d.ts +22 -0
  70. package/dist/styles/sprout/ContextMenu.svelte +89 -0
  71. package/dist/styles/sprout/ContextMenu.svelte.d.ts +15 -0
  72. package/dist/styles/sprout/DatePicker.svelte +193 -0
  73. package/dist/styles/sprout/DatePicker.svelte.d.ts +23 -0
  74. package/dist/styles/sprout/Divider.svelte +30 -0
  75. package/dist/styles/sprout/Divider.svelte.d.ts +10 -0
  76. package/dist/styles/sprout/Drawer.svelte +166 -0
  77. package/dist/styles/sprout/Drawer.svelte.d.ts +20 -0
  78. package/dist/styles/sprout/Dropdown.svelte +95 -0
  79. package/dist/styles/sprout/Dropdown.svelte.d.ts +23 -0
  80. package/dist/styles/sprout/Dropzone.svelte +211 -0
  81. package/dist/styles/sprout/Dropzone.svelte.d.ts +29 -0
  82. package/dist/styles/sprout/EmptyState.svelte +71 -0
  83. package/dist/styles/sprout/EmptyState.svelte.d.ts +18 -0
  84. package/dist/styles/sprout/FeatureCard.svelte +61 -0
  85. package/dist/styles/sprout/FeatureCard.svelte.d.ts +16 -0
  86. package/dist/styles/sprout/FeatureGrid.svelte +62 -0
  87. package/dist/styles/sprout/FeatureGrid.svelte.d.ts +16 -0
  88. package/dist/styles/sprout/Field.svelte +42 -0
  89. package/dist/styles/sprout/Field.svelte.d.ts +16 -0
  90. package/dist/styles/sprout/Footer.svelte +91 -0
  91. package/dist/styles/sprout/Footer.svelte.d.ts +25 -0
  92. package/dist/styles/sprout/HeroSection.svelte +86 -0
  93. package/dist/styles/sprout/HeroSection.svelte.d.ts +21 -0
  94. package/dist/styles/sprout/Input.svelte +61 -0
  95. package/dist/styles/sprout/Input.svelte.d.ts +18 -0
  96. package/dist/styles/sprout/InputGroup.svelte +20 -0
  97. package/dist/styles/sprout/InputGroup.svelte.d.ts +11 -0
  98. package/dist/styles/sprout/Kbd.svelte +18 -0
  99. package/dist/styles/sprout/Kbd.svelte.d.ts +8 -0
  100. package/dist/styles/sprout/Label.svelte +16 -0
  101. package/dist/styles/sprout/Label.svelte.d.ts +9 -0
  102. package/dist/styles/sprout/Link.svelte +44 -0
  103. package/dist/styles/sprout/Link.svelte.d.ts +16 -0
  104. package/dist/styles/sprout/LocaleProvider.svelte +24 -0
  105. package/dist/styles/sprout/LocaleProvider.svelte.d.ts +10 -0
  106. package/dist/styles/sprout/LogoCloud.svelte +61 -0
  107. package/dist/styles/sprout/LogoCloud.svelte.d.ts +19 -0
  108. package/dist/styles/sprout/MenuItem.svelte +64 -0
  109. package/dist/styles/sprout/MenuItem.svelte.d.ts +18 -0
  110. package/dist/styles/sprout/MenuSeparator.svelte +22 -0
  111. package/dist/styles/sprout/MenuSeparator.svelte.d.ts +8 -0
  112. package/dist/styles/sprout/Modal.svelte +127 -0
  113. package/dist/styles/sprout/Modal.svelte.d.ts +21 -0
  114. package/dist/styles/sprout/MultiSelect.svelte +288 -0
  115. package/dist/styles/sprout/MultiSelect.svelte.d.ts +31 -0
  116. package/dist/styles/sprout/Navbar.svelte +201 -0
  117. package/dist/styles/sprout/Navbar.svelte.d.ts +33 -0
  118. package/dist/styles/sprout/NumberInput.svelte +97 -0
  119. package/dist/styles/sprout/NumberInput.svelte.d.ts +15 -0
  120. package/dist/styles/sprout/Pagination.svelte +84 -0
  121. package/dist/styles/sprout/Pagination.svelte.d.ts +15 -0
  122. package/dist/styles/sprout/PasswordInput.svelte +101 -0
  123. package/dist/styles/sprout/PasswordInput.svelte.d.ts +12 -0
  124. package/dist/styles/sprout/Popover.svelte +90 -0
  125. package/dist/styles/sprout/Popover.svelte.d.ts +19 -0
  126. package/dist/styles/sprout/PricingCard.svelte +109 -0
  127. package/dist/styles/sprout/PricingCard.svelte.d.ts +25 -0
  128. package/dist/styles/sprout/Progress.svelte +59 -0
  129. package/dist/styles/sprout/Progress.svelte.d.ts +17 -0
  130. package/dist/styles/sprout/Radio.svelte +56 -0
  131. package/dist/styles/sprout/Radio.svelte.d.ts +15 -0
  132. package/dist/styles/sprout/RadioGroup.svelte +96 -0
  133. package/dist/styles/sprout/RadioGroup.svelte.d.ts +27 -0
  134. package/dist/styles/sprout/SegmentedControl.svelte +89 -0
  135. package/dist/styles/sprout/SegmentedControl.svelte.d.ts +25 -0
  136. package/dist/styles/sprout/Select.svelte +62 -0
  137. package/dist/styles/sprout/Select.svelte.d.ts +20 -0
  138. package/dist/styles/sprout/Sidebar.svelte +255 -0
  139. package/dist/styles/sprout/Sidebar.svelte.d.ts +50 -0
  140. package/dist/styles/sprout/Skeleton.svelte +46 -0
  141. package/dist/styles/sprout/Skeleton.svelte.d.ts +13 -0
  142. package/dist/styles/sprout/Slider.svelte +80 -0
  143. package/dist/styles/sprout/Slider.svelte.d.ts +19 -0
  144. package/dist/styles/sprout/Spinner.svelte +34 -0
  145. package/dist/styles/sprout/Spinner.svelte.d.ts +13 -0
  146. package/dist/styles/sprout/Stat.svelte +51 -0
  147. package/dist/styles/sprout/Stat.svelte.d.ts +16 -0
  148. package/dist/styles/sprout/StatsBand.svelte +64 -0
  149. package/dist/styles/sprout/StatsBand.svelte.d.ts +20 -0
  150. package/dist/styles/sprout/Steps.svelte +143 -0
  151. package/dist/styles/sprout/Steps.svelte.d.ts +24 -0
  152. package/dist/styles/sprout/Switch.svelte +65 -0
  153. package/dist/styles/sprout/Switch.svelte.d.ts +14 -0
  154. package/dist/styles/sprout/Table.svelte +218 -0
  155. package/dist/styles/sprout/Table.svelte.d.ts +66 -0
  156. package/dist/styles/sprout/Tabs.svelte +103 -0
  157. package/dist/styles/sprout/Tabs.svelte.d.ts +23 -0
  158. package/dist/styles/sprout/Testimonial.svelte +65 -0
  159. package/dist/styles/sprout/Testimonial.svelte.d.ts +19 -0
  160. package/dist/styles/sprout/Textarea.svelte +55 -0
  161. package/dist/styles/sprout/Textarea.svelte.d.ts +12 -0
  162. package/dist/styles/sprout/ThemeToggle.svelte +131 -0
  163. package/dist/styles/sprout/ThemeToggle.svelte.d.ts +16 -0
  164. package/dist/styles/sprout/Toaster.svelte +133 -0
  165. package/dist/styles/sprout/Toaster.svelte.d.ts +8 -0
  166. package/dist/styles/sprout/Tooltip.svelte +34 -0
  167. package/dist/styles/sprout/Tooltip.svelte.d.ts +12 -0
  168. package/dist/styles/sprout/fonts.css +77 -0
  169. package/dist/styles/sprout/index.d.ts +109 -0
  170. package/dist/styles/sprout/index.js +83 -0
  171. package/dist/styles/sprout/lift.d.ts +40 -0
  172. package/dist/styles/sprout/lift.js +36 -0
  173. package/dist/styles/sprout/theme.css +255 -0
  174. package/package.json +269 -3
  175. package/registry.json +139 -66
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { anchored } from '../../core/actions/anchor.js';
4
+ import { clickOutside, focusTrap, navigateList, portal } from '../../core/actions/dismissable.js';
5
+ import { menuItems } from '../../core/trigger.js';
6
+ import { useLocale } from '../../core/locale.svelte.js';
7
+ import { soft } from './lift.js';
8
+
9
+ interface Props {
10
+ /** The region that responds to a right-click. */
11
+ children: Snippet;
12
+ /** Menu contents: `MenuItem`, `MenuSeparator`, anything. */
13
+ menu: Snippet;
14
+ open?: boolean;
15
+ label?: string;
16
+ disabled?: boolean;
17
+ class?: string;
18
+ menuClass?: string;
19
+ }
20
+
21
+ let {
22
+ children,
23
+ menu,
24
+ open = $bindable(false),
25
+ label,
26
+ disabled = false,
27
+ class: className = '',
28
+ menuClass = ''
29
+ }: Props = $props();
30
+
31
+ const t = useLocale();
32
+
33
+ let menuEl: HTMLElement | null = $state(null);
34
+ /**
35
+ * A 0×0 element parked at the pointer, so the same `anchored` action that
36
+ * positions dropdowns can flip and clamp this menu too.
37
+ */
38
+ let cursor: HTMLElement | null = $state(null);
39
+
40
+ function onContextMenu(event: MouseEvent) {
41
+ if (disabled) return;
42
+ event.preventDefault();
43
+ if (!cursor) {
44
+ cursor = document.createElement('div');
45
+ cursor.style.cssText = 'position:fixed;width:0;height:0;pointer-events:none';
46
+ document.body.appendChild(cursor);
47
+ }
48
+ cursor.style.left = `${event.clientX}px`;
49
+ cursor.style.top = `${event.clientY}px`;
50
+ open = true;
51
+ }
52
+
53
+ $effect(() => () => cursor?.remove());
54
+
55
+ function onMenuKeydown(event: KeyboardEvent) {
56
+ if (event.key === 'Escape' || event.key === 'Tab') {
57
+ if (event.key === 'Escape') event.preventDefault();
58
+ event.stopPropagation();
59
+ open = false;
60
+ return;
61
+ }
62
+ navigateList(event, menuItems(menuEl));
63
+ }
64
+ </script>
65
+
66
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
67
+ <div oncontextmenu={onContextMenu} class={className}>
68
+ {@render children()}
69
+ </div>
70
+
71
+ {#if open}
72
+ <div
73
+ bind:this={menuEl}
74
+ use:portal
75
+ use:anchored={{ anchor: cursor, placement: 'bottom-start', offset: 2 }}
76
+ use:clickOutside={{ onoutside: () => (open = false) }}
77
+ use:focusTrap={{ restore: true }}
78
+ onkeydown={onMenuKeydown}
79
+ onclick={(event) => {
80
+ if ((event.target as HTMLElement).closest('[role="menuitem"]:not([disabled])')) open = false;
81
+ }}
82
+ role="menu"
83
+ aria-label={label ?? t.current.menu}
84
+ tabindex="-1"
85
+ class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 flex-col overflow-y-auto rounded-2xl border border-hairline bg-bg py-1 font-sans {soft} {menuClass}"
86
+ >
87
+ {@render menu()}
88
+ </div>
89
+ {/if}
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** The region that responds to a right-click. */
4
+ children: Snippet;
5
+ /** Menu contents: `MenuItem`, `MenuSeparator`, anything. */
6
+ menu: Snippet;
7
+ open?: boolean;
8
+ label?: string;
9
+ disabled?: boolean;
10
+ class?: string;
11
+ menuClass?: string;
12
+ }
13
+ declare const ContextMenu: import("svelte").Component<Props, {}, "open">;
14
+ type ContextMenu = ReturnType<typeof ContextMenu>;
15
+ export default ContextMenu;
@@ -0,0 +1,193 @@
1
+ <script lang="ts">
2
+ import { anchored } from '../../core/actions/anchor.js';
3
+ import { clickOutside, portal } from '../../core/actions/dismissable.js';
4
+ import { dateHint } from '../../core/calendar.js';
5
+ import { formatISO, parseFormatted } from '../../core/date.js';
6
+ import { useLocale } from '../../core/locale.svelte.js';
7
+ import { toneFocusWithinBorder, type Tone } from '../../core/tones.js';
8
+ import Calendar from './Calendar.svelte';
9
+ import { edge, soft } from './lift.js';
10
+
11
+ interface Props {
12
+ /** Bindable `YYYY-MM-DD` — the same shape as `<input type="date">`. */
13
+ value?: string;
14
+ /** Inclusive bounds, `YYYY-MM-DD`. */
15
+ min?: string;
16
+ max?: string;
17
+ isDisabled?: (date: Date) => boolean;
18
+ weekStart?: 0 | 1;
19
+ tone?: Tone;
20
+ invalid?: boolean;
21
+ disabled?: boolean;
22
+ clearable?: boolean;
23
+ placeholder?: string;
24
+ id?: string;
25
+ /** Submits the ISO value with a form. */
26
+ name?: string;
27
+ onchange?: (value: string) => void;
28
+ class?: string;
29
+ }
30
+
31
+ let {
32
+ value = $bindable(''),
33
+ min,
34
+ max,
35
+ isDisabled,
36
+ weekStart = 1,
37
+ tone = 'brand',
38
+ invalid = false,
39
+ disabled = false,
40
+ clearable = true,
41
+ placeholder,
42
+ id,
43
+ name,
44
+ onchange,
45
+ class: className = ''
46
+ }: Props = $props();
47
+
48
+ const t = useLocale();
49
+
50
+ let open = $state(false);
51
+ let text = $state('');
52
+ let editing = $state(false);
53
+ let wrapper: HTMLElement | null = $state(null);
54
+ let inputEl: HTMLInputElement | null = $state(null);
55
+
56
+ /** While typing the field shows the raw text; otherwise the formatted value. */
57
+ const display = $derived(editing ? text : formatISO(value, t.current.dateFormat));
58
+ const hint = $derived(placeholder ?? dateHint(t.current.dateFormat));
59
+
60
+ function commit() {
61
+ editing = false;
62
+ if (!text.trim()) {
63
+ if (value) {
64
+ value = '';
65
+ onchange?.('');
66
+ }
67
+ return;
68
+ }
69
+ const parsed = parseFormatted(text, t.current.dateFormat);
70
+ if (parsed && parsed !== value) {
71
+ value = parsed;
72
+ onchange?.(parsed);
73
+ }
74
+ }
75
+
76
+ function onKeydown(event: KeyboardEvent) {
77
+ if (event.key === 'Escape' && open) {
78
+ event.preventDefault();
79
+ event.stopPropagation();
80
+ open = false;
81
+ return;
82
+ }
83
+ if (event.key === 'Enter') {
84
+ event.preventDefault();
85
+ commit();
86
+ open = false;
87
+ return;
88
+ }
89
+ if (event.key === 'ArrowDown' && !open) {
90
+ event.preventDefault();
91
+ open = true;
92
+ }
93
+ }
94
+ </script>
95
+
96
+ <div
97
+ bind:this={wrapper}
98
+ class="inline-flex w-full items-center gap-1 rounded-xl border bg-bg pr-1 pl-3 {edge} transition-colors duration-150 ease-brand-out
99
+ {invalid ? 'border-danger' : `border-hairline-strong ${toneFocusWithinBorder[tone]}`}
100
+ {disabled ? 'pointer-events-none opacity-50' : ''} {className}"
101
+ >
102
+ <input
103
+ bind:this={inputEl}
104
+ {id}
105
+ {disabled}
106
+ type="text"
107
+ inputmode="numeric"
108
+ autocomplete="off"
109
+ role="combobox"
110
+ aria-haspopup="dialog"
111
+ aria-controls="{id ?? 'datepicker'}-calendar"
112
+ aria-invalid={invalid ? 'true' : undefined}
113
+ aria-expanded={open}
114
+ placeholder={hint}
115
+ value={display}
116
+ oninput={(event) => {
117
+ editing = true;
118
+ text = event.currentTarget.value;
119
+ }}
120
+ onblur={commit}
121
+ onkeydown={onKeydown}
122
+ class="h-10 w-full min-w-0 bg-transparent font-sans text-sm text-text tabular-nums placeholder:text-text-muted focus:outline-none"
123
+ />
124
+
125
+ {#if name}<input type="hidden" {name} {value} />{/if}
126
+
127
+ {#if clearable && value && !disabled}
128
+ <button
129
+ type="button"
130
+ onclick={() => {
131
+ value = '';
132
+ text = '';
133
+ onchange?.('');
134
+ inputEl?.focus();
135
+ }}
136
+ aria-label={t.current.clear}
137
+ class="shrink-0 rounded p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
138
+ >
139
+ <svg class="size-3.5" viewBox="0 0 14 14" fill="none" aria-hidden="true">
140
+ <path
141
+ d="m2 2 10 10M12 2 2 12"
142
+ stroke="currentColor"
143
+ stroke-width="1.6"
144
+ stroke-linecap="round"
145
+ />
146
+ </svg>
147
+ </button>
148
+ {/if}
149
+
150
+ <button
151
+ type="button"
152
+ {disabled}
153
+ onclick={() => (open = !open)}
154
+ aria-label={t.current.selectDate}
155
+ aria-expanded={open}
156
+ class="shrink-0 rounded p-1.5 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current"
157
+ >
158
+ <svg class="size-4" viewBox="0 0 20 20" fill="none" aria-hidden="true">
159
+ <rect x="3" y="4.5" width="14" height="12" rx="2" stroke="currentColor" stroke-width="1.4" />
160
+ <path
161
+ d="M3 8.5h14M7 3v3M13 3v3"
162
+ stroke="currentColor"
163
+ stroke-width="1.4"
164
+ stroke-linecap="round"
165
+ />
166
+ </svg>
167
+ </button>
168
+ </div>
169
+
170
+ {#if open}
171
+ <div
172
+ use:portal
173
+ use:anchored={{ anchor: wrapper, placement: 'bottom-start', offset: 4 }}
174
+ use:clickOutside={{ onoutside: () => (open = false), ignore: [wrapper] }}
175
+ id="{id ?? 'datepicker'}-calendar"
176
+ class="z-50 rounded-2xl border border-hairline bg-bg {soft}"
177
+ >
178
+ <Calendar
179
+ bind:value
180
+ {min}
181
+ {max}
182
+ {isDisabled}
183
+ {weekStart}
184
+ {tone}
185
+ onselect={(next) => {
186
+ editing = false;
187
+ open = false;
188
+ onchange?.(next);
189
+ inputEl?.focus();
190
+ }}
191
+ />
192
+ </div>
193
+ {/if}
@@ -0,0 +1,23 @@
1
+ import { type Tone } from '../../core/tones.js';
2
+ interface Props {
3
+ /** Bindable `YYYY-MM-DD` — the same shape as `<input type="date">`. */
4
+ value?: string;
5
+ /** Inclusive bounds, `YYYY-MM-DD`. */
6
+ min?: string;
7
+ max?: string;
8
+ isDisabled?: (date: Date) => boolean;
9
+ weekStart?: 0 | 1;
10
+ tone?: Tone;
11
+ invalid?: boolean;
12
+ disabled?: boolean;
13
+ clearable?: boolean;
14
+ placeholder?: string;
15
+ id?: string;
16
+ /** Submits the ISO value with a form. */
17
+ name?: string;
18
+ onchange?: (value: string) => void;
19
+ class?: string;
20
+ }
21
+ declare const DatePicker: import("svelte").Component<Props, {}, "value">;
22
+ type DatePicker = ReturnType<typeof DatePicker>;
23
+ export default DatePicker;
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ export type DividerOrientation = 'horizontal' | 'vertical';
5
+
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ orientation?: DividerOrientation;
8
+ /** Centred caption drawn over a horizontal rule. */
9
+ label?: string;
10
+ }
11
+
12
+ let { orientation = 'horizontal', label, class: className = '', ...rest }: Props = $props();
13
+ </script>
14
+
15
+ {#if orientation === 'vertical'}
16
+ <div
17
+ role="separator"
18
+ aria-orientation="vertical"
19
+ class="w-px self-stretch bg-hairline {className}"
20
+ {...rest}
21
+ ></div>
22
+ {:else if label}
23
+ <div class="flex items-center gap-3 {className}" {...rest}>
24
+ <span class="h-px flex-1 bg-hairline"></span>
25
+ <span class="font-sans text-xs font-medium text-text-muted">{label}</span>
26
+ <span class="h-px flex-1 bg-hairline"></span>
27
+ </div>
28
+ {:else}
29
+ <div role="separator" class="h-px w-full bg-hairline {className}" {...rest}></div>
30
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export type DividerOrientation = 'horizontal' | 'vertical';
3
+ interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ orientation?: DividerOrientation;
5
+ /** Centred caption drawn over a horizontal rule. */
6
+ label?: string;
7
+ }
8
+ declare const Divider: import("svelte").Component<Props, {}, "">;
9
+ type Divider = ReturnType<typeof Divider>;
10
+ export default Divider;
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLDialogAttributes } from 'svelte/elements';
4
+ import { useLocale } from '../../core/locale.svelte.js';
5
+ import { isBackdropClick, syncDialog } from '../../core/dialog.svelte.js';
6
+ import { float } from './lift.js';
7
+
8
+ export type DrawerSide = 'left' | 'right' | 'top' | 'bottom';
9
+ export type DrawerSize = 'sm' | 'md' | 'lg' | 'full';
10
+
11
+ interface Props extends HTMLDialogAttributes {
12
+ /** Bindable. Built on the native `<dialog>`, so it gets the top layer,
13
+ * the backdrop, Escape handling and focus containment for free. */
14
+ open?: boolean;
15
+ side?: DrawerSide;
16
+ size?: DrawerSize;
17
+ title?: string;
18
+ description?: string;
19
+ dismissible?: boolean;
20
+ footer?: Snippet;
21
+ onclose?: () => void;
22
+ children: Snippet;
23
+ }
24
+
25
+ let {
26
+ open = $bindable(false),
27
+ side = 'right',
28
+ size = 'md',
29
+ title,
30
+ description,
31
+ dismissible = true,
32
+ footer,
33
+ onclose,
34
+ class: className = '',
35
+ children,
36
+ ...rest
37
+ }: Props = $props();
38
+
39
+ const t = useLocale();
40
+ let dialog: HTMLDialogElement | null = $state(null);
41
+
42
+ const horizontal = $derived(side === 'left' || side === 'right');
43
+
44
+ const widths: Record<DrawerSize, string> = {
45
+ sm: 'w-[min(20rem,100vw)]',
46
+ md: 'w-[min(28rem,100vw)]',
47
+ lg: 'w-[min(40rem,100vw)]',
48
+ full: 'w-screen'
49
+ };
50
+
51
+ const heights: Record<DrawerSize, string> = {
52
+ sm: 'h-[min(16rem,100vh)]',
53
+ md: 'h-[min(24rem,100vh)]',
54
+ lg: 'h-[min(36rem,100vh)]',
55
+ full: 'h-screen'
56
+ };
57
+
58
+ const sides: Record<DrawerSide, string> = {
59
+ left: 'mr-auto ml-0 h-screen max-h-screen border-r',
60
+ right: 'mr-0 ml-auto h-screen max-h-screen border-l',
61
+ top: 'mt-0 mb-auto w-screen max-w-screen border-b',
62
+ bottom: 'mt-auto mb-0 w-screen max-w-screen border-t'
63
+ };
64
+
65
+ const slide: Record<DrawerSide, string> = {
66
+ left: 'animate-[drawer-left_220ms_cubic-bezier(0,0,0.2,1)]',
67
+ right: 'animate-[drawer-right_220ms_cubic-bezier(0,0,0.2,1)]',
68
+ top: 'animate-[drawer-top_220ms_cubic-bezier(0,0,0.2,1)]',
69
+ bottom: 'animate-[drawer-bottom_220ms_cubic-bezier(0,0,0.2,1)]'
70
+ };
71
+
72
+ $effect(() => syncDialog(dialog, open));
73
+
74
+ function close() {
75
+ open = false;
76
+ onclose?.();
77
+ }
78
+ </script>
79
+
80
+ <dialog
81
+ bind:this={dialog}
82
+ onclose={close}
83
+ onclick={(event) => {
84
+ if (dismissible && isBackdropClick(event, dialog)) close();
85
+ }}
86
+ oncancel={(event) => {
87
+ if (!dismissible) event.preventDefault();
88
+ }}
89
+ class="max-h-none max-w-none border-hairline bg-bg p-0 text-text {float} backdrop:bg-text/30 backdrop:backdrop-blur-[1px]
90
+ {sides[side]} {horizontal ? widths[size] : heights[size]} {slide[side]} {className}"
91
+ {...rest}
92
+ >
93
+ <div class="flex h-full flex-col">
94
+ {#if title || dismissible}
95
+ <div class="flex items-start gap-4 border-b border-hairline px-5 py-4">
96
+ <div class="flex min-w-0 flex-1 flex-col gap-1">
97
+ {#if title}
98
+ <h2 class="font-heading text-base font-semibold text-text">{title}</h2>
99
+ {/if}
100
+ {#if description}
101
+ <p class="font-sans text-[13px] text-text-muted">{description}</p>
102
+ {/if}
103
+ </div>
104
+ {#if dismissible}
105
+ <button
106
+ type="button"
107
+ onclick={close}
108
+ aria-label={t.current.close}
109
+ class="-m-1 shrink-0 rounded-xl p-1 text-text-muted transition-colors duration-150 hover:bg-bg-inset hover:text-text focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
110
+ >
111
+ <svg class="size-4" viewBox="0 0 14 14" fill="none" aria-hidden="true">
112
+ <path
113
+ d="m2 2 10 10M12 2 2 12"
114
+ stroke="currentColor"
115
+ stroke-width="1.6"
116
+ stroke-linecap="round"
117
+ />
118
+ </svg>
119
+ </button>
120
+ {/if}
121
+ </div>
122
+ {/if}
123
+
124
+ <div
125
+ class="flex-1 overflow-y-auto px-5 py-4 font-sans text-sm leading-relaxed text-text-secondary"
126
+ >
127
+ {@render children()}
128
+ </div>
129
+
130
+ {#if footer}
131
+ <div
132
+ class="flex items-center justify-end gap-2 border-t border-hairline bg-bg-alt px-5 py-3.5"
133
+ >
134
+ {@render footer()}
135
+ </div>
136
+ {/if}
137
+ </div>
138
+ </dialog>
139
+
140
+ <style>
141
+ @keyframes -global-drawer-right {
142
+ from {
143
+ transform: translateX(100%);
144
+ }
145
+ }
146
+ @keyframes -global-drawer-left {
147
+ from {
148
+ transform: translateX(-100%);
149
+ }
150
+ }
151
+ @keyframes -global-drawer-top {
152
+ from {
153
+ transform: translateY(-100%);
154
+ }
155
+ }
156
+ @keyframes -global-drawer-bottom {
157
+ from {
158
+ transform: translateY(100%);
159
+ }
160
+ }
161
+ @media (prefers-reduced-motion: reduce) {
162
+ dialog {
163
+ animation: none;
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLDialogAttributes } from 'svelte/elements';
3
+ export type DrawerSide = 'left' | 'right' | 'top' | 'bottom';
4
+ export type DrawerSize = 'sm' | 'md' | 'lg' | 'full';
5
+ interface Props extends HTMLDialogAttributes {
6
+ /** Bindable. Built on the native `<dialog>`, so it gets the top layer,
7
+ * the backdrop, Escape handling and focus containment for free. */
8
+ open?: boolean;
9
+ side?: DrawerSide;
10
+ size?: DrawerSize;
11
+ title?: string;
12
+ description?: string;
13
+ dismissible?: boolean;
14
+ footer?: Snippet;
15
+ onclose?: () => void;
16
+ children: Snippet;
17
+ }
18
+ declare const Drawer: import("svelte").Component<Props, {}, "open">;
19
+ type Drawer = ReturnType<typeof Drawer>;
20
+ export default Drawer;
@@ -0,0 +1,95 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { anchored, type Placement } from '../../core/actions/anchor.js';
4
+ import { clickOutside, focusTrap, navigateList, portal } from '../../core/actions/dismissable.js';
5
+ import { describeTrigger, menuItems, popupTrigger } from '../../core/trigger.js';
6
+ import { useLocale } from '../../core/locale.svelte.js';
7
+ import { soft } from './lift.js';
8
+
9
+ interface Props {
10
+ /** The control that opens the menu — put a `Button` in here. */
11
+ trigger: Snippet;
12
+ /** Menu contents: `MenuItem`, `MenuSeparator`, or anything else. */
13
+ children: Snippet;
14
+ open?: boolean;
15
+ placement?: Placement;
16
+ offset?: number;
17
+ /** Stretch the menu to the trigger's width. */
18
+ matchWidth?: boolean;
19
+ /** Close as soon as an item inside is activated. Default true. */
20
+ closeOnSelect?: boolean;
21
+ /** Accessible name for the menu. Defaults to the locale's `menu`. */
22
+ label?: string;
23
+ class?: string;
24
+ /** Classes for the wrapper around the trigger. */
25
+ triggerClass?: string;
26
+ }
27
+
28
+ let {
29
+ trigger,
30
+ children,
31
+ open = $bindable(false),
32
+ placement = 'bottom-start',
33
+ offset = 6,
34
+ matchWidth = false,
35
+ closeOnSelect = true,
36
+ label,
37
+ class: className = '',
38
+ triggerClass = ''
39
+ }: Props = $props();
40
+
41
+ const t = useLocale();
42
+
43
+ let anchorEl: HTMLElement | null = $state(null);
44
+ let menuEl: HTMLElement | null = $state(null);
45
+
46
+ // The ARIA state goes on the consumer's own control, not on our wrapper.
47
+ $effect(() => describeTrigger(anchorEl, 'menu', open));
48
+
49
+ function onMenuKeydown(event: KeyboardEvent) {
50
+ if (event.key === 'Escape' || event.key === 'Tab') {
51
+ if (event.key === 'Escape') event.preventDefault();
52
+ event.stopPropagation();
53
+ open = false;
54
+ return;
55
+ }
56
+ navigateList(event, menuItems(menuEl));
57
+ }
58
+
59
+ function onMenuClick(event: MouseEvent) {
60
+ if (!closeOnSelect) return;
61
+ if ((event.target as HTMLElement).closest('[role="menuitem"]:not([disabled])')) open = false;
62
+ }
63
+ </script>
64
+
65
+ <span
66
+ bind:this={anchorEl}
67
+ use:popupTrigger={{
68
+ toggle: () => (open = !open),
69
+ open: () => (open = true),
70
+ close: () => (open = false),
71
+ arrowOpens: true,
72
+ isOpen: () => open
73
+ }}
74
+ class="inline-flex {triggerClass}"
75
+ >
76
+ {@render trigger()}
77
+ </span>
78
+
79
+ {#if open}
80
+ <div
81
+ bind:this={menuEl}
82
+ use:portal
83
+ use:anchored={{ anchor: anchorEl, placement, offset, matchWidth }}
84
+ use:clickOutside={{ onoutside: () => (open = false), ignore: [anchorEl] }}
85
+ use:focusTrap={{ restore: true }}
86
+ onkeydown={onMenuKeydown}
87
+ onclick={onMenuClick}
88
+ role="menu"
89
+ aria-label={label ?? t.current.menu}
90
+ tabindex="-1"
91
+ class="z-50 flex max-h-[min(24rem,80vh)] min-w-48 flex-col overflow-y-auto rounded-2xl border border-hairline bg-bg py-1 font-sans {soft} {className}"
92
+ >
93
+ {@render children()}
94
+ </div>
95
+ {/if}
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type Placement } from '../../core/actions/anchor.js';
3
+ interface Props {
4
+ /** The control that opens the menu — put a `Button` in here. */
5
+ trigger: Snippet;
6
+ /** Menu contents: `MenuItem`, `MenuSeparator`, or anything else. */
7
+ children: Snippet;
8
+ open?: boolean;
9
+ placement?: Placement;
10
+ offset?: number;
11
+ /** Stretch the menu to the trigger's width. */
12
+ matchWidth?: boolean;
13
+ /** Close as soon as an item inside is activated. Default true. */
14
+ closeOnSelect?: boolean;
15
+ /** Accessible name for the menu. Defaults to the locale's `menu`. */
16
+ label?: string;
17
+ class?: string;
18
+ /** Classes for the wrapper around the trigger. */
19
+ triggerClass?: string;
20
+ }
21
+ declare const Dropdown: import("svelte").Component<Props, {}, "open">;
22
+ type Dropdown = ReturnType<typeof Dropdown>;
23
+ export default Dropdown;