@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Noah Baumgartner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Then use the components:
25
25
  ```svelte
26
26
  <script lang="ts">
27
27
  import { Button, Card, CardHeader, CardTitle, CardContent } from '@noahbaumgartner/svelte-ui';
28
- import { Plus } from '@noahbaumgartner/svelte-ui/icons';
28
+ import { Plus } from '@lucide/svelte';
29
29
  </script>
30
30
 
31
31
  <Card>
@@ -40,21 +40,32 @@ Then use the components:
40
40
 
41
41
  ### Icons
42
42
 
43
- [Lucide](https://lucide.dev) icons are re-exported from `@noahbaumgartner/svelte-ui/icons`, so you don't need a separate `@lucide/svelte` install. They live on their own entry point because some icon names (`Link`, `Image`, …) collide with component names.
43
+ The library ships no icons and works with any icon library. Every `icon` prop takes a Svelte component, so use [Lucide](https://lucide.dev), [Phosphor](https://phosphoricons.com), your own SVG components or anything else:
44
+
45
+ ```svelte
46
+ <script>
47
+ import { Button } from '@noahbaumgartner/svelte-ui';
48
+ import Plus from '@lucide/svelte/icons/plus';
49
+ </script>
50
+
51
+ <Button icon={Plus}>New project</Button>
52
+ ```
53
+
54
+ An icon component receives `class` and `aria-hidden` and is sized with CSS, so it should render a single `<svg>` that fills its box. The `IconComponent` type describes what the props accept.
44
55
 
45
56
  ### Theming
46
57
 
47
- All colours are derived from two variables, `--base` and `--ink`, mixed into a grey scale. Light and dark mode follow `prefers-color-scheme` by default; force one with a `data-theme` attribute on `<html>`:
58
+ All colours are derived from two variables, `--ui-base` and `--ui-ink`, mixed into a grey scale. Light and dark mode follow `prefers-color-scheme` by default; force one with a `data-theme` attribute on `<html>`:
48
59
 
49
60
  ```html
50
61
  <html data-theme="dark"></html>
51
62
  ```
52
63
 
53
- Set `--accent` to colour checked controls, primary buttons and focus rings. It defaults to `--ink`, and the foreground colour on top of it is picked automatically for contrast:
64
+ Set `--ui-accent` to colour checked controls, primary buttons and focus rings. It defaults to `--ui-ink`, and the foreground colour on top of it is picked automatically for contrast:
54
65
 
55
66
  ```css
56
67
  :root {
57
- --accent: #2563eb;
68
+ --ui-accent: #2563eb;
58
69
  }
59
70
  ```
60
71
 
@@ -94,3 +105,7 @@ Components live in `src/lib/components/<name>/`, each next to its `.stories.svel
94
105
  ## Releasing
95
106
 
96
107
  Publishing a GitHub release triggers [`.github/workflows/publish.yml`](.github/workflows/publish.yml), which sets the package version from the release tag (e.g. `v0.2.0` → `0.2.0`), type-checks, and stages the publish on npm with provenance. The staged version then has to be approved with 2FA (`npm stage approve <stage-id>` or on npmjs.com).
108
+
109
+ ## License
110
+
111
+ [MIT](LICENSE)
@@ -6,11 +6,19 @@
6
6
  type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
7
7
  /** Lets several items be open at once. By default opening one closes the others. */
8
8
  multiple?: boolean;
9
+ /** The root element. Bindable. */
10
+ ref?: HTMLDivElement | null;
9
11
  /** AccordionItems. */
10
12
  children: Snippet;
11
13
  };
12
14
 
13
- let { multiple = false, class: className, children, ...rest }: Props = $props();
15
+ let {
16
+ multiple = false,
17
+ ref = $bindable(null),
18
+ class: className,
19
+ children,
20
+ ...rest
21
+ }: Props = $props();
14
22
 
15
23
  const name = $props.id();
16
24
 
@@ -21,14 +29,16 @@
21
29
  });
22
30
  </script>
23
31
 
24
- <div {...rest} class={['accordion', className]}>
32
+ <div {...rest} bind:this={ref} class={['accordion', className]}>
25
33
  {@render children()}
26
34
  </div>
27
35
 
28
36
  <style>
