@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,151 @@
1
+ <script lang="ts">
2
+ import type { Attachment } from 'svelte/attachments';
3
+ import { fly } from 'svelte/transition';
4
+ import Button from '../button/Button.svelte';
5
+ import Spinner from '../spinner/Spinner.svelte';
6
+ import { toast, toasts, type ToastData } from './toast.svelte.js';
7
+
8
+ type Props = {
9
+ /** Accessible name of the notification region. */
10
+ label?: string;
11
+ };
12
+
13
+ let { label = 'Notifications' }: Props = $props();
14
+
15
+ // Hovering or focusing the toast pauses its timer; they restart on leave.
16
+ let paused = $state(false);
17
+
18
+ // Re-show on every new toast so it stays above dialogs opened in the meantime.
19
+ const topLayer: Attachment<HTMLOListElement> = (node) => {
20
+ if (toasts.length === 0) return;
21
+ node.hidePopover();
22
+ node.showPopover();
23
+ };
24
+
25
+ const exitDuration = 150;
26
+
27
+ // Waits for the outgoing toast, still in the DOM, to slide out first.
28
+ function enter(node: HTMLElement) {
29
+ const replacing = node.parentElement!.childElementCount > 1;
30
+ return fly(node, { y: 24, duration: 200, delay: replacing ? exitDuration : 0 });
31
+ }
32
+
33
+ function timer(data: ToastData): Attachment {
34
+ return () => {
35
+ const duration = data.duration ?? (data.loading ? Infinity : 3000);
36
+ if (paused || duration === Infinity) return;
37
+ const timeout = setTimeout(() => toast.dismiss(data.id), duration);
38
+ return () => clearTimeout(timeout);
39
+ };
40
+ }
41
+ </script>
42
+
43
+ <ol
44
+ class="toaster"
45
+ aria-label={label}
46
+ aria-live="polite"
47
+ popover="manual"
48
+ onpointerenter={() => (paused = true)}
49
+ onpointerleave={() => (paused = false)}
50
+ onfocusin={() => (paused = true)}
51
+ onfocusout={() => (paused = false)}
52
+ {@attach topLayer}
53
+ >
54
+ {#each toasts as data (data.id)}
55
+ <!-- Pointer convenience only; the toast is not focusable and closes on its own -->
56
+ <!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
57
+ <li
58
+ class="toast"
59
+ onclick={() => toast.dismiss(data.id)}
60
+ aria-busy={data.loading || undefined}
61
+ {@attach timer(data)}
62
+ in:enter
63
+ out:fly={{ y: 24, duration: exitDuration }}
64
+ >
65
+ {#if data.loading}
66
+ <Spinner class="toast-spinner" aria-hidden="true" />
67
+ {:else if data.icon}
68
+ <data.icon class="toast-icon" aria-hidden="true" />
69
+ {/if}
70
+ <span class="toast-message">{data.message}</span>
71
+ {#if data.action}
72
+ {@const action = data.action}
73
+ <Button class="toast-action" variant="secondary" size="sm" onclick={action.onclick}>
74
+ {action.label}
75
+ </Button>
76
+ {/if}
77
+ </li>
78
+ {/each}
79
+ </ol>
80
+
81
+ <style>
82
+ .toaster {
83
+ position: fixed;
84
+ inset: auto 0 16px;
85
+ /* One cell: the outgoing toast overlaps the incoming one instead of stacking */
86
+ display: grid;
87
+ /* minmax(0, …) keeps a long message from widening the column past the screen */
88
+ grid-template-columns: minmax(0, 1fr);
89
+ justify-items: center;
90
+ align-items: end;
91
+ width: auto;
92
+ height: auto;
93
+ margin: 0;
94
+ padding: 0 16px;
95
+ overflow: visible;
96
+ list-style: none;
97
+ background: none;
98
+ border: none;
99
+ pointer-events: none;
100
+ }
101
+
102
+ .toast {
103
+ grid-area: 1 / 1;
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 10px;
107
+ box-sizing: border-box;
108
+ max-width: min(420px, 100%);
109
+ height: 44px;
110
+ padding: 8px 16px 8px 14px;
111
+ font-size: 13px;
112
+ line-height: 1.4;
113
+ background-color: var(--color-bg);
114
+ color: var(--color-text);
115
+ border: 1px solid var(--color-border);
116
+ border-radius: 16px;
117
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
118
+ pointer-events: auto;
119
+ cursor: pointer;
120
+ }
121
+
122
+ /* Same gap to the right edge as above and below the 28px button: (44 - 2 - 28) / 2 */
123
+ .toast:has(:global(.toast-action)) {
124
+ padding-right: 7px;
125
+ }
126
+
127
+ /* Same sizes as in a Button */
128
+ .toast :global(.toast-icon) {
129
+ width: 16px;
130
+ height: 16px;
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ .toast :global(.toast-spinner) {
135
+ --spinner-size: 14px;
136
+ margin: 0 1px;
137
+ }
138
+
139
+ .toast-message {
140
+ flex: 1;
141
+ min-width: 0;
142
+ overflow: hidden;
143
+ white-space: nowrap;
144
+ text-overflow: ellipsis;
145
+ }
146
+
147
+ .toast :global(.toast-action) {
148
+ flex-shrink: 0;
149
+ margin-left: 6px;
150
+ }
151
+ </style>
@@ -0,0 +1,7 @@
1
+ type Props = {
2
+ /** Accessible name of the notification region. */
3
+ label?: string;
4
+ };
5
+ declare const Toaster: import("svelte").Component<Props, {}, "">;
6
+ type Toaster = ReturnType<typeof Toaster>;
7
+ export default Toaster;
@@ -0,0 +1,28 @@
1
+ import type { LucideIcon } from '@lucide/svelte';
2
+ export type ToastAction = {
3
+ label: string;
4
+ onclick: () => void;
5
+ };
6
+ export type ToastOptions = {
7
+ /** Updates the toast with this id instead of adding a new one. */
8
+ id?: number;
9
+ /** Any Lucide icon (import from `svelte-ui/icons`), shown on the left. */
10
+ icon?: LucideIcon;
11
+ /** Shows a Spinner instead of the icon and keeps the toast open until it is updated. */
12
+ loading?: boolean;
13
+ /** Button on the right; clicking it runs `onclick` and dismisses the toast. */
14
+ action?: ToastAction;
15
+ /** Milliseconds until the toast closes. `Infinity` keeps it open. Default 3000.
16
+ * Clicking the toast always closes it. */
17
+ duration?: number;
18
+ };
19
+ export type ToastData = Omit<ToastOptions, 'id'> & {
20
+ id: number;
21
+ message: string;
22
+ };
23
+ export declare const toasts: ToastData[];
24
+ /** Shows a toast in the `Toaster`, replacing the current one, and returns its id. */
25
+ export declare function toast(message: string, options?: ToastOptions): number;
26
+ export declare namespace toast {
27
+ var dismiss: (id?: number) => void;
28
+ }
@@ -0,0 +1,22 @@
1
+ let nextId = 1;
2
+ export const toasts = $state([]);
3
+ /** Shows a toast in the `Toaster`, replacing the current one, and returns its id. */
4
+ export function toast(message, options = {}) {
5
+ const { id = nextId++, ...rest } = options;
6
+ const data = { id, message, ...rest };
7
+ const index = toasts.findIndex((t) => t.id === id);
8
+ // Only the newest toast is shown, so a new one replaces the current one.
9
+ if (index === -1)
10
+ toasts.splice(0, toasts.length, data);
11
+ else
12
+ toasts[index] = data;
13
+ return id;
14
+ }
15
+ /** Closes the toast with this id, or all toasts when called without one. */
16
+ toast.dismiss = (id) => {
17
+ if (id === undefined)
18
+ toasts.length = 0;
19
+ const index = toasts.findIndex((t) => t.id === id);
20
+ if (index !== -1)
21
+ toasts.splice(index, 1);
22
+ };
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import type { ComponentProps } from 'svelte';
3
+ import Button from '../button/Button.svelte';
4
+
5
+ type Props = Omit<
6
+ ComponentProps<typeof Button>,
7
+ 'href' | 'target' | 'rel' | 'type' | 'variant'
8
+ > & {
9
+ pressed?: boolean;
10
+ };
11
+
12
+ let { pressed = $bindable(false), class: className, onclick, ...rest }: Props = $props();
13
+ </script>
14
+
15
+ <Button
16
+ {...rest}
17
+ variant="secondary"
18
+ aria-pressed={pressed}
19
+ class={['toggle', className]}
20
+ onclick={(event) => {
21
+ pressed = !pressed;
22
+ onclick?.(event);
23
+ }}
24
+ />
25
+
26
+ <style>
27
+ :global(.button.toggle[aria-pressed='true']),
28
+ :global(.button.toggle[aria-pressed='true']:hover:not(:disabled)) {
29
+ background-color: var(--color-surface-active);
30
+ }
31
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { ComponentProps } from 'svelte';
2
+ import Button from '../button/Button.svelte';
3
+ type Props = Omit<ComponentProps<typeof Button>, 'href' | 'target' | 'rel' | 'type' | 'variant'> & {
4
+ pressed?: boolean;
5
+ };
6
+ declare const Toggle: import("svelte").Component<Props, {}, "pressed">;
7
+ type Toggle = ReturnType<typeof Toggle>;
8
+ export default Toggle;
@@ -0,0 +1,224 @@
1
+ <script module lang="ts">
2
+ import { createAttachmentKey, type Attachment } from 'svelte/attachments';
3
+
4
+ export type TooltipTriggerProps = {
5
+ onpointerenter: (event: PointerEvent) => void;
6
+ onpointerleave: () => void;
7
+ onpointerdown: () => void;
8
+ onfocus: () => void;
9
+ onblur: () => void;
10
+ 'aria-describedby': string | undefined;
11
+ [key: symbol]: Attachment<HTMLElement>;
12
+ };
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import type { Snippet } from 'svelte';
17
+ import { fade } from 'svelte/transition';
18
+
19
+ type Side = 'top' | 'right' | 'bottom' | 'left';
20
+
21
+ type Props = {
22
+ /** Whether the tooltip is shown. Bindable. */
23
+ open?: boolean;
24
+ /** Preferred side; flips to the opposite one when there is not enough room. */
25
+ side?: Side;
26
+ /** Milliseconds to wait on hover before showing. Focus shows immediately. */
27
+ delay?: number;
28
+ /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. */
29
+ trigger: Snippet<[TooltipTriggerProps]>;
30
+ /** Tooltip content; may contain `Kbd` / `KbdGroup`. */
31
+ children: Snippet;
32
+ };
33
+
34
+ let { open = $bindable(false), side = 'top', delay = 0, trigger, children }: Props = $props();
35
+
36
+ const id = $props.id();
37
+ const gap = 6;
38
+ const edge = 8;
39
+ const opposite = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' } as const;
40
+
41
+ let triggerEl: HTMLElement | undefined;
42
+ let placement: Side = $state('top');
43
+ let position = $state({ top: 0, left: 0, arrow: 0 });
44
+ let timer: ReturnType<typeof setTimeout> | undefined;
45
+ // A click dismisses the tooltip until the pointer leaves the trigger.
46
+ let pressed = false;
47
+
48
+ function show(wait: number) {
49
+ clearTimeout(timer);
50
+ if (pressed) return;
51
+ if (wait > 0) timer = setTimeout(() => (open = true), wait);
52
+ else open = true;
53
+ }
54
+
55
+ function hide() {
56
+ clearTimeout(timer);
57
+ open = false;
58
+ }
59
+
60
+ const triggerProps: TooltipTriggerProps = $derived({
61
+ onpointerenter: (event: PointerEvent) => {
62
+ if (event.pointerType !== 'touch') show(delay);
63
+ },
64
+ onpointerleave: () => {
65
+ pressed = false;
66
+ hide();
67
+ },
68
+ onpointerdown: () => {
69
+ pressed = true;
70
+ hide();
71
+ },
72
+ onfocus: () => {
73
+ if (triggerEl?.matches(':focus-visible')) show(0);
74
+ },
75
+ onblur: hide,
76
+ 'aria-describedby': open ? `${id}-tooltip` : undefined,
77
+ [createAttachmentKey()]: (node: HTMLElement) => {
78
+ triggerEl = node;
79
+ return () => clearTimeout(timer);
80
+ }
81
+ });
82
+
83
+ function fits(rect: DOMRect, width: number, height: number, s: Side) {
84
+ if (s === 'top') return rect.top - gap - height >= edge;
85
+ if (s === 'bottom') return rect.bottom + gap + height <= window.innerHeight - edge;
86
+ if (s === 'left') return rect.left - gap - width >= edge;
87
+ return rect.right + gap + width <= window.innerWidth - edge;
88
+ }
89
+
90
+ function place(node: HTMLElement) {
91
+ if (!triggerEl) return;
92
+ const rect = triggerEl.getBoundingClientRect();
93
+ const { offsetWidth: width, offsetHeight: height } = node;
94
+ const s =
95
+ fits(rect, width, height, side) || !fits(rect, width, height, opposite[side])
96
+ ? side
97
+ : opposite[side];
98
+ const clamp = (value: number, max: number) => Math.min(Math.max(value, edge), max - edge);
99
+
100
+ if (s === 'top' || s === 'bottom') {
101
+ const center = rect.left + rect.width / 2;
102
+ const left = clamp(center - width / 2, window.innerWidth - width);
103
+ const top = s === 'top' ? rect.top - gap - height : rect.bottom + gap;
104
+ position = { top, left, arrow: center - left };
105
+ } else {
106
+ const center = rect.top + rect.height / 2;
107
+ const top = clamp(center - height / 2, window.innerHeight - height);
108
+ const left = s === 'left' ? rect.left - gap - width : rect.right + gap;
109
+ position = { top, left, arrow: center - top };
110
+ }
111
+ placement = s;
112
+ }
113
+
114
+ const popover: Attachment<HTMLDivElement> = (node) => {
115
+ node.showPopover();
116
+ place(node);
117
+ };
118
+
119
+ $effect(() => {
120
+ if (!open) return;
121
+
122
+ function handleKeydown(event: KeyboardEvent) {
123
+ if (event.key === 'Escape') hide();
124
+ }
125
+
126
+ window.addEventListener('keydown', handleKeydown);
127
+ window.addEventListener('scroll', hide, true);
128
+ window.addEventListener('resize', hide);
129
+
130
+ return () => {
131
+ window.removeEventListener('keydown', handleKeydown);
132
+ window.removeEventListener('scroll', hide, true);
133
+ window.removeEventListener('resize', hide);
134
+ };
135
+ });
136
+ </script>
137
+
138
+ <span class="tooltip">
139
+ {@render trigger(triggerProps)}
140
+
141
+ {#if open}
142
+ <div
143
+ id="{id}-tooltip"
144
+ role="tooltip"
145
+ popover="manual"
146
+ class={['tooltip-content', `tooltip-content--${placement}`]}
147
+ style:top="{position.top}px"
148
+ style:left="{position.left}px"
149
+ style:--tooltip-arrow="{position.arrow}px"
150
+ {@attach popover}
151
+ transition:fade={{ duration: 100 }}
152
+ >
153
+ {@render children()}
154
+ <span class="tooltip-arrow" aria-hidden="true"></span>
155
+ </div>
156
+ {/if}
157
+ </span>
158
+
159
+ <style>
160
+ .tooltip {
161
+ display: inline-flex;
162
+ height: fit-content;
163
+ }
164
+
165
+ .tooltip-content {
166
+ position: fixed;
167
+ inset: auto;
168
+ margin: 0;
169
+ display: inline-flex;
170
+ align-items: center;
171
+ gap: 6px;
172
+ width: max-content;
173
+ max-width: 280px;
174
+ padding: 6px 10px;
175
+ overflow: visible;
176
+ font-size: 12px;
177
+ line-height: 1.4;
178
+ text-wrap: balance;
179
+ background-color: var(--color-ink);
180
+ color: var(--color-base);
181
+ border: none;
182
+ border-radius: 10px;
183
+ pointer-events: none;
184
+ }
185
+
186
+ .tooltip-content :global(:is(.kbd, .kbd-group)) {
187
+ font-size: inherit;
188
+ }
189
+
190
+ .tooltip-arrow {
191
+ position: absolute;
192
+ width: 8px;
193
+ height: 8px;
194
+ background-color: inherit;
195
+ border-radius: 2px;
196
+ transform: rotate(45deg);
197
+ }
198
+
199
+ .tooltip-content--top .tooltip-arrow,
200
+ .tooltip-content--bottom .tooltip-arrow {
201
+ left: calc(var(--tooltip-arrow) - 4px);
202
+ }
203
+
204
+ .tooltip-content--left .tooltip-arrow,
205
+ .tooltip-content--right .tooltip-arrow {
206
+ top: calc(var(--tooltip-arrow) - 4px);
207
+ }
208
+
209
+ .tooltip-content--top .tooltip-arrow {
210
+ bottom: -3px;
211
+ }
212
+
213
+ .tooltip-content--bottom .tooltip-arrow {
214
+ top: -3px;
215
+ }
216
+
217
+ .tooltip-content--left .tooltip-arrow {
218
+ right: -3px;
219
+ }
220
+
221
+ .tooltip-content--right .tooltip-arrow {
222
+ left: -3px;
223
+ }
224
+ </style>
@@ -0,0 +1,27 @@
1
+ import { type Attachment } from 'svelte/attachments';
2
+ export type TooltipTriggerProps = {
3
+ onpointerenter: (event: PointerEvent) => void;
4
+ onpointerleave: () => void;
5
+ onpointerdown: () => void;
6
+ onfocus: () => void;
7
+ onblur: () => void;
8
+ 'aria-describedby': string | undefined;
9
+ [key: symbol]: Attachment<HTMLElement>;
10
+ };
11
+ import type { Snippet } from 'svelte';
12
+ type Side = 'top' | 'right' | 'bottom' | 'left';
13
+ type Props = {
14
+ /** Whether the tooltip is shown. Bindable. */
15
+ open?: boolean;
16
+ /** Preferred side; flips to the opposite one when there is not enough room. */
17
+ side?: Side;
18
+ /** Milliseconds to wait on hover before showing. Focus shows immediately. */
19
+ delay?: number;
20
+ /** Renders the trigger. Spread the given props onto it, e.g. `<Button {...props} />`. */
21
+ trigger: Snippet<[TooltipTriggerProps]>;
22
+ /** Tooltip content; may contain `Kbd` / `KbdGroup`. */
23
+ children: Snippet;
24
+ };
25
+ declare const Tooltip: import("svelte").Component<Props, {}, "open">;
26
+ type Tooltip = ReturnType<typeof Tooltip>;
27
+ export default Tooltip;
@@ -0,0 +1,2 @@
1
+ export * from '@lucide/svelte';
2
+ export type { LucideIcon, LucideProps } from '@lucide/svelte';
package/dist/icons.js ADDED
@@ -0,0 +1,4 @@
1
+ // Lucide icons, re-exported under `svelte-ui/icons` so consumers don't need
2
+ // their own @lucide/svelte install. Kept off the root entry because icon names
3
+ // (Link, Tag, Image, …) would collide with component names.
4
+ export * from '@lucide/svelte';
@@ -0,0 +1,73 @@
1
+ export { default as AlertDialog } from './components/alert-dialog/AlertDialog.svelte';
2
+ export { default as Avatar } from './components/avatar/Avatar.svelte';
3
+ export { default as Badge } from './components/badge/Badge.svelte';
4
+ export { default as Breadcrumb } from './components/breadcrumb/Breadcrumb.svelte';
5
+ export { default as BreadcrumbEllipsis } from './components/breadcrumb/BreadcrumbEllipsis.svelte';
6
+ export { default as BreadcrumbItem } from './components/breadcrumb/BreadcrumbItem.svelte';
7
+ export { default as Button } from './components/button/Button.svelte';
8
+ export { default as ButtonGroup } from './components/button-group/ButtonGroup.svelte';
9
+ export { default as Calendar } from './components/calendar/Calendar.svelte';
10
+ export { default as Card } from './components/card/Card.svelte';
11
+ export { default as CardAction } from './components/card/CardAction.svelte';
12
+ export { default as CardContent } from './components/card/CardContent.svelte';
13
+ export { default as CardDescription } from './components/card/CardDescription.svelte';
14
+ export { default as CardFooter } from './components/card/CardFooter.svelte';
15
+ export { default as CardHeader } from './components/card/CardHeader.svelte';
16
+ export { default as CardTitle } from './components/card/CardTitle.svelte';
17
+ export { default as Checkbox } from './components/checkbox/Checkbox.svelte';
18
+ export { default as ColorPicker } from './components/color-picker/ColorPicker.svelte';
19
+ export { default as ContextMenu } from './components/context-menu/ContextMenu.svelte';
20
+ export { default as ContextMenuItem } from './components/context-menu/ContextMenuItem.svelte';
21
+ export { default as Dialog } from './components/dialog/Dialog.svelte';
22
+ export { default as Field } from './components/field/Field.svelte';
23
+ export { default as FieldContent } from './components/field/FieldContent.svelte';
24
+ export { default as FieldDescription } from './components/field/FieldDescription.svelte';
25
+ export { default as FieldError } from './components/field/FieldError.svelte';
26
+ export { default as FieldGroup } from './components/field/FieldGroup.svelte';
27
+ export { default as FieldLegend } from './components/field/FieldLegend.svelte';
28
+ export { default as FieldSeparator } from './components/field/FieldSeparator.svelte';
29
+ export { default as FieldSet } from './components/field/FieldSet.svelte';
30
+ export { default as Input } from './components/input/Input.svelte';
31
+ export { default as Kbd } from './components/kbd/Kbd.svelte';
32
+ export { default as KbdGroup } from './components/kbd/KbdGroup.svelte';
33
+ export { default as InputGroup } from './components/input-group/InputGroup.svelte';
34
+ export { default as InputGroupAddon } from './components/input-group/InputGroupAddon.svelte';
35
+ export { default as Label } from './components/label/Label.svelte';
36
+ export { default as Link } from './components/link/Link.svelte';
37
+ export { default as Navbar } from './components/navbar/Navbar.svelte';
38
+ export { default as NavigationMenu } from './components/navigation-menu/NavigationMenu.svelte';
39
+ export { default as NavigationMenuItem } from './components/navigation-menu/NavigationMenuItem.svelte';
40
+ export { default as NavigationMenuLink } from './components/navigation-menu/NavigationMenuLink.svelte';
41
+ export { default as Popover } from './components/popover/Popover.svelte';
42
+ export { default as RadioGroup } from './components/radio-group/RadioGroup.svelte';
43
+ export { default as RadioGroupItem } from './components/radio-group/RadioGroupItem.svelte';
44
+ export { default as Separator } from './components/separator/Separator.svelte';
45
+ export { default as Sidebar } from './components/sidebar/Sidebar.svelte';
46
+ export { default as SidebarContent } from './components/sidebar/SidebarContent.svelte';
47
+ export { default as SidebarFooter } from './components/sidebar/SidebarFooter.svelte';
48
+ export { default as SidebarGroup } from './components/sidebar/SidebarGroup.svelte';
49
+ export { default as SidebarHeader } from './components/sidebar/SidebarHeader.svelte';
50
+ export { default as SidebarInset } from './components/sidebar/SidebarInset.svelte';
51
+ export { default as SidebarMenu } from './components/sidebar/SidebarMenu.svelte';
52
+ export { default as SidebarMenuItem } from './components/sidebar/SidebarMenuItem.svelte';
53
+ export { default as SidebarProvider } from './components/sidebar/SidebarProvider.svelte';
54
+ export { default as SidebarSeparator } from './components/sidebar/SidebarSeparator.svelte';
55
+ export { default as SidebarTrigger } from './components/sidebar/SidebarTrigger.svelte';
56
+ export type { AlertDialogTriggerProps } from './components/alert-dialog/AlertDialog.svelte';
57
+ export { default as Skeleton } from './components/skeleton/Skeleton.svelte';
58
+ export { default as Spinner } from './components/spinner/Spinner.svelte';
59
+ export { default as Switch } from './components/switch/Switch.svelte';
60
+ export { default as Table, type TableColumn, type TableSort, type TableSortValue } from './components/table/Table.svelte';
61
+ export { default as Textarea } from './components/textarea/Textarea.svelte';
62
+ export { default as Toaster } from './components/toaster/Toaster.svelte';
63
+ export { toast, type ToastOptions, type ToastAction } from './components/toaster/toast.svelte.js';
64
+ export { default as Toggle } from './components/toggle/Toggle.svelte';
65
+ export { default as Tooltip } from './components/tooltip/Tooltip.svelte';
66
+ export { default as TimePicker } from './components/time-picker/TimePicker.svelte';
67
+ export type { NavbarTriggerProps } from './components/navbar/Navbar.svelte';
68
+ export type { PopoverTriggerProps } from './components/popover/Popover.svelte';
69
+ export type { TooltipTriggerProps } from './components/tooltip/Tooltip.svelte';
70
+ export type { InputType } from './components/input/Input.svelte';
71
+ export type { DialogTriggerProps } from './components/dialog/Dialog.svelte';
72
+ export type { ContextMenuTriggerProps } from './components/context-menu/context.js';
73
+ export { getSidebarContext, type SidebarContext } from './components/sidebar/context.js';
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ export { default as AlertDialog } from './components/alert-dialog/AlertDialog.svelte';
2
+ export { default as Avatar } from './components/avatar/Avatar.svelte';
3
+ export { default as Badge } from './components/badge/Badge.svelte';
4
+ export { default as Breadcrumb } from './components/breadcrumb/Breadcrumb.svelte';
5
+ export { default as BreadcrumbEllipsis } from './components/breadcrumb/BreadcrumbEllipsis.svelte';
6
+ export { default as BreadcrumbItem } from './components/breadcrumb/BreadcrumbItem.svelte';
7
+ export { default as Button } from './components/button/Button.svelte';
8
+ export { default as ButtonGroup } from './components/button-group/ButtonGroup.svelte';
9
+ export { default as Calendar } from './components/calendar/Calendar.svelte';
10
+ export { default as Card } from './components/card/Card.svelte';
11
+ export { default as CardAction } from './components/card/CardAction.svelte';
12
+ export { default as CardContent } from './components/card/CardContent.svelte';
13
+ export { default as CardDescription } from './components/card/CardDescription.svelte';
14
+ export { default as CardFooter } from './components/card/CardFooter.svelte';
15
+ export { default as CardHeader } from './components/card/CardHeader.svelte';
16
+ export { default as CardTitle } from './components/card/CardTitle.svelte';
17
+ export { default as Checkbox } from './components/checkbox/Checkbox.svelte';
18
+ export { default as ColorPicker } from './components/color-picker/ColorPicker.svelte';
19
+ export { default as ContextMenu } from './components/context-menu/ContextMenu.svelte';
20
+ export { default as ContextMenuItem } from './components/context-menu/ContextMenuItem.svelte';
21
+ export { default as Dialog } from './components/dialog/Dialog.svelte';
22
+ export { default as Field } from './components/field/Field.svelte';
23
+ export { default as FieldContent } from './components/field/FieldContent.svelte';
24
+ export { default as FieldDescription } from './components/field/FieldDescription.svelte';
25
+ export { default as FieldError } from './components/field/FieldError.svelte';
26
+ export { default as FieldGroup } from './components/field/FieldGroup.svelte';
27
+ export { default as FieldLegend } from './components/field/FieldLegend.svelte';
28
+ export { default as FieldSeparator } from './components/field/FieldSeparator.svelte';
29
+ export { default as FieldSet } from './components/field/FieldSet.svelte';
30
+ export { default as Input } from './components/input/Input.svelte';
31
+ export { default as Kbd } from './components/kbd/Kbd.svelte';
32
+ export { default as KbdGroup } from './components/kbd/KbdGroup.svelte';
33
+ export { default as InputGroup } from './components/input-group/InputGroup.svelte';
34
+ export { default as InputGroupAddon } from './components/input-group/InputGroupAddon.svelte';
35
+ export { default as Label } from './components/label/Label.svelte';
36
+ export { default as Link } from './components/link/Link.svelte';
37
+ export { default as Navbar } from './components/navbar/Navbar.svelte';
38
+ export { default as NavigationMenu } from './components/navigation-menu/NavigationMenu.svelte';
39
+ export { default as NavigationMenuItem } from './components/navigation-menu/NavigationMenuItem.svelte';
40
+ export { default as NavigationMenuLink } from './components/navigation-menu/NavigationMenuLink.svelte';
41
+ export { default as Popover } from './components/popover/Popover.svelte';
42
+ export { default as RadioGroup } from './components/radio-group/RadioGroup.svelte';
43
+ export { default as RadioGroupItem } from './components/radio-group/RadioGroupItem.svelte';
44
+ export { default as Separator } from './components/separator/Separator.svelte';
45
+ export { default as Sidebar } from './components/sidebar/Sidebar.svelte';
46
+ export { default as SidebarContent } from './components/sidebar/SidebarContent.svelte';
47
+ export { default as SidebarFooter } from './components/sidebar/SidebarFooter.svelte';
48
+ export { default as SidebarGroup } from './components/sidebar/SidebarGroup.svelte';
49
+ export { default as SidebarHeader } from './components/sidebar/SidebarHeader.svelte';
50
+ export { default as SidebarInset } from './components/sidebar/SidebarInset.svelte';
51
+ export { default as SidebarMenu } from './components/sidebar/SidebarMenu.svelte';
52
+ export { default as SidebarMenuItem } from './components/sidebar/SidebarMenuItem.svelte';
53
+ export { default as SidebarProvider } from './components/sidebar/SidebarProvider.svelte';
54
+ export { default as SidebarSeparator } from './components/sidebar/SidebarSeparator.svelte';
55
+ export { default as SidebarTrigger } from './components/sidebar/SidebarTrigger.svelte';
56
+ export { default as Skeleton } from './components/skeleton/Skeleton.svelte';
57
+ export { default as Spinner } from './components/spinner/Spinner.svelte';
58
+ export { default as Switch } from './components/switch/Switch.svelte';
59
+ export { default as Table } from './components/table/Table.svelte';
60
+ export { default as Textarea } from './components/textarea/Textarea.svelte';
61
+ export { default as Toaster } from './components/toaster/Toaster.svelte';
62
+ export { toast } from './components/toaster/toast.svelte.js';
63
+ export { default as Toggle } from './components/toggle/Toggle.svelte';
64
+ export { default as Tooltip } from './components/tooltip/Tooltip.svelte';
65
+ export { default as TimePicker } from './components/time-picker/TimePicker.svelte';
66
+ export { getSidebarContext } from './components/sidebar/context.js';