@noahbaumgartner/svelte-ui 0.0.1

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 (149) hide show
  1. package/README.md +65 -0
  2. package/dist/components/alert-dialog/AlertDialog.svelte +161 -0
  3. package/dist/components/alert-dialog/AlertDialog.svelte.d.ts +20 -0
  4. package/dist/components/avatar/Avatar.svelte +85 -0
  5. package/dist/components/avatar/Avatar.svelte.d.ts +12 -0
  6. package/dist/components/badge/Badge.svelte +49 -0
  7. package/dist/components/badge/Badge.svelte.d.ts +12 -0
  8. package/dist/components/breadcrumb/Breadcrumb.svelte +37 -0
  9. package/dist/components/breadcrumb/Breadcrumb.svelte.d.ts +9 -0
  10. package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte +32 -0
  11. package/dist/components/breadcrumb/BreadcrumbEllipsis.svelte.d.ts +10 -0
  12. package/dist/components/breadcrumb/BreadcrumbItem.svelte +71 -0
  13. package/dist/components/breadcrumb/BreadcrumbItem.svelte.d.ts +12 -0
  14. package/dist/components/breadcrumb/BreadcrumbSeparator.svelte +18 -0
  15. package/dist/components/breadcrumb/BreadcrumbSeparator.svelte.d.ts +18 -0
  16. package/dist/components/button/Button.svelte +247 -0
  17. package/dist/components/button/Button.svelte.d.ts +24 -0
  18. package/dist/components/button-group/ButtonGroup.svelte +89 -0
  19. package/dist/components/button-group/ButtonGroup.svelte.d.ts +12 -0
  20. package/dist/components/calendar/Calendar.svelte +401 -0
  21. package/dist/components/calendar/Calendar.svelte.d.ts +17 -0
  22. package/dist/components/calendar/dates.d.ts +12 -0
  23. package/dist/components/calendar/dates.js +48 -0
  24. package/dist/components/card/Card.svelte +55 -0
  25. package/dist/components/card/Card.svelte.d.ts +10 -0
  26. package/dist/components/card/CardAction.svelte +24 -0
  27. package/dist/components/card/CardAction.svelte.d.ts +9 -0
  28. package/dist/components/card/CardContent.svelte +20 -0
  29. package/dist/components/card/CardContent.svelte.d.ts +8 -0
  30. package/dist/components/card/CardDescription.svelte +23 -0
  31. package/dist/components/card/CardDescription.svelte.d.ts +8 -0
  32. package/dist/components/card/CardFooter.svelte +28 -0
  33. package/dist/components/card/CardFooter.svelte.d.ts +9 -0
  34. package/dist/components/card/CardHeader.svelte +29 -0
  35. package/dist/components/card/CardHeader.svelte.d.ts +9 -0
  36. package/dist/components/card/CardTitle.svelte +23 -0
  37. package/dist/components/card/CardTitle.svelte.d.ts +8 -0
  38. package/dist/components/checkbox/Checkbox.svelte +93 -0
  39. package/dist/components/checkbox/Checkbox.svelte.d.ts +5 -0
  40. package/dist/components/color-picker/ColorPicker.svelte +233 -0
  41. package/dist/components/color-picker/ColorPicker.svelte.d.ts +10 -0
  42. package/dist/components/context-menu/ContextMenu.svelte +201 -0
  43. package/dist/components/context-menu/ContextMenu.svelte.d.ts +14 -0
  44. package/dist/components/context-menu/ContextMenuItem.svelte +105 -0
  45. package/dist/components/context-menu/ContextMenuItem.svelte.d.ts +14 -0
  46. package/dist/components/context-menu/context.d.ts +14 -0
  47. package/dist/components/context-menu/context.js +2 -0
  48. package/dist/components/dialog/Dialog.svelte +225 -0
  49. package/dist/components/dialog/Dialog.svelte.d.ts +22 -0
  50. package/dist/components/field/Field.svelte +78 -0
  51. package/dist/components/field/Field.svelte.d.ts +11 -0
  52. package/dist/components/field/FieldContent.svelte +25 -0
  53. package/dist/components/field/FieldContent.svelte.d.ts +9 -0
  54. package/dist/components/field/FieldDescription.svelte +34 -0
  55. package/dist/components/field/FieldDescription.svelte.d.ts +9 -0
  56. package/dist/components/field/FieldError.svelte +45 -0
  57. package/dist/components/field/FieldError.svelte.d.ts +11 -0
  58. package/dist/components/field/FieldGroup.svelte +30 -0
  59. package/dist/components/field/FieldGroup.svelte.d.ts +9 -0
  60. package/dist/components/field/FieldLegend.svelte +34 -0
  61. package/dist/components/field/FieldLegend.svelte.d.ts +10 -0
  62. package/dist/components/field/FieldSeparator.svelte +31 -0
  63. package/dist/components/field/FieldSeparator.svelte.d.ts +9 -0
  64. package/dist/components/field/FieldSet.svelte +33 -0
  65. package/dist/components/field/FieldSet.svelte.d.ts +9 -0
  66. package/dist/components/input/Input.svelte +459 -0
  67. package/dist/components/input/Input.svelte.d.ts +10 -0
  68. package/dist/components/input-group/InputGroup.svelte +88 -0
  69. package/dist/components/input-group/InputGroup.svelte.d.ts +9 -0
  70. package/dist/components/input-group/InputGroupAddon.svelte +88 -0
  71. package/dist/components/input-group/InputGroupAddon.svelte.d.ts +11 -0
  72. package/dist/components/kbd/Kbd.svelte +46 -0
  73. package/dist/components/kbd/Kbd.svelte.d.ts +8 -0
  74. package/dist/components/kbd/KbdGroup.svelte +27 -0
  75. package/dist/components/kbd/KbdGroup.svelte.d.ts +8 -0
  76. package/dist/components/label/Label.svelte +48 -0
  77. package/dist/components/label/Label.svelte.d.ts +9 -0
  78. package/dist/components/link/Link.svelte +40 -0
  79. package/dist/components/link/Link.svelte.d.ts +10 -0
  80. package/dist/components/navbar/Navbar.svelte +266 -0
  81. package/dist/components/navbar/Navbar.svelte.d.ts +26 -0
  82. package/dist/components/navigation-menu/NavigationMenu.svelte +95 -0
  83. package/dist/components/navigation-menu/NavigationMenu.svelte.d.ts +12 -0
  84. package/dist/components/navigation-menu/NavigationMenuItem.svelte +257 -0
  85. package/dist/components/navigation-menu/NavigationMenuItem.svelte.d.ts +15 -0
  86. package/dist/components/navigation-menu/NavigationMenuLink.svelte +91 -0
  87. package/dist/components/navigation-menu/NavigationMenuLink.svelte.d.ts +16 -0
  88. package/dist/components/navigation-menu/context.d.ts +15 -0
  89. package/dist/components/navigation-menu/context.js +2 -0
  90. package/dist/components/popover/Popover.svelte +295 -0
  91. package/dist/components/popover/Popover.svelte.d.ts +34 -0
  92. package/dist/components/radio-group/RadioGroup.svelte +86 -0
  93. package/dist/components/radio-group/RadioGroup.svelte.d.ts +16 -0
  94. package/dist/components/radio-group/RadioGroupItem.svelte +95 -0
  95. package/dist/components/radio-group/RadioGroupItem.svelte.d.ts +7 -0
  96. package/dist/components/radio-group/context.d.ts +9 -0
  97. package/dist/components/radio-group/context.js +2 -0
  98. package/dist/components/separator/Separator.svelte +41 -0
  99. package/dist/components/separator/Separator.svelte.d.ts +10 -0
  100. package/dist/components/sidebar/Sidebar.svelte +180 -0
  101. package/dist/components/sidebar/Sidebar.svelte.d.ts +14 -0
  102. package/dist/components/sidebar/SidebarContent.svelte +30 -0
  103. package/dist/components/sidebar/SidebarContent.svelte.d.ts +9 -0
  104. package/dist/components/sidebar/SidebarFooter.svelte +23 -0
  105. package/dist/components/sidebar/SidebarFooter.svelte.d.ts +8 -0
  106. package/dist/components/sidebar/SidebarGroup.svelte +55 -0
  107. package/dist/components/sidebar/SidebarGroup.svelte.d.ts +11 -0
  108. package/dist/components/sidebar/SidebarHeader.svelte +23 -0
  109. package/dist/components/sidebar/SidebarHeader.svelte.d.ts +8 -0
  110. package/dist/components/sidebar/SidebarInset.svelte +25 -0
  111. package/dist/components/sidebar/SidebarInset.svelte.d.ts +9 -0
  112. package/dist/components/sidebar/SidebarMenu.svelte +27 -0
  113. package/dist/components/sidebar/SidebarMenu.svelte.d.ts +9 -0
  114. package/dist/components/sidebar/SidebarMenuItem.svelte +221 -0
  115. package/dist/components/sidebar/SidebarMenuItem.svelte.d.ts +23 -0
  116. package/dist/components/sidebar/SidebarProvider.svelte +62 -0
  117. package/dist/components/sidebar/SidebarProvider.svelte.d.ts +11 -0
  118. package/dist/components/sidebar/SidebarSeparator.svelte +5 -0
  119. package/dist/components/sidebar/SidebarSeparator.svelte.d.ts +18 -0
  120. package/dist/components/sidebar/SidebarTrigger.svelte +24 -0
  121. package/dist/components/sidebar/SidebarTrigger.svelte.d.ts +7 -0
  122. package/dist/components/sidebar/context.d.ts +15 -0
  123. package/dist/components/sidebar/context.js +2 -0
  124. package/dist/components/skeleton/Skeleton.svelte +30 -0
  125. package/dist/components/skeleton/Skeleton.svelte.d.ts +5 -0
  126. package/dist/components/spinner/Spinner.svelte +72 -0
  127. package/dist/components/spinner/Spinner.svelte.d.ts +9 -0
  128. package/dist/components/switch/Switch.svelte +88 -0
  129. package/dist/components/switch/Switch.svelte.d.ts +5 -0
  130. package/dist/components/table/Table.svelte +276 -0
  131. package/dist/components/table/Table.svelte.d.ts +60 -0
  132. package/dist/components/textarea/Textarea.svelte +57 -0
  133. package/dist/components/textarea/Textarea.svelte.d.ts +4 -0
  134. package/dist/components/time-picker/TimePicker.svelte +162 -0
  135. package/dist/components/time-picker/TimePicker.svelte.d.ts +11 -0
  136. package/dist/components/toaster/Toaster.svelte +151 -0
  137. package/dist/components/toaster/Toaster.svelte.d.ts +7 -0
  138. package/dist/components/toaster/toast.svelte.d.ts +28 -0
  139. package/dist/components/toaster/toast.svelte.js +22 -0
  140. package/dist/components/toggle/Toggle.svelte +31 -0
  141. package/dist/components/toggle/Toggle.svelte.d.ts +8 -0
  142. package/dist/components/tooltip/Tooltip.svelte +224 -0
  143. package/dist/components/tooltip/Tooltip.svelte.d.ts +27 -0
  144. package/dist/icons.d.ts +2 -0
  145. package/dist/icons.js +4 -0
  146. package/dist/index.d.ts +73 -0
  147. package/dist/index.js +66 -0
  148. package/dist/styles/tokens.css +50 -0
  149. package/package.json +88 -0
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { LucideIcon } from '@lucide/svelte';
4
+ import Check from '@lucide/svelte/icons/check';
5
+ import { getMenuContext } from './context.js';
6
+
7
+ type Props = {
8
+ /** Any Lucide icon (import from `svelte-ui/icons`). */
9
+ icon?: LucideIcon;
10
+ /** Makes the item a radio option; `true` marks it as the current choice. */
11
+ selected?: boolean;
12
+ disabled?: boolean;
13
+ onclick?: () => void;
14
+ children: Snippet;
15
+ };
16
+
17
+ let { icon: Icon, selected, disabled = false, onclick, children }: Props = $props();
18
+
19
+ const menu = getMenuContext();
20
+
21
+ function handleClick() {
22
+ onclick?.();
23
+ menu.close();
24
+ }
25
+ </script>
26
+
27
+ <button
28
+ type="button"
29
+ tabindex="-1"
30
+ role={selected === undefined ? 'menuitem' : 'menuitemradio'}
31
+ aria-checked={selected}
32
+ {disabled}
33
+ class={['context-menu-item', { 'context-menu-item--selected': selected }]}
34
+ onclick={handleClick}
35
+ >
36
+ {#if Icon}<Icon class="context-menu-item-icon" aria-hidden="true" />{/if}
37
+ <span class="context-menu-item-label">{@render children()}</span>
38
+ {#if selected !== undefined}
39
+ <span class="context-menu-item-check">
40
+ {#if selected}<Check aria-hidden="true" />{/if}
41
+ </span>
42
+ {/if}
43
+ </button>
44
+
45
+ <style>
46
+ .context-menu-item {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 10px;
50
+ width: 100%;
51
+ padding: 8px 10px;
52
+ background-color: transparent;
53
+ border: none;
54
+ border-radius: 6px;
55
+ font: inherit;
56
+ font-size: 13px;
57
+ color: var(--color-text);
58
+ text-align: left;
59
+ outline: none;
60
+ cursor: pointer;
61
+ transition: background-color 150ms ease;
62
+ }
63
+
64
+ .context-menu-item:hover:not(:disabled),
65
+ .context-menu-item:focus-visible {
66
+ background-color: var(--color-surface);
67
+ }
68
+
69
+ .context-menu-item:disabled {
70
+ cursor: not-allowed;
71
+ opacity: 0.5;
72
+ }
73
+
74
+ .context-menu-item--selected {
75
+ background-color: var(--color-surface);
76
+ font-weight: 600;
77
+ }
78
+
79
+ .context-menu-item--selected:hover,
80
+ .context-menu-item--selected:focus-visible {
81
+ background-color: var(--color-surface-hover);
82
+ }
83
+
84
+ .context-menu-item :global(.context-menu-item-icon) {
85
+ width: 16px;
86
+ height: 16px;
87
+ flex-shrink: 0;
88
+ }
89
+
90
+ .context-menu-item-label {
91
+ flex-grow: 1;
92
+ }
93
+
94
+ .context-menu-item-check {
95
+ display: flex;
96
+ width: 14px;
97
+ height: 14px;
98
+ flex-shrink: 0;
99
+ }
100
+
101
+ .context-menu-item-check :global(svg) {
102
+ width: 14px;
103
+ height: 14px;
104
+ }
105
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { LucideIcon } from '@lucide/svelte';
3
+ type Props = {
4
+ /** Any Lucide icon (import from `svelte-ui/icons`). */
5
+ icon?: LucideIcon;
6
+ /** Makes the item a radio option; `true` marks it as the current choice. */
7
+ selected?: boolean;
8
+ disabled?: boolean;
9
+ onclick?: () => void;
10
+ children: Snippet;
11
+ };
12
+ declare const ContextMenuItem: import("svelte").Component<Props, {}, "">;
13
+ type ContextMenuItem = ReturnType<typeof ContextMenuItem>;
14
+ export default ContextMenuItem;
@@ -0,0 +1,14 @@
1
+ export type ContextMenuTriggerProps = {
2
+ onclick: () => void;
3
+ onkeydown: (event: KeyboardEvent) => void;
4
+ 'aria-haspopup': 'menu';
5
+ 'aria-expanded': boolean;
6
+ 'aria-controls': string;
7
+ };
8
+ export declare const getMenuContext: () => {
9
+ close: () => void;
10
+ }, setMenuContext: (context: {
11
+ close: () => void;
12
+ }) => {
13
+ close: () => void;
14
+ };
@@ -0,0 +1,2 @@
1
+ import { createContext } from 'svelte';
2
+ export const [getMenuContext, setMenuContext] = createContext();
@@ -0,0 +1,225 @@
1
+ <script module lang="ts">
2
+ export type DialogTriggerProps = {
3
+ onclick: () => void;
4
+ 'aria-haspopup': 'dialog';
5
+ };
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ import type { Snippet } from 'svelte';
10
+ import { X } from '@lucide/svelte';
11
+ import Button from '../button/Button.svelte';
12
+ import Spinner from '../spinner/Spinner.svelte';
13
+
14
+ type Props = {
15
+ /** Whether the dialog is open. Bindable. Escape, the close button and a click on the backdrop close it. */
16
+ open?: boolean;
17
+ title: string;
18
+ description?: string;
19
+ /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. Omit to control `open` yourself. */
20
+ trigger?: Snippet<[DialogTriggerProps]>;
21
+ /** The dialog's content. The first focusable element in it gets the focus on open. */
22
+ children?: Snippet;
23
+ /** Optional Buttons in the footer, right-aligned with the primary one last (rightmost). Call `close` to close the dialog. */
24
+ actions?: Snippet<[close: () => void]>;
25
+ /** Grays out the content and actions, makes them inert and shows a centered Spinner. The close button keeps working. */
26
+ loading?: boolean;
27
+ };
28
+
29
+ let {
30
+ open = $bindable(false),
31
+ title,
32
+ description,
33
+ trigger,
34
+ children,
35
+ actions,
36
+ loading = false
37
+ }: Props = $props();
38
+
39
+ const id = $props.id();
40
+ let dialogEl: HTMLDialogElement;
41
+
42
+ const triggerProps: DialogTriggerProps = {
43
+ onclick: () => (open = true),
44
+ 'aria-haspopup': 'dialog'
45
+ };
46
+
47
+ function close() {
48
+ open = false;
49
+ }
50
+
51
+ $effect(() => {
52
+ if (open && !dialogEl.open) {
53
+ dialogEl.showModal();
54
+ } else if (!open && dialogEl.open) {
55
+ dialogEl.close();
56
+ }
57
+ });
58
+ </script>
59
+
60
+ {@render trigger?.(triggerProps)}
61
+
62
+ <dialog
63
+ bind:this={dialogEl}
64
+ aria-labelledby="{id}-title"
65
+ aria-describedby={description ? `${id}-description` : undefined}
66
+ aria-busy={loading || undefined}
67
+ class="dialog"
68
+ onkeydown={(event) => {
69
+ if (event.key !== 'Escape') return;
70
+ event.preventDefault();
71
+ close();
72
+ }}
73
+ oncancel={(event) => {
74
+ event.preventDefault();
75
+ close();
76
+ }}
77
+ onclose={() => (open = false)}
78
+ onclick={(event) => {
79
+ // Clicks on the ::backdrop target the <dialog> itself, outside its box
80
+ if (event.target !== dialogEl) return;
81
+ const rect = dialogEl.getBoundingClientRect();
82
+ const inside =
83
+ event.clientX >= rect.left &&
84
+ event.clientX <= rect.right &&
85
+ event.clientY >= rect.top &&
86
+ event.clientY <= rect.bottom;
87
+ if (!inside) close();
88
+ }}
89
+ >
90
+ <div class="dialog-content" class:dialog-dimmed={loading} inert={loading}>
91
+ <h2 id="{id}-title" class="dialog-title">{title}</h2>
92
+ {#if description}
93
+ <p id="{id}-description" class="dialog-description">{description}</p>
94
+ {/if}
95
+ {#if children}
96
+ <div class="dialog-body">{@render children()}</div>
97
+ {/if}
98
+ </div>
99
+ {#if actions}
100
+ <div class="dialog-actions" class:dialog-dimmed={loading} inert={loading}>
101
+ {@render actions(close)}
102
+ </div>
103
+ {/if}
104
+ {#if loading}
105
+ <Spinner class="dialog-spinner" size="lg" />
106
+ {/if}
107
+ <!-- Last in the DOM so the content, not the close button, gets the initial focus -->
108
+ <Button class="dialog-close" variant="ghost" size="sm" icon={X} label="Close" onclick={close} />
109
+ </dialog>
110
+
111
+ <style>
112
+ /* Locks page scrolling while open; the gutter keeps the layout from shifting */
113
+ :global(html:has(.dialog[open])) {
114
+ overflow: hidden;
115
+ scrollbar-gutter: stable;
116
+ }
117
+
118
+ .dialog {
119
+ box-sizing: border-box;
120
+ width: calc(100% - 32px);
121
+ max-width: 480px;
122
+ max-height: calc(100% - 32px);
123
+ flex-direction: column;
124
+ padding: 0;
125
+ overflow: hidden;
126
+ background-color: var(--color-bg);
127
+ color: var(--color-text);
128
+ border: 1px solid var(--color-border);
129
+ border-radius: 14px;
130
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.16);
131
+ outline: none;
132
+ opacity: 0;
133
+ scale: 0.95;
134
+ transition:
135
+ opacity 140ms ease,
136
+ scale 140ms ease,
137
+ display 140ms allow-discrete,
138
+ overlay 140ms allow-discrete;
139
+ }
140
+
141
+ .dialog[open] {
142
+ display: flex;
143
+ opacity: 1;
144
+ scale: 1;
145
+ }
146
+
147
+ .dialog::backdrop {
148
+ background-color: rgba(0, 0, 0, 0);
149
+ transition:
150
+ background-color 140ms ease,
151
+ display 140ms allow-discrete,
152
+ overlay 140ms allow-discrete;
153
+ }
154
+
155
+ .dialog[open]::backdrop {
156
+ background-color: rgba(0, 0, 0, 0.4);
157
+ }
158
+
159
+ @starting-style {
160
+ .dialog[open] {
161
+ opacity: 0;
162
+ scale: 0.95;
163
+ }
164
+
165
+ .dialog[open]::backdrop {
166
+ background-color: rgba(0, 0, 0, 0);
167
+ }
168
+ }
169
+
170
+ .dialog-content,
171
+ .dialog-actions {
172
+ transition: opacity 140ms ease;
173
+ }
174
+
175
+ .dialog-content {
176
+ min-height: 0;
177
+ padding: 20px;
178
+ overflow-y: auto;
179
+ }
180
+
181
+ .dialog-title {
182
+ margin: 0;
183
+ padding-right: 32px;
184
+ font-size: 15px;
185
+ font-weight: 600;
186
+ }
187
+
188
+ .dialog-description {
189
+ margin: 6px 0 0;
190
+ font-size: 13px;
191
+ line-height: 1.5;
192
+ color: var(--color-text-muted);
193
+ }
194
+
195
+ .dialog-body {
196
+ margin-top: 16px;
197
+ font-size: 13px;
198
+ }
199
+
200
+ .dialog-actions {
201
+ display: flex;
202
+ flex-wrap: wrap;
203
+ justify-content: flex-end;
204
+ gap: 4px;
205
+ padding: 16px;
206
+ border-top: 1px solid var(--color-border);
207
+ }
208
+
209
+ .dialog-dimmed {
210
+ opacity: 0.4;
211
+ user-select: none;
212
+ }
213
+
214
+ .dialog :global(.dialog-spinner) {
215
+ position: absolute;
216
+ inset: 0;
217
+ margin: auto;
218
+ }
219
+
220
+ .dialog :global(.dialog-close) {
221
+ position: absolute;
222
+ top: 14px;
223
+ right: 14px;
224
+ }
225
+ </style>
@@ -0,0 +1,22 @@
1
+ export type DialogTriggerProps = {
2
+ onclick: () => void;
3
+ 'aria-haspopup': 'dialog';
4
+ };
5
+ import type { Snippet } from 'svelte';
6
+ type Props = {
7
+ /** Whether the dialog is open. Bindable. Escape, the close button and a click on the backdrop close it. */
8
+ open?: boolean;
9
+ title: string;
10
+ description?: string;
11
+ /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. Omit to control `open` yourself. */
12
+ trigger?: Snippet<[DialogTriggerProps]>;
13
+ /** The dialog's content. The first focusable element in it gets the focus on open. */
14
+ children?: Snippet;
15
+ /** Optional Buttons in the footer, right-aligned with the primary one last (rightmost). Call `close` to close the dialog. */
16
+ actions?: Snippet<[close: () => void]>;
17
+ /** Grays out the content and actions, makes them inert and shows a centered Spinner. The close button keeps working. */
18
+ loading?: boolean;
19
+ };
20
+ declare const Dialog: import("svelte").Component<Props, {}, "open">;
21
+ type Dialog = ReturnType<typeof Dialog>;
22
+ export default Dialog;
@@ -0,0 +1,78 @@
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'> & {
6
+ /** `responsive` is vertical and switches to horizontal when the surrounding FieldGroup is wider than 448px. */
7
+ orientation?: 'vertical' | 'horizontal' | 'responsive';
8
+ /** A Label, the control, and optionally FieldDescription and FieldError. Horizontal fields wrap label and description in a FieldContent. */
9
+ children: Snippet;
10
+ };
11
+
12
+ let { orientation = 'vertical', class: className, children, ...rest }: Props = $props();
13
+ </script>
14
+
15
+ <div {...rest} role="group" class={['field', `field--${orientation}`, className]}>
16
+ {@render children()}
17
+ </div>
18
+
19
+ <style>
20
+ .field {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 8px;
24
+ width: 100%;
25
+ font-size: 13px;
26
+ color: var(--color-text);
27
+ }
28
+
29
+ .field--horizontal {
30
+ flex-direction: row;
31
+ align-items: center;
32
+ }
33
+
34
+ .field--horizontal > :global(.label) {
35
+ flex: auto;
36
+ }
37
+
38
+ .field--horizontal:has(> :global(.field-content)) {
39
+ align-items: flex-start;
40
+ }
41
+
42
+ .field--horizontal:has(> :global(.field-content)) > :global(:is(.checkbox, .radio)) {
43
+ margin-top: 2px;
44
+ }
45
+
46
+ @container field-group (min-width: 448px) {
47
+ .field--responsive {
48
+ flex-direction: row;
49
+ align-items: center;
50
+ }
51
+
52
+ .field--responsive > :global(:is(.label, .field-content)) {
53
+ flex: auto;
54
+ }
55
+
56
+ .field--responsive:has(> :global(.field-content)) {
57
+ align-items: flex-start;
58
+ }
59
+ }
60
+
61
+ /* State follows the control, so there is no invalid or disabled prop to keep in sync */
62
+ .field:has(:global([aria-invalid='true'])) :global(.label) {
63
+ color: var(--color-destructive);
64
+ }
65
+
66
+ .field:has(:global(:is(.checkbox, .radio, .switch))) :global(.label) {
67
+ cursor: pointer;
68
+ }
69
+
70
+ /* The control dims itself */
71
+ .field:has(:global(:disabled)) > :global(:is(.label, .field-description, .field-content)) {
72
+ opacity: 0.5;
73
+ }
74
+
75
+ .field:has(:global(:disabled)) :global(.label) {
76
+ cursor: not-allowed;
77
+ }
78
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** `responsive` is vertical and switches to horizontal when the surrounding FieldGroup is wider than 448px. */
5
+ orientation?: 'vertical' | 'horizontal' | 'responsive';
6
+ /** A Label, the control, and optionally FieldDescription and FieldError. Horizontal fields wrap label and description in a FieldContent. */
7
+ children: Snippet;
8
+ };
9
+ declare const Field: import("svelte").Component<Props, {}, "">;
10
+ type Field = ReturnType<typeof Field>;
11
+ export default Field;
@@ -0,0 +1,25 @@
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'> & {
6
+ /** Label, FieldDescription and FieldError next to the control in a horizontal Field. */
7
+ children: Snippet;
8
+ };
9
+
10
+ let { class: className, children, ...rest }: Props = $props();
11
+ </script>
12
+
13
+ <div {...rest} class={['field-content', className]}>
14
+ {@render children()}
15
+ </div>
16
+
17
+ <style>
18
+ .field-content {
19
+ display: flex;
20
+ flex: 1;
21
+ flex-direction: column;
22
+ gap: 4px;
23
+ min-width: 0;
24
+ }
25
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** Label, FieldDescription and FieldError next to the control in a horizontal Field. */
5
+ children: Snippet;
6
+ };
7
+ declare const FieldContent: import("svelte").Component<Props, {}, "">;
8
+ type FieldContent = ReturnType<typeof FieldContent>;
9
+ export default FieldContent;
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ type Props = Omit<HTMLAttributes<HTMLParagraphElement>, 'children'> & {
6
+ /** Give it an id and reference it from the control's `aria-describedby`. */
7
+ children: Snippet;
8
+ };
9
+
10
+ let { class: className, children, ...rest }: Props = $props();
11
+ </script>
12
+
13
+ <p {...rest} class={['field-description', className]}>
14
+ {@render children()}
15
+ </p>
16
+
17
+ <style>
18
+ .field-description {
19
+ margin: 0;
20
+ font-size: 12px;
21
+ line-height: 1.5;
22
+ color: var(--color-text-muted);
23
+ }
24
+
25
+ .field-description :global(a) {
26
+ color: inherit;
27
+ text-decoration: underline;
28
+ text-underline-offset: 3px;
29
+ }
30
+
31
+ .field-description :global(a:hover) {
32
+ color: var(--color-text);
33
+ }
34
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLParagraphElement>, 'children'> & {
4
+ /** Give it an id and reference it from the control's `aria-describedby`. */
5
+ children: Snippet;
6
+ };
7
+ declare const FieldDescription: import("svelte").Component<Props, {}, "">;
8
+ type FieldDescription = ReturnType<typeof FieldDescription>;
9
+ export default FieldDescription;
@@ -0,0 +1,45 @@
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'> & {
6
+ /** Messages from validation. Duplicates are dropped, several render as a list. */
7
+ errors?: (string | undefined)[];
8
+ /** Custom content instead of `errors`. */
9
+ children?: Snippet;
10
+ };
11
+
12
+ let { errors = [], class: className, children, ...rest }: Props = $props();
13
+
14
+ let messages = $derived([...new Set(errors.filter(Boolean))]);
15
+ </script>
16
+
17
+ {#if children || messages.length}
18
+ <div {...rest} role="alert" class={['field-error', className]}>
19
+ {#if children}
20
+ {@render children()}
21
+ {:else if messages.length === 1}
22
+ {messages[0]}
23
+ {:else}
24
+ <ul>
25
+ {#each messages as message (message)}<li>{message}</li>{/each}
26
+ </ul>
27
+ {/if}
28
+ </div>
29
+ {/if}
30
+
31
+ <style>
32
+ .field-error {
33
+ font-size: 12px;
34
+ line-height: 1.5;
35
+ color: var(--color-destructive);
36
+ }
37
+
38
+ ul {
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 2px;
42
+ margin: 0;
43
+ padding-left: 16px;
44
+ }
45
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** Messages from validation. Duplicates are dropped, several render as a list. */
5
+ errors?: (string | undefined)[];
6
+ /** Custom content instead of `errors`. */
7
+ children?: Snippet;
8
+ };
9
+ declare const FieldError: import("svelte").Component<Props, {}, "">;
10
+ type FieldError = ReturnType<typeof FieldError>;
11
+ export default FieldError;
@@ -0,0 +1,30 @@
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'> & {
6
+ /** Fields, FieldSets and FieldSeparators. */
7
+ children: Snippet;
8
+ };
9
+
10
+ let { class: className, children, ...rest }: Props = $props();
11
+ </script>
12
+
13
+ <div {...rest} class={['field-group', className]}>
14
+ {@render children()}
15
+ </div>
16
+
17
+ <style>
18
+ .field-group {
19
+ container: field-group / inline-size;
20
+ display: flex;
21
+ flex-direction: column;
22
+ gap: 24px;
23
+ width: 100%;
24
+ }
25
+
26
+ /* Tighter spacing for lists of checkboxes or radios */
27
+ .field-group:has(> :global(.field--horizontal)):not(:has(> :global(:not(.field--horizontal)))) {
28
+ gap: 12px;
29
+ }
30
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /** Fields, FieldSets and FieldSeparators. */
5
+ children: Snippet;
6
+ };
7
+ declare const FieldGroup: import("svelte").Component<Props, {}, "">;
8
+ type FieldGroup = ReturnType<typeof FieldGroup>;
9
+ export default FieldGroup;
@@ -0,0 +1,34 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ type Props = Omit<HTMLAttributes<HTMLLegendElement>, 'children'> & {
6
+ /** `label` matches a Label, for a group of checkboxes or radios. */
7
+ variant?: 'legend' | 'label';
8
+ children: Snippet;
9
+ };
10
+
11
+ let { variant = 'legend', class: className, children, ...rest }: Props = $props();
12
+ </script>
13
+
14
+ <legend {...rest} class={['field-legend', `field-legend--${variant}`, className]}>
15
+ {@render children()}
16
+ </legend>
17
+
18
+ <style>
19
+ .field-legend {
20
+ margin-bottom: 12px;
21
+ padding: 0;
22
+ font-weight: 600;
23
+ line-height: 1.4;
24
+ }
25
+
26
+ .field-legend--legend {
27
+ font-size: 15px;
28
+ }
29
+
30
+ .field-legend--label {
31
+ font-size: 13px;
32
+ font-weight: 500;
33
+ }
34
+ </style>