29
- .accordion {
30
- display: flex;
31
- flex-direction: column;
32
- width: 100%;
37
+ @layer svelte-ui {
38
+ .accordion {
39
+ display: flex;
40
+ flex-direction: column;
41
+ width: 100%;
42
+ }
33
43
  }
34
44
  </style>
@@ -3,9 +3,11 @@ import type { HTMLAttributes } from 'svelte/elements';
3
3
  type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
4
  /** Lets several items be open at once. By default opening one closes the others. */
5
5
  multiple?: boolean;
6
+ /** The root element. Bindable. */
7
+ ref?: HTMLDivElement | null;
6
8
  /** AccordionItems. */
7
9
  children: Snippet;
8
10
  };
9
- declare const Accordion: import("svelte").Component<Props, {}, "">;
11
+ declare const Accordion: import("svelte").Component<Props, {}, "ref">;
10
12
  type Accordion = ReturnType<typeof Accordion>;
11
13
  export default Accordion;
@@ -1,106 +1,145 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLDetailsAttributes } from 'svelte/elements';
4
- import { ChevronDown } from '@lucide/svelte';
4
+ import ChevronDown from '../../internal/icons/ChevronDown.svelte';
5
5
  import { getAccordionContext } from './context.js';
6
6
 
7
7
  type Props = Omit<HTMLDetailsAttributes, 'children' | 'title' | 'name'> & {
8
8
  /** Whether the item is expanded. Bindable. */
9
9
  open?: boolean;
10
+ /** The details element. Bindable. */
11
+ ref?: HTMLDetailsElement | null;
10
12
  /** The always visible heading that toggles the item. */
11
13
  title: string;
14
+ /** Replaces the title text, e.g. to add an icon or badge. */
15
+ heading?: Snippet;
12
16
  /** The content shown while the item is open. */
13
17
  children: Snippet;
14
18
  };
15
19
 
16
- let { open = $bindable(false), title, class: className, children, ...rest }: Props = $props();
20
+ let {
21
+ open = $bindable(false),
22
+ ref = $bindable(null),
23
+ title,
24
+ heading,
25
+ class: className,
26
+ children,
27
+ ...rest
28
+ }: Props = $props();
17
29
 
18
30
  const accordion = getAccordionContext();
19
31
  </script>
20
32
 
21
- <details {...rest} bind:open name={accordion.name} class={['accordion-item', className]}>
33
+ <details
34
+ {...rest}
35
+ bind:this={ref}
36
+ bind:open
37
+ name={accordion.name}
38
+ class={['accordion-item', className]}
39
+ >
22
40
  <summary class="accordion-trigger">
