@noahbaumgartner/svelte-ui 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -13
  3. package/dist/components/accordion/Accordion.svelte +44 -0
  4. package/dist/components/accordion/Accordion.svelte.d.ts +13 -0
  5. package/dist/components/accordion/AccordionItem.svelte +145 -0
  6. package/dist/components/accordion/AccordionItem.svelte.d.ts +17 -0
  7. package/dist/components/accordion/context.d.ts +5 -0
  8. package/dist/components/accordion/context.js +2 -0
  9. package/dist/components/alert-dialog/AlertDialog.svelte +108 -76
  10. package/dist/components/alert-dialog/AlertDialog.svelte.d.ts +5 -2
  11. package/dist/components/avatar/Avatar.svelte +71 -49
  12. package/dist/components/avatar/Avatar.svelte.d.ts +5 -2
  13. package/dist/components/avatar-group/AvatarGroup.svelte +103 -0
  14. package/dist/components/avatar-group/AvatarGroup.svelte.d.ts +19 -0
  15. package/dist/components/badge/Badge.svelte +50 -28
  16. package/dist/components/badge/Badge.svelte.d.ts +6 -4
  17. package/dist/components/breadcrumb/Breadcrumb.svelte +21 -17
  18. package/dist/components/breadcrumb/Breadcrumb.svelte.d.ts +3 -1
  19. package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte +17 -6
  20. package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte.d.ts +5 -2
  21. package/dist/components/breadcrumb/BreadcrumbItem.svelte +75 -36
  22. package/dist/components/breadcrumb/BreadcrumbItem.svelte.d.ts +8 -5
  23. package/dist/components/breadcrumb/BreadcrumbSeparator.svelte +32 -9
  24. package/dist/components/breadcrumb/BreadcrumbSeparator.svelte.d.ts +10 -17
  25. package/dist/components/button/Button.svelte +206 -168
  26. package/dist/components/button/Button.svelte.d.ts +9 -5
  27. package/dist/components/button-group/ButtonGroup.svelte +52 -55
  28. package/dist/components/button-group/ButtonGroup.svelte.d.ts +3 -1
  29. package/dist/components/calendar/Calendar.svelte +232 -144
  30. package/dist/components/calendar/Calendar.svelte.d.ts +19 -2
  31. package/dist/components/calendar/dates.d.ts +4 -1
  32. package/dist/components/calendar/dates.js +8 -2
  33. package/dist/components/card/Card.svelte +48 -32
  34. package/dist/components/card/Card.svelte.d.ts +3 -1
  35. package/dist/components/card/CardAction.svelte +11 -7
  36. package/dist/components/card/CardAction.svelte.d.ts +3 -1
  37. package/dist/components/card/CardContent.svelte +8 -4
  38. package/dist/components/card/CardContent.svelte.d.ts +3 -1
  39. package/dist/components/card/CardDescription.svelte +11 -7
  40. package/dist/components/card/CardDescription.svelte.d.ts +3 -1
  41. package/dist/components/card/CardFooter.svelte +15 -11
  42. package/dist/components/card/CardFooter.svelte.d.ts +3 -1
  43. package/dist/components/card/CardHeader.svelte +15 -11
  44. package/dist/components/card/CardHeader.svelte.d.ts +3 -1
  45. package/dist/components/card/CardTitle.svelte +11 -7
  46. package/dist/components/card/CardTitle.svelte.d.ts +3 -1
  47. package/dist/components/checkbox/Checkbox.svelte +152 -61
  48. package/dist/components/checkbox/Checkbox.svelte.d.ts +5 -2
  49. package/dist/components/color-picker/ColorPicker.svelte +131 -78
  50. package/dist/components/color-picker/ColorPicker.svelte.d.ts +10 -1
  51. package/dist/components/context-menu/ContextMenu.svelte +78 -61
  52. package/dist/components/context-menu/ContextMenu.svelte.d.ts +5 -2
  53. package/dist/components/context-menu/ContextMenuInfo.svelte +91 -0
  54. package/dist/components/context-menu/ContextMenuInfo.svelte.d.ts +19 -0
  55. package/dist/components/context-menu/ContextMenuItem.svelte +102 -57
  56. package/dist/components/context-menu/ContextMenuItem.svelte.d.ts +10 -5
  57. package/dist/components/context-menu/ContextMenuLabel.svelte +28 -0
  58. package/dist/components/context-menu/ContextMenuLabel.svelte.d.ts +10 -0
  59. package/dist/components/context-menu/ContextMenuSeparator.svelte +29 -0
  60. package/dist/components/context-menu/ContextMenuSeparator.svelte.d.ts +8 -0
  61. package/dist/components/dialog/Dialog.svelte +149 -108
  62. package/dist/components/dialog/Dialog.svelte.d.ts +7 -2
  63. package/dist/components/drawer/Drawer.svelte +419 -0
  64. package/dist/components/drawer/Drawer.svelte.d.ts +28 -0
  65. package/dist/components/empty-state/EmptyState.svelte +106 -0
  66. package/dist/components/empty-state/EmptyState.svelte.d.ts +19 -0
  67. package/dist/components/entry/Entry.svelte +94 -0
  68. package/dist/components/entry/Entry.svelte.d.ts +16 -0
  69. package/dist/components/field/Field.svelte +66 -45
  70. package/dist/components/field/Field.svelte.d.ts +2 -1
  71. package/dist/components/field/FieldContent.svelte +11 -8
  72. package/dist/components/field/FieldContent.svelte.d.ts +2 -1
  73. package/dist/components/field/FieldDescription.svelte +18 -15
  74. package/dist/components/field/FieldDescription.svelte.d.ts +2 -1
  75. package/dist/components/field/FieldError.svelte +22 -13
  76. package/dist/components/field/FieldError.svelte.d.ts +2 -1
  77. package/dist/components/field/FieldGroup.svelte +15 -12
  78. package/dist/components/field/FieldGroup.svelte.d.ts +2 -1
  79. package/dist/components/field/FieldLegend.svelte +23 -14
  80. package/dist/components/field/FieldLegend.svelte.d.ts +2 -1
  81. package/dist/components/field/FieldSeparator.svelte +12 -9
  82. package/dist/components/field/FieldSeparator.svelte.d.ts +2 -1
  83. package/dist/components/field/FieldSet.svelte +18 -15
  84. package/dist/components/field/FieldSet.svelte.d.ts +2 -1
  85. package/dist/components/footer/Footer.svelte +105 -0
  86. package/dist/components/footer/Footer.svelte.d.ts +14 -0
  87. package/dist/components/footer/FooterColumn.svelte +37 -0
  88. package/dist/components/footer/FooterColumn.svelte.d.ts +11 -0
  89. package/dist/components/image/Image.svelte +61 -0
  90. package/dist/components/image/Image.svelte.d.ts +18 -0
  91. package/dist/components/input/Input.svelte +307 -208
  92. package/dist/components/input/Input.svelte.d.ts +12 -1
  93. package/dist/components/input-group/InputGroup.svelte +101 -61
  94. package/dist/components/input-group/InputGroup.svelte.d.ts +2 -1
  95. package/dist/components/input-group/InputGroupAddon.svelte +58 -49
  96. package/dist/components/kbd/Kbd.svelte +39 -30
  97. package/dist/components/kbd/Kbd.svelte.d.ts +2 -1
  98. package/dist/components/kbd/KbdGroup.svelte +15 -12
  99. package/dist/components/kbd/KbdGroup.svelte.d.ts +2 -1
  100. package/dist/components/label/Label.svelte +47 -28
  101. package/dist/components/label/Label.svelte.d.ts +2 -1
  102. package/dist/components/link/Link.svelte +71 -17
  103. package/dist/components/link/Link.svelte.d.ts +2 -1
  104. package/dist/components/mesh-gradient/MeshGradient.svelte +97 -0
  105. package/dist/components/mesh-gradient/MeshGradient.svelte.d.ts +11 -0
  106. package/dist/components/navbar/Navbar.svelte +137 -99
  107. package/dist/components/navbar/Navbar.svelte.d.ts +4 -2
  108. package/dist/components/navigation-menu/NavigationMenu.svelte +37 -20
  109. package/dist/components/navigation-menu/NavigationMenu.svelte.d.ts +4 -2
  110. package/dist/components/navigation-menu/NavigationMenuItem.svelte +131 -89
  111. package/dist/components/navigation-menu/NavigationMenuItem.svelte.d.ts +4 -2
  112. package/dist/components/navigation-menu/NavigationMenuLink.svelte +90 -51
  113. package/dist/components/navigation-menu/NavigationMenuLink.svelte.d.ts +7 -5
  114. package/dist/components/number-stepper/NumberStepper.svelte +101 -0
  115. package/dist/components/number-stepper/NumberStepper.svelte.d.ts +21 -0
  116. package/dist/components/popover/Popover.svelte +85 -65
  117. package/dist/components/popover/Popover.svelte.d.ts +5 -2
  118. package/dist/components/preview-card/PreviewCard.svelte +184 -0
  119. package/dist/components/preview-card/PreviewCard.svelte.d.ts +23 -0
  120. package/dist/components/progress/Progress.svelte +111 -0
  121. package/dist/components/progress/Progress.svelte.d.ts +13 -0
  122. package/dist/components/radio-group/RadioGroup.svelte +24 -19
  123. package/dist/components/radio-group/RadioGroup.svelte.d.ts +2 -1
  124. package/dist/components/radio-group/RadioGroupItem.svelte +101 -54
  125. package/dist/components/radio-group/RadioGroupItem.svelte.d.ts +2 -1
  126. package/dist/components/section/Section.svelte +114 -0
  127. package/dist/components/section/Section.svelte.d.ts +22 -0
  128. package/dist/components/select/Select.svelte +474 -0
  129. package/dist/components/select/Select.svelte.d.ts +48 -0
  130. package/dist/components/separator/Separator.svelte +21 -10
  131. package/dist/components/separator/Separator.svelte.d.ts +2 -1
  132. package/dist/components/sidebar/Sidebar.svelte +158 -87
  133. package/dist/components/sidebar/Sidebar.svelte.d.ts +6 -1
  134. package/dist/components/sidebar/SidebarContent.svelte +15 -12
  135. package/dist/components/sidebar/SidebarContent.svelte.d.ts +2 -1
  136. package/dist/components/sidebar/SidebarFooter.svelte +10 -7
  137. package/dist/components/sidebar/SidebarFooter.svelte.d.ts +2 -1
  138. package/dist/components/sidebar/SidebarGroup.svelte +35 -25
  139. package/dist/components/sidebar/SidebarGroup.svelte.d.ts +2 -1
  140. package/dist/components/sidebar/SidebarHeader.svelte +37 -8
  141. package/dist/components/sidebar/SidebarHeader.svelte.d.ts +6 -1
  142. package/dist/components/sidebar/SidebarInset.svelte +11 -8
  143. package/dist/components/sidebar/SidebarInset.svelte.d.ts +2 -1
  144. package/dist/components/sidebar/SidebarMenu.svelte +13 -10
  145. package/dist/components/sidebar/SidebarMenu.svelte.d.ts +2 -1
  146. package/dist/components/sidebar/SidebarMenuItem.svelte +195 -114
  147. package/dist/components/sidebar/SidebarMenuItem.svelte.d.ts +11 -5
  148. package/dist/components/sidebar/SidebarProvider.svelte +18 -9
  149. package/dist/components/sidebar/SidebarProvider.svelte.d.ts +2 -1
  150. package/dist/components/sidebar/SidebarSeparator.svelte +6 -1
  151. package/dist/components/sidebar/SidebarSeparator.svelte.d.ts +6 -17
  152. package/dist/components/sidebar/SidebarTrigger.svelte +17 -5
  153. package/dist/components/sidebar/SidebarTrigger.svelte.d.ts +4 -2
  154. package/dist/components/skeleton/Skeleton.svelte +26 -16
  155. package/dist/components/skeleton/Skeleton.svelte.d.ts +4 -2
  156. package/dist/components/spinner/Spinner.svelte +71 -46
  157. package/dist/components/spinner/Spinner.svelte.d.ts +2 -1
  158. package/dist/components/switch/Switch.svelte +126 -65
  159. package/dist/components/switch/Switch.svelte.d.ts +4 -2
  160. package/dist/components/table/Table.svelte +135 -76
  161. package/dist/components/table/Table.svelte.d.ts +12 -2
  162. package/dist/components/tabs/Tabs.svelte +337 -0
  163. package/dist/components/tabs/Tabs.svelte.d.ts +50 -0
  164. package/dist/components/textarea/Textarea.svelte +76 -42
  165. package/dist/components/textarea/Textarea.svelte.d.ts +4 -1
  166. package/dist/components/time-picker/TimePicker.svelte +214 -85
  167. package/dist/components/time-picker/TimePicker.svelte.d.ts +18 -1
  168. package/dist/components/toaster/Toaster.svelte +132 -70
  169. package/dist/components/toaster/Toaster.svelte.d.ts +18 -2
  170. package/dist/components/toaster/toast.svelte.d.ts +3 -3
  171. package/dist/components/toggle/Toggle.svelte +25 -4
  172. package/dist/components/toggle/Toggle.svelte.d.ts +1 -1
  173. package/dist/components/tooltip/Tooltip.svelte +87 -58
  174. package/dist/components/tooltip/Tooltip.svelte.d.ts +4 -2
  175. package/dist/icon.d.ts +12 -0
  176. package/dist/icon.js +1 -0
  177. package/dist/index.d.ts +21 -0
  178. package/dist/index.js +19 -0
  179. package/dist/internal/icons/ArrowDown.svelte +14 -0
  180. package/dist/internal/icons/ArrowDown.svelte.d.ts +4 -0
  181. package/dist/internal/icons/ArrowUp.svelte +14 -0
  182. package/dist/internal/icons/ArrowUp.svelte.d.ts +4 -0
  183. package/dist/internal/icons/Calendar.svelte +16 -0
  184. package/dist/internal/icons/Calendar.svelte.d.ts +4 -0
  185. package/dist/internal/icons/Check.svelte +8 -0
  186. package/dist/internal/icons/Check.svelte.d.ts +4 -0
  187. package/dist/internal/icons/ChevronDown.svelte +8 -0
  188. package/dist/internal/icons/ChevronDown.svelte.d.ts +4 -0
  189. package/dist/internal/icons/ChevronLeft.svelte +8 -0
  190. package/dist/internal/icons/ChevronLeft.svelte.d.ts +4 -0
  191. package/dist/internal/icons/ChevronRight.svelte +8 -0
  192. package/dist/internal/icons/ChevronRight.svelte.d.ts +4 -0
  193. package/dist/internal/icons/ChevronUp.svelte +8 -0
  194. package/dist/internal/icons/ChevronUp.svelte.d.ts +4 -0
  195. package/dist/internal/icons/ChevronsUpDown.svelte +14 -0
  196. package/dist/internal/icons/ChevronsUpDown.svelte.d.ts +4 -0
  197. package/dist/internal/icons/Clock.svelte +14 -0
  198. package/dist/internal/icons/Clock.svelte.d.ts +4 -0
  199. package/dist/internal/icons/Ellipsis.svelte +15 -0
  200. package/dist/internal/icons/Ellipsis.svelte.d.ts +4 -0
  201. package/dist/internal/icons/Icon.svelte +33 -0
  202. package/dist/internal/icons/Icon.svelte.d.ts +8 -0
  203. package/dist/internal/icons/Minus.svelte +8 -0
  204. package/dist/internal/icons/Minus.svelte.d.ts +4 -0
  205. package/dist/internal/icons/PanelLeft.svelte +14 -0
  206. package/dist/internal/icons/PanelLeft.svelte.d.ts +4 -0
  207. package/dist/internal/icons/Plus.svelte +14 -0
  208. package/dist/internal/icons/Plus.svelte.d.ts +4 -0
  209. package/dist/internal/icons/User.svelte +14 -0
  210. package/dist/internal/icons/User.svelte.d.ts +4 -0
  211. package/dist/internal/icons/X.svelte +14 -0
  212. package/dist/internal/icons/X.svelte.d.ts +4 -0
  213. package/dist/styles/tokens.css +39 -41
  214. package/package.json +5 -10
  215. package/dist/icons.d.ts +0 -2
  216. package/dist/icons.js +0 -4
