@noahbaumgartner/svelte-ui 0.0.5 → 0.0.7

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