23
- <span class="accordion-title">{title}</span>
41
+ <span class="accordion-title">
42
+ {#if heading}{@render heading()}{:else}{title}{/if}
43
+ </span>
24
44
  <ChevronDown class="accordion-chevron" aria-hidden="true" />
25
45
  </summary>
26
46
  <div class="accordion-content">{@render children()}</div>
27
47
  </details>
28
48
 
29
49
  <style>
30
- .accordion-item {
31
- font-size: 13px;
32
- color: var(--color-text);
33
- border-bottom: 1px solid var(--color-border);
34
- /* Lets the height animate to and from `auto` where the browser supports it */
35
- interpolate-size: allow-keywords;
36
- }
50
+ @layer svelte-ui {
51
+ .accordion-item {
52
+ font-size: 13px;
53
+ color: var(--color-text);
54
+ border-bottom: 1px solid var(--color-border);
55
+ /* Lets the height animate to and from `auto` where the browser supports it */
56
+ interpolate-size: allow-keywords;
57
+ }
37
58
 
38
- .accordion-item:last-child {
39
- border-bottom: 0;
40
- }
59
+ .accordion-item:last-child {
60
+ border-bottom: 0;
61
+ }
41
62
 
42
- .accordion-trigger {
43
- display: flex;
44
- align-items: center;
45
- justify-content: space-between;
46
- gap: 12px;
47
- padding: 14px 0;
48
- font-weight: 500;
49
- list-style: none;
50
- border-radius: 6px;
51
- outline: none;
52
- cursor: pointer;
53
- user-select: none;
54
- }
63
+ .accordion-trigger {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: space-between;
67
+ gap: 12px;
68
+ padding: 14px 0;
69
+ font-weight: 500;
70
+ list-style: none;
71
+ border-radius: 6px;
72
+ outline: none;
73
+ cursor: pointer;
74
+ user-select: none;
75
+ }
55
76
 
56
- .accordion-trigger::-webkit-details-marker {
57
- display: none;
58
- }
77
+ .accordion-trigger::-webkit-details-marker {
78
+ display: none;
79
+ }
59
80
 
60
- .accordion-trigger:hover .accordion-title {
61
- text-decoration: underline;
62
- text-underline-offset: 3px;
63
- }
81
+ .accordion-trigger:hover .accordion-title {
82
+ text-decoration: underline;
83
+ text-underline-offset: 3px;
84
+ }
64
85
 
65
- .accordion-trigger:focus-visible {
66
- outline: 2px solid var(--color-accent);
67
- outline-offset: 2px;
68
- }
86
+ .accordion-trigger:focus-visible {
87
+ outline: 2px solid var(--color-accent);
88
+ outline-offset: 2px;
89
+ }
69
90
 
70
- .accordion-trigger :global(.accordion-chevron) {
71
- width: 16px;
72
- height: 16px;
73
- flex-shrink: 0;
74
- color: var(--color-text-muted);
75
- transition: rotate 200ms ease;
76
- }
91
+ .accordion-trigger :global(.accordion-chevron) {
92
+ width: 16px;
93
+ height: 16px;
94
+ flex-shrink: 0;
95
+ color: var(--color-text-muted);
96
+ transition: rotate 200ms ease;
97
+ }
77
98
 
78
- .accordion-item[open] > .accordion-trigger :global(.accordion-chevron) {
79
- rotate: 180deg;
80
- }
99
+ .accordion-item[open] > .accordion-trigger :global(.accordion-chevron) {
100
+ rotate: 180deg;
101
+ }
81
102
 
82
- .accordion-item::details-content {
83
- block-size: 0;
84
- overflow: clip;
85
- transition:
86
- block-size 200ms ease,
87
- content-visibility 200ms allow-discrete;
88
- }
103
+ .accordion-item::details-content {
104
+ block-size: 0;
105
+ overflow: clip;
106
+ transition:
107
+ block-size 200ms ease,
108
+ content-visibility 200ms allow-discrete;
109
+ }
89
110
 
90
- .accordion-item[open]::details-content {
91
- block-size: auto;
92
- }
111
+ .accordion-item[open]::details-content {
112
+ block-size: auto;
113
+ }
93
114
 
94
- .accordion-content {
95
- padding-bottom: 14px;
96
- line-height: 1.5;
97
- color: var(--color-text-secondary);
98
- }
115
+ .accordion-content {
116
+ padding-bottom: 14px;
117
+ line-height: 1.5;
118
+ color: var(--color-text-secondary);
119
+ }
99
120
 
100
- @media (prefers-reduced-motion: reduce) {
101
- .accordion-item::details-content,
102
- .accordion-trigger :global(.accordion-chevron) {
103
- transition: none;
121
+ @media (pointer: coarse) {
122
+ .accordion-trigger {
123
+ min-height: 44px;
124
+ box-sizing: border-box;
125
+ }
126
+ }
127
+
128
+ @media (forced-colors: active) {
129
+ .accordion-trigger:focus-visible {
130
+ outline-color: Highlight;
131
+ }
132
+
133
+ .accordion-item {
134
+ border-bottom-color: CanvasText;
135
+ }
136
+ }
137
+
138
+ @media (prefers-reduced-motion: reduce) {
139
+ .accordion-item::details-content,
140
+ .accordion-trigger :global(.accordion-chevron) {
141
+ transition: none;
142
+ }
104
143
  }
105
144
  }
106
145
  </style>
@@ -3,11 +3,15 @@ import type { HTMLDetailsAttributes } from 'svelte/elements';
3
3
  type Props = Omit<HTMLDetailsAttributes, 'children' | 'title' | 'name'> & {
4
4
  /** Whether the item is expanded. Bindable. */
5
5
  open?: boolean;
6
+ /** The details element. Bindable. */
7
+ ref?: HTMLDetailsElement | null;
6
8
  /** The always visible heading that toggles the item. */
7
9
  title: string;
10
+ /** Replaces the title text, e.g. to add an icon or badge. */
11
+ heading?: Snippet;
8
12
  /** The content shown while the item is open. */
9
13
  children: Snippet;
10
14
  };
11
- declare const AccordionItem: import("svelte").Component<Props, {}, "open">;
15
+ declare const AccordionItem: import("svelte").Component<Props, {}, "ref" | "open">;
12
16
  type AccordionItem = ReturnType<typeof AccordionItem>;
13
17
  export default AccordionItem;
@@ -7,10 +7,16 @@
7
7
 
8
8
  <script lang="ts">
9
9
  import type { Snippet } from 'svelte';
10
+ import type { HTMLDialogAttributes } from 'svelte/elements';
10
11
 
11
- type Props = {
12
+ type Props = Omit<
13
+ HTMLDialogAttributes,
14
+ 'open' | 'title' | 'children' | 'role' | 'aria-labelledby' | 'aria-describedby'
15
+ > & {
12
16
  /** Whether the dialog is open. Bindable. Escape closes it. */
13
17
  open?: boolean;
18
+ /** The dialog element. Bindable. */
19
+ ref?: HTMLDialogElement | null;
14
20
  title: string;
15
21
  description?: string;
16
22
  /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. Omit to control `open` yourself. */
@@ -21,10 +27,19 @@
21
27
  children?: Snippet;
22
28
  };
23
29
 
24
- let { open = $bindable(false), title, description, trigger, actions, children }: Props = $props();
30
+ let {
31
+ open = $bindable(false),
32
+ ref = $bindable(null),
33
+ title,
34
+ description,
35
+ trigger,
36
+ actions,
37
+ children,
38
+ class: className,
39
+ ...rest
40
+ }: Props = $props();
25
41
 
26
42
  const id = $props.id();
27
- let dialogEl: HTMLDialogElement;
28
43
 
29
44
  const triggerProps: AlertDialogTriggerProps = {
30
45
  onclick: () => (open = true),
@@ -36,13 +51,14 @@
36
51
  }
37
52
 
38
53
  $effect(() => {
39
- if (open && !dialogEl.open) {
40
- dialogEl.showModal();
41
- Array.from(dialogEl.querySelectorAll<HTMLElement>('.alert-dialog-actions :is(button, a)'))
54
+ if (!ref) return;
55
+ if (open && !ref.open) {
56
+ ref.showModal();
57
+ Array.from(ref.querySelectorAll<HTMLElement>('.alert-dialog-actions :is(button, a)'))
42
58
  .at(-1)
43
59
  ?.focus();
44
- } else if (!open && dialogEl.open) {
45
- dialogEl.close();
60
+ } else if (!open && ref.open) {
61
+ ref.close();
46
62
  }
47
63
  });
48
64
  </script>
@@ -50,11 +66,12 @@
50
66
  {@render trigger?.(triggerProps)}
51
67
 
52
68
  <dialog
53
- bind:this={dialogEl}
69
+ {...rest}
70
+ bind:this={ref}
54
71
  role="alertdialog"
55
72
  aria-labelledby="{id}-title"
56
73
  aria-describedby={description ? `${id}-description` : undefined}
57
- class="alert-dialog"
74
+ class={['alert-dialog', className]}
58
75
  onkeydown={(event) => {
59
76
  if (event.key !== 'Escape') return;
60
77
  event.preventDefault();
@@ -79,83 +96,98 @@
79
96
  </dialog>
80
97
 
81
98
  <style>
82
- /* Locks page scrolling while open; the gutter keeps the layout from shifting */
83
- :global(html:has(.alert-dialog[open])) {
84
- overflow: hidden;
85
- scrollbar-gutter: stable;
86
- }
99
+ @layer svelte-ui {
100
+ /* Locks page scrolling while open; the gutter keeps the layout from shifting */
101
+ :global(html:has(.alert-dialog[open])) {
102
+ overflow: hidden;
103
+ scrollbar-gutter: stable;
104
+ }
87
105
 
88
- .alert-dialog {
89
- box-sizing: border-box;
90
- width: calc(100% - 32px);
91
- max-width: 420px;
92
- padding: 20px;
93
- background-color: var(--color-bg);
94
- color: var(--color-text);
95
- border: 1px solid var(--color-border);
96
- border-radius: 14px;
97
- box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
98
- outline: none;
99
- opacity: 0;
100
- scale: 0.95;
101
- transition:
102
- opacity 140ms ease,
103
- scale 140ms ease,
104
- display 140ms allow-discrete,
105
- overlay 140ms allow-discrete;
106
- }
106
+ .alert-dialog {
107
+ box-sizing: border-box;
108
+ width: calc(100% - 32px);
109
+ max-width: 420px;
110
+ padding: 20px;
111
+ background-color: var(--color-bg);
112
+ color: var(--color-text);
113
+ border: 1px solid var(--color-border);
114
+ border-radius: 14px;
115
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
116
+ outline: none;
117
+ opacity: 0;
118
+ scale: 0.95;
119
+ transition:
120
+ opacity 140ms ease,
121
+ scale 140ms ease,
122
+ display 140ms allow-discrete,
123
+ overlay 140ms allow-discrete;
124
+ }
107
125
 
108
- .alert-dialog[open] {
109
- opacity: 1;
110
- scale: 1;
111
- }
126
+ .alert-dialog[open] {
127
+ opacity: 1;
128
+ scale: 1;
129
+ }
112
130
 
113
- .alert-dialog::backdrop {
114
- background-color: rgba(0, 0, 0, 0);
115
- transition:
116
- background-color 140ms ease,
117
- display 140ms allow-discrete,
118
- overlay 140ms allow-discrete;
119
- }
131
+ .alert-dialog::backdrop {
132
+ background-color: rgba(0, 0, 0, 0);
133
+ transition:
134
+ background-color 140ms ease,
135
+ display 140ms allow-discrete,
136
+ overlay 140ms allow-discrete;
137
+ }
120
138
 
121
- .alert-dialog[open]::backdrop {
122
- background-color: rgba(0, 0, 0, 0.4);
123
- }
139
+ .alert-dialog[open]::backdrop {
140
+ background-color: rgba(0, 0, 0, 0.4);
141
+ }
124
142
 
125
- @starting-style {
126
- .alert-dialog[open] {
127
- opacity: 0;
128
- scale: 0.95;
143
+ @starting-style {
144
+ .alert-dialog[open] {
145
+ opacity: 0;
146
+ scale: 0.95;
147
+ }
148
+
149
+ .alert-dialog[open]::backdrop {
150
+ background-color: rgba(0, 0, 0, 0);
151
+ }
129
152
  }
130
153
 
131
- .alert-dialog[open]::backdrop {
132
- background-color: rgba(0, 0, 0, 0);
154
+ .alert-dialog-title {
155
+ margin: 0;
156
+ font-size: 15px;
157
+ font-weight: 600;
133
158
  }
134
- }
135
159
 
136
- .alert-dialog-title {
137
- margin: 0;
138
- font-size: 15px;
139
- font-weight: 600;
140
- }
160
+ .alert-dialog-description {
161
+ margin: 6px 0 0;
162
+ font-size: 13px;
163
+ line-height: 1.5;
164
+ color: var(--color-text-muted);
165
+ }
141
166
 
142
- .alert-dialog-description {
143
- margin: 6px 0 0;
144
- font-size: 13px;
145
- line-height: 1.5;
146
- color: var(--color-text-muted);
147
- }
167
+ .alert-dialog-body {
168
+ margin-top: 16px;
169
+ font-size: 13px;
170
+ }
148
171
 
149
- .alert-dialog-body {
150
- margin-top: 16px;
151
- font-size: 13px;
152
- }
172
+ @media (prefers-reduced-motion: reduce) {
173
+ .alert-dialog,
174
+ .alert-dialog::backdrop {
175
+ transition: none;
176
+ }
177
+ }
153
178
 
154
- .alert-dialog-actions {
155
- display: flex;
156
- flex-wrap: wrap;
157
- justify-content: center;
158
- gap: 4px;
159
- margin-top: 20px;
179
+ @media (forced-colors: active) {
180
+ .alert-dialog {
181
+ border-color: CanvasText;
182
+ }
183
+ }
184
+
185
+ .alert-dialog-actions {
186
+ display: flex;
187
+ flex-wrap: wrap;
188
+ justify-content: center;
189
+ gap: 4px;
190
+ margin-top: 20px;
191
+ }
160
192
  }
161
193
  </style>
@@ -3,9 +3,12 @@ export type AlertDialogTriggerProps = {
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' | 'role' | 'aria-labelledby' | 'aria-describedby'> & {
7
8
  /** Whether the dialog is open. Bindable. Escape closes it. */
8
9
  open?: boolean;
10
+ /** The dialog element. Bindable. */
11
+ ref?: HTMLDialogElement | null;
9
12
  title: string;
10
13
  description?: string;
11
14
  /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. Omit to control `open` yourself. */
@@ -15,6 +18,6 @@ type Props = {
15
18
  /** Extra content between the description and the buttons. */
16
19
  children?: Snippet;
17
20
  };
18
- declare const AlertDialog: import("svelte").Component<Props, {}, "open">;
21
+ declare const AlertDialog: import("svelte").Component<Props, {}, "ref" | "open">;
19
22
  type AlertDialog = ReturnType<typeof AlertDialog>;
20
23
  export default AlertDialog;