@@ -0,0 +1,419 @@
1
+ <script module lang="ts">
2
+ export type DrawerTriggerProps = {
3
+ onclick: () => void;
4
+ 'aria-haspopup': 'dialog';
5
+ };
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ import type { Snippet } from 'svelte';
10
+ import type { HTMLDialogAttributes } from 'svelte/elements';
11
+ import X from '../../internal/icons/X.svelte';
12
+
13
+ type Props = Omit<
14
+ HTMLDialogAttributes,
15
+ 'open' | 'title' | 'children' | 'aria-labelledby' | 'aria-describedby'
16
+ > & {
17
+ /** The dialog element. Bindable. */
18
+ ref?: HTMLDialogElement | null;
19
+ /**
20
+ * Whether the drawer is open. Bindable. A sheet from the bottom on phones, a panel on the right from 768px up.
21
+ * Escape, the handle or close button, dragging it away and a click on the backdrop close it.
22
+ */
23
+ open?: boolean;
24
+ title: string;
25
+ description?: string;
26
+ /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. Omit to control `open` yourself. */
27
+ trigger?: Snippet<[DrawerTriggerProps]>;
28
+ /** The drawer's content; scrolls when it is taller than the screen. The first focusable element in it gets the focus on open. */
29
+ children?: Snippet;
30
+ /** Optional Buttons in the footer, stacked at full width with the primary one last; it ends up on top. Call `close` to close the drawer. */
31
+ actions?: Snippet<[close: () => void]>;
32
+ /** Accessible name of the handle and close button. */
33
+ closeLabel?: string;
34
+ };
35
+
36
+ let {
37
+ ref = $bindable(null),
38
+ open = $bindable(false),
39
+ title,
40
+ description,
41
+ trigger,
42
+ children,
43
+ actions,
44
+ closeLabel = 'Close',
45
+ class: className,
46
+ style,
47
+ ...rest
48
+ }: Props = $props();
49
+
50
+ const id = $props.id();
51
+
52
+ const triggerProps: DrawerTriggerProps = {
53
+ onclick: () => (open = true),
54
+ 'aria-haspopup': 'dialog'
55
+ };
56
+
57
+ function close() {
58
+ open = false;
59
+ }
60
+
61
+ $effect(() => {
62
+ if (!ref) return;
63
+ if (open && !ref.open) {
64
+ ref.showModal();
65
+ } else if (!open && ref.open) {
66
+ ref.close();
67
+ }
68
+ });
69
+
70
+ /* Dragging the header towards the edge moves the drawer along; letting go far enough or fast enough closes it */
71
+ const slop = 4;
72
+ let offset = $state(0);
73
+ let dragging = $state(false);
74
+ /* Matches the media query below: the panel on the right leaves sideways */
75
+ let sideways = $state(false);
76
+ let dragged = false;
77
+
78
+ function startDrag(event: PointerEvent) {
79
+ if (event.button !== 0) return;
80
+ sideways = window.matchMedia('(min-width: 768px)').matches;
81
+ const start = sideways ? event.clientX : event.clientY;
82
+ const startTime = event.timeStamp;
83
+
84
+ function move(e: PointerEvent) {
85
+ const distance = (sideways ? e.clientX : e.clientY) - start;
86
+ if (!dragging && Math.abs(distance) < slop) return;
87
+ dragging = true;
88
+ dragged = true;
89
+ offset = Math.max(0, distance);
90
+ }
91
+
92
+ function end(e: PointerEvent) {
93
+ window.removeEventListener('pointermove', move);
94
+ window.removeEventListener('pointerup', end);
95
+ window.removeEventListener('pointercancel', end);
96
+ if (!dragging) return;
97
+ const velocity = offset / Math.max(1, e.timeStamp - startTime);
98
+ const dismiss =
99
+ e.type === 'pointerup' &&
100
+ (offset > (sideways ? (ref?.offsetWidth ?? 0) : (ref?.offsetHeight ?? 0)) / 3 ||
101
+ velocity > 0.5);
102
+ dragging = false;
103
+ offset = 0;
104
+ if (dismiss) close();
105
+ // The click that follows a drag ending on the handle must not count as a tap on it
106
+ setTimeout(() => (dragged = false));
107
+ }
108
+
109
+ window.addEventListener('pointermove', move);
110
+ window.addEventListener('pointerup', end);
111
+ window.addEventListener('pointercancel', end);
112
+ }
113
+ </script>
114
+
115
+ {@render trigger?.(triggerProps)}
116
+
117
+ <dialog
118
+ {...rest}
119
+ bind:this={ref}
120
+ aria-labelledby="{id}-title"
121
+ aria-describedby={description ? `${id}-description` : undefined}
122
+ class={['drawer', { 'drawer--dragging': dragging }, className]}
123
+ {style}
124
+ style:translate={dragging ? (sideways ? `${offset}px 0` : `0 ${offset}px`) : undefined}
125
+ onkeydown={(event) => {
126
+ if (event.key !== 'Escape') return;
127
+ event.preventDefault();
128
+ close();
129
+ }}
130
+ oncancel={(event) => {
131
+ event.preventDefault();
132
+ close();
133
+ }}
134
+ onclose={() => (open = false)}
135
+ onclick={(event) => {
136
+ // Clicks on the ::backdrop target the <dialog> itself, outside its box
137
+ if (event.target !== ref || !ref) return;
138
+ const rect = ref.getBoundingClientRect();
139
+ const inside =
140
+ event.clientX >= rect.left &&
141
+ event.clientX <= rect.right &&
142
+ event.clientY >= rect.top &&
143
+ event.clientY <= rect.bottom;
144
+ if (!inside) close();
145
+ }}
146
+ >
147
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
148
+ <div class="drawer-header" onpointerdown={startDrag}>
149
+ <h2 id="{id}-title" class="drawer-title">{title}</h2>
150
+ {#if description}
151
+ <p id="{id}-description" class="drawer-description">{description}</p>
152
+ {/if}
153
+ </div>
154
+ {#if children}
155
+ <div class="drawer-body">{@render children()}</div>
156
+ {/if}
157
+ {#if actions}
158
+ <div class="drawer-actions">{@render actions(close)}</div>
159
+ {/if}
160
+ <!-- Last in the DOM so the content, not the handle, gets the initial focus -->
161
+ <button
162
+ type="button"
163
+ class="drawer-handle"
164
+ aria-label={closeLabel}
165
+ onpointerdown={startDrag}
166
+ onclick={() => {
167
+ if (!dragged) close();
168
+ }}
169
+ >
170
+ <span class="drawer-handle-bar"></span>
171
+ <X class="drawer-handle-icon" aria-hidden="true" />
172
+ </button>
173
+ </dialog>
174
+
175
+ <style>
176
+ @layer svelte-ui {
177
+ /* Locks page scrolling while open; the gutter keeps the layout from shifting */
178
+ :global(html:has(.drawer[open])) {
179
+ overflow: hidden;
180
+ scrollbar-gutter: stable;
181
+ }
182
+
183
+ .drawer {
184
+ box-sizing: border-box;
185
+ width: 100%;
186
+ max-width: 560px;
187
+ max-height: calc(100dvh - 48px);
188
+ flex-direction: column;
189
+ /* Pinned to the bottom edge */
190
+ margin: auto auto 0;
191
+ padding: 0 0 env(safe-area-inset-bottom);
192
+ overflow: hidden;
193
+ background-color: var(--color-bg);
194
+ color: var(--color-text);
195
+ border: 1px solid var(--color-border);
196
+ border-bottom: 0;
197
+ border-radius: 16px 16px 0 0;
198
+ box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.12);
199
+ outline: none;
200
+ translate: 0 100%;
201
+ transition:
202
+ translate 260ms cubic-bezier(0.32, 0.72, 0, 1),
203
+ display 260ms allow-discrete,
204
+ overlay 260ms allow-discrete;
205
+ }
206
+
207
+ .drawer[open] {
208
+ display: flex;
209
+ translate: 0 0;
210
+ }
211
+
212
+ /* Follows the finger directly */
213
+ .drawer--dragging {
214
+ transition: none;
215
+ }
216
+
217
+ .drawer::backdrop {
218
+ background-color: rgba(0, 0, 0, 0);
219
+ transition:
220
+ background-color 260ms ease,
221
+ display 260ms allow-discrete,
222
+ overlay 260ms allow-discrete;
223
+ }
224
+
225
+ .drawer[open]::backdrop {
226
+ background-color: rgba(0, 0, 0, 0.4);
227
+ }
228
+
229
+ @starting-style {
230
+ .drawer[open] {
231
+ translate: 0 100%;
232
+ }
233
+
234
+ .drawer[open]::backdrop {
235
+ background-color: rgba(0, 0, 0, 0);
236
+ }
237
+ }
238
+
239
+ @media (prefers-reduced-motion: reduce) {
240
+ .drawer,
241
+ .drawer::backdrop,
242
+ .drawer-handle-bar {
243
+ transition: none;
244
+ }
245
+ }
246
+
247
+ @media (forced-colors: active) {
248
+ .drawer {
249
+ border: 1px solid CanvasText;
250
+ }
251
+
252
+ .drawer-handle-bar {
253
+ background-color: ButtonText;
254
+ }
255
+
256
+ .drawer-handle:focus-visible {
257
+ outline-color: Highlight;
258
+ }
259
+ }
260
+
261
+ @media (pointer: coarse) {
262
+ .drawer-handle {
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ box-sizing: border-box;
267
+ min-width: 44px;
268
+ min-height: 44px;
269
+ }
270
+ }
271
+
272
+ .drawer-header {
273
+ flex-shrink: 0;
274
+ /* Room for the handle */
275
+ padding: 28px 20px 12px;
276
+ cursor: grab;
277
+ user-select: none;
278
+ /* Dragging must not scroll the page or get cancelled by the browser */
279
+ touch-action: none;
280
+ }
281
+
282
+ .drawer--dragging .drawer-header,
283
+ .drawer--dragging .drawer-handle {
284
+ cursor: grabbing;
285
+ }
286
+
287
+ .drawer-title {
288
+ margin: 0;
289
+ font-size: 15px;
290
+ font-weight: 600;
291
+ }
292
+
293
+ .drawer-description {
294
+ margin: 6px 0 0;
295
+ font-size: 13px;
296
+ line-height: 1.5;
297
+ color: var(--color-text-muted);
298
+ }
299
+
300
+ .drawer-body {
301
+ min-height: 0;
302
+ padding: 4px 20px 20px;
303
+ overflow-y: auto;
304
+ overscroll-behavior: contain;
305
+ font-size: 13px;
306
+ }
307
+
308
+ /* Stacked, the primary action (last in the markup) on top */
309
+ .drawer-actions {
310
+ display: flex;
311
+ flex-shrink: 0;
312
+ flex-direction: column-reverse;
313
+ gap: 8px;
314
+ padding: 4px 20px 20px;
315
+ }
316
+
317
+ .drawer-actions :global(.button) {
318
+ width: 100%;
319
+ }
320
+
321
+ .drawer-handle {
322
+ position: absolute;
323
+ top: 0;
324
+ left: 50%;
325
+ padding: 10px 24px;
326
+ background: none;
327
+ border: 0;
328
+ border-radius: 8px;
329
+ cursor: grab;
330
+ translate: -50% 0;
331
+ touch-action: none;
332
+ -webkit-tap-highlight-color: transparent;
333
+ }
334
+
335
+ .drawer-handle:focus-visible {
336
+ outline: 2px solid var(--color-accent);
337
+ outline-offset: -4px;
338
+ }
339
+
340
+ .drawer-handle-bar {
341
+ display: block;
342
+ width: 36px;
343
+ height: 4px;
344
+ background-color: var(--color-border-strong);
345
+ border-radius: 2px;
346
+ transition: background-color 120ms ease;
347
+ }
348
+
349
+ .drawer-handle:hover .drawer-handle-bar {
350
+ background-color: var(--color-text-faint);
351
+ }
352
+
353
+ .drawer-handle :global(.drawer-handle-icon) {
354
+ display: none;
355
+ width: 16px;
356
+ height: 16px;
357
+ }
358
+
359
+ /* Larger screens: a floating panel on the right, with a close button instead of the handle */
360
+ @media (min-width: 768px) {
361
+ .drawer {
362
+ width: 400px;
363
+ max-width: calc(100% - 48px);
364
+ height: calc(100dvh - 2 * var(--drawer-inset));
365
+ max-height: none;
366
+ /* Floats with a gap to the screen edges */
367
+ --drawer-inset: 8px;
368
+ margin: var(--drawer-inset) var(--drawer-inset) var(--drawer-inset) auto;
369
+ padding: 0;
370
+ border: 1px solid var(--color-border);
371
+ border-radius: 20px;
372
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
373
+ translate: calc(100% + var(--drawer-inset)) 0;
374
+ }
375
+
376
+ @starting-style {
377
+ .drawer[open] {
378
+ translate: calc(100% + var(--drawer-inset)) 0;
379
+ }
380
+ }
381
+
382
+ .drawer-header {
383
+ padding: 20px 56px 12px 20px;
384
+ }
385
+
386
+ /* Pushes the actions to the bottom */
387
+ .drawer-body {
388
+ flex: 1;
389
+ }
390
+
391
+ .drawer-handle {
392
+ top: 14px;
393
+ right: 14px;
394
+ left: auto;
395
+ padding: 6px;
396
+ color: var(--color-text-muted);
397
+ cursor: pointer;
398
+ translate: none;
399
+ }
400
+
401
+ .drawer-handle:hover {
402
+ color: var(--color-text);
403
+ background-color: var(--color-surface);
404
+ }
405
+
406
+ .drawer-handle:focus-visible {
407
+ outline-offset: 2px;
408
+ }
409
+
410
+ .drawer-handle-bar {
411
+ display: none;
412
+ }
413
+
414
+ .drawer-handle :global(.drawer-handle-icon) {
415
+ display: block;
416
+ }
417
+ }
418
+ }
419
+ </style>
@@ -0,0 +1,28 @@
1
+ export type DrawerTriggerProps = {
2
+ onclick: () => void;
3
+ 'aria-haspopup': 'dialog';
4
+ };
5
+ import type { Snippet } from 'svelte';
6
+ import type { HTMLDialogAttributes } from 'svelte/elements';
7
+ type Props = Omit<HTMLDialogAttributes, 'open' | 'title' | 'children' | 'aria-labelledby' | 'aria-describedby'> & {
8
+ /** The dialog element. Bindable. */
9
+ ref?: HTMLDialogElement | null;
10
+ /**
11
+ * Whether the drawer is open. Bindable. A sheet from the bottom on phones, a panel on the right from 768px up.
12
+ * Escape, the handle or close button, dragging it away and a click on the backdrop close it.
13
+ */
14
+ open?: boolean;
15
+ title: string;
16
+ description?: string;
17
+ /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. Omit to control `open` yourself. */
18
+ trigger?: Snippet<[DrawerTriggerProps]>;
19
+ /** The drawer's content; scrolls when it is taller than the screen. The first focusable element in it gets the focus on open. */
20
+ children?: Snippet;
21
+ /** Optional Buttons in the footer, stacked at full width with the primary one last; it ends up on top. Call `close` to close the drawer. */
22
+ actions?: Snippet<[close: () => void]>;
23
+ /** Accessible name of the handle and close button. */
24
+ closeLabel?: string;
25
+ };
26
+ declare const Drawer: import("svelte").Component<Props, {}, "ref" | "open">;
27
+ type Drawer = ReturnType<typeof Drawer>;
28
+ export default Drawer;
@@ -0,0 +1,106 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import type { IconComponent } from '../../icon.js';
5
+
6
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
7
+ /** Any icon component, e.g. from `@lucide/svelte`. */
8
+ icon?: IconComponent;
9
+ title: string;
10
+ description?: string;
11
+ /** `outline` has a dashed border, for empty areas that can be filled. */
12
+ variant?: 'secondary' | 'outline';
13
+ /** Optional actions below the text, e.g. a Button that creates the first entry. */
14
+ children?: Snippet;
15
+ /** Custom media instead of `icon`. */
16
+ media?: Snippet;
17
+ ref?: HTMLDivElement | null;
18
+ };
19
+
20
+ let {
21
+ icon: Icon,
22
+ title,
23
+ description,
24
+ variant = 'secondary',
25
+ class: className,
26
+ children,
27
+ media,
28
+ ref = $bindable(null),
29
+ ...rest
30
+ }: Props = $props();
31
+ </script>
32
+
33
+ <div {...rest} bind:this={ref} class={['empty-state', `empty-state--${variant}`, className]}>
34
+ {#if media}{@render media()}{:else if Icon}<Icon
35
+ class="empty-state-icon"
36
+ aria-hidden="true"
37
+ />{/if}
38
+ <p class="empty-state-title">{title}</p>
39
+ {#if description}<p class="empty-state-description">{description}</p>{/if}
40
+ {#if children}<div class="empty-state-actions">{@render children()}</div>{/if}
41
+ </div>
42
+
43
+ <style>
44
+ @layer svelte-ui {
45
+ .empty-state {
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: center;
49
+ gap: 6px;
50
+ box-sizing: border-box;
51
+ padding: 32px 16px;
52
+ font-size: 13px;
53
+ line-height: 1.5;
54
+ color: var(--color-text);
55
+ text-align: center;
56
+ border-radius: 14px;
57
+ }
58
+
59
+ .empty-state--secondary {
60
+ background-color: var(--color-surface);
61
+ border: 1px solid transparent;
62
+ }
63
+
64
+ .empty-state--outline {
65
+ border: 1px dashed var(--color-border-strong);
66
+ }
67
+
68
+ .empty-state :global(.empty-state-icon) {
69
+ width: 20px;
70
+ height: 20px;
71
+ margin-bottom: 4px;
72
+ color: var(--color-text-muted);
73
+ }
74
+
75
+ p {
76
+ max-width: 44ch;
77
+ margin: 0;
78
+ }
79
+
80
+ .empty-state-title {
81
+ font-weight: 500;
82
+ }
83
+
84
+ .empty-state-description {
85
+ color: var(--color-text-muted);
86
+ }
87
+
88
+ .empty-state-actions {
89
+ display: flex;
90
+ flex-wrap: wrap;
91
+ justify-content: center;
92
+ gap: 8px;
93
+ margin-top: 8px;
94
+ }
95
+
96
+ @media (forced-colors: active) {
97
+ .empty-state--secondary {
98
+ border-color: GrayText;
99
+ }
100
+
101
+ .empty-state--outline {
102
+ border-color: CanvasText;
103
+ }
104
+ }
105
+ }
106
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { IconComponent } from '../../icon.js';
4
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
5
+ /** Any icon component, e.g. from `@lucide/svelte`. */
6
+ icon?: IconComponent;
7
+ title: string;
8
+ description?: string;
9
+ /** `outline` has a dashed border, for empty areas that can be filled. */
10
+ variant?: 'secondary' | 'outline';
11
+ /** Optional actions below the text, e.g. a Button that creates the first entry. */
12
+ children?: Snippet;
13
+ /** Custom media instead of `icon`. */
14
+ media?: Snippet;
15
+ ref?: HTMLDivElement | null;
16
+ };
17
+ declare const EmptyState: import("svelte").Component<Props, {}, "ref">;
18
+ type EmptyState = ReturnType<typeof EmptyState>;
19
+ export default EmptyState;
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
6
+ /** Rendered as an `<h3>`. */
7
+ title: string;
8
+ /** Muted text next to the title, e.g. a period like "2021 - 2025". */
9
+ meta?: string;
10
+ /** The description. */
11
+ children?: Snippet;
12
+ /** Below the description, e.g. a Link. */
13
+ footer?: Snippet;
14
+ ref?: HTMLDivElement | null;
15
+ };
16
+
17
+ let {
18
+ title,
19
+ meta,
20
+ class: className,
21
+ children,
22
+ footer,
23
+ ref = $bindable(null),
24
+ ...rest
25
+ }: Props = $props();
26
+ </script>
27
+
28
+ <div {...rest} bind:this={ref} class={['entry', className]}>
29
+ <div class="entry-heading">
30
+ <h3 class="entry-title">{title}</h3>
31
+ {#if meta}
32
+ <span class="entry-meta">{meta}</span>
33
+ {/if}
34
+ </div>
35
+ {#if children}
36
+ <p class="entry-description">{@render children()}</p>
37
+ {/if}
38
+ {#if footer}
39
+ <div class="entry-footer">{@render footer()}</div>
40
+ {/if}
41
+ </div>
42
+
43
+ <style>
44
+ @layer svelte-ui {
45
+ .entry {
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: flex-start;
49
+ gap: 10px;
50
+ box-sizing: border-box;
51
+ padding: 24px;
52
+ color: var(--color-text);
53
+ border: 1px solid var(--color-border-strong);
54
+ border-radius: 10px;
55
+ }
56
+
57
+ .entry-heading {
58
+ display: flex;
59
+ flex-wrap: wrap;
60
+ align-items: baseline;
61
+ column-gap: 8px;
62
+ }
63
+
64
+ .entry-title {
65
+ margin: 0;
66
+ font-size: 20px;
67
+ font-weight: 500;
68
+ }
69
+
70
+ .entry-meta {
71
+ font-size: 14px;
72
+ color: var(--color-text-muted);
73
+ white-space: nowrap;
74
+ }
75
+
76
+ .entry-description {
77
+ margin: 0;
78
+ max-width: 640px;
79
+ }
80
+
81
+ .entry-footer {
82
+ display: flex;
83
+ flex-wrap: wrap;
84
+ align-items: center;
85
+ gap: 8px;
86
+ }
87
+
88
+ @media (min-width: 768px) {
89
+ .entry {
90
+ padding: 32px;
91
+ }
92
+ }
93
+ }
94
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
4
+ /** Rendered as an `<h3>`. */
5
+ title: string;
6
+ /** Muted text next to the title, e.g. a period like "2021 - 2025". */
7
+ meta?: string;
8
+ /** The description. */
9
+ children?: Snippet;
10
+ /** Below the description, e.g. a Link. */
11
+ footer?: Snippet;
12
+ ref?: HTMLDivElement | null;
13
+ };
14
+ declare const Entry: import("svelte").Component<Props, {}, "ref">;
15
+ type Entry = ReturnType<typeof Entry>;
16
+ export default Entry;