@spaethtech/svelte-ui 0.14.1-dev.65.3b37f4b → 0.15.1-dev.67.fbf3875
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.
- package/.claude/skills/svelte-ui/SKILL.md +5 -5
- package/dist/components/Carousel/Carousel.svelte +147 -0
- package/dist/components/Carousel/Carousel.svelte.d.ts +38 -0
- package/dist/components/Carousel/index.d.ts +1 -0
- package/dist/components/Carousel/index.js +1 -0
- package/dist/components/CommandPalette/CommandPalette.svelte +203 -0
- package/dist/components/CommandPalette/CommandPalette.svelte.d.ts +23 -0
- package/dist/components/CommandPalette/index.d.ts +2 -0
- package/dist/components/CommandPalette/index.js +1 -0
- package/dist/components/ContextMenu/ContextMenu.svelte +58 -0
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +16 -0
- package/dist/components/ContextMenu/index.d.ts +1 -0
- package/dist/components/ContextMenu/index.js +1 -0
- package/dist/components/EmptyState/EmptyState.svelte +68 -0
- package/dist/components/EmptyState/EmptyState.svelte.d.ts +19 -0
- package/dist/components/EmptyState/index.d.ts +1 -0
- package/dist/components/EmptyState/index.js +1 -0
- package/dist/components/Kbd/Kbd.svelte +53 -0
- package/dist/components/Kbd/Kbd.svelte.d.ts +15 -0
- package/dist/components/Kbd/index.d.ts +1 -0
- package/dist/components/Kbd/index.js +1 -0
- package/dist/components/ScrollArea/ScrollArea.svelte +71 -0
- package/dist/components/ScrollArea/ScrollArea.svelte.d.ts +15 -0
- package/dist/components/ScrollArea/index.d.ts +1 -0
- package/dist/components/ScrollArea/index.js +1 -0
- package/dist/components/Splitter/Splitter.svelte +127 -0
- package/dist/components/Splitter/Splitter.svelte.d.ts +18 -0
- package/dist/components/Splitter/index.d.ts +1 -0
- package/dist/components/Splitter/index.js +1 -0
- package/dist/components/Stat/Stat.svelte +95 -0
- package/dist/components/Stat/Stat.svelte.d.ts +21 -0
- package/dist/components/Stat/index.d.ts +1 -0
- package/dist/components/Stat/index.js +1 -0
- package/dist/components/Timeline/Timeline.svelte +98 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +21 -0
- package/dist/components/Timeline/index.d.ts +2 -0
- package/dist/components/Timeline/index.js +1 -0
- package/dist/components/Tree/Tree.svelte +178 -0
- package/dist/components/Tree/Tree.svelte.d.ts +26 -0
- package/dist/components/Tree/index.d.ts +2 -0
- package/dist/components/Tree/index.js +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +10 -0
- package/docs/components.md +106 -0
- package/docs/usage.md +166 -0
- package/package.json +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
/**
|
|
3
|
+
* Tree — a hierarchical, expandable tree view (ARIA `tree`). Data-driven nodes; expand/collapse +
|
|
4
|
+
* single-select with roving-tabindex keyboard nav (↑/↓ move, →/← expand/collapse, Enter/Space select).
|
|
5
|
+
* Shared `variant`/`size` axes, `--ui-*` tokens. See Tree.spec.md.
|
|
6
|
+
*/
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts" module>
|
|
9
|
+
import type { Snippet } from "svelte";
|
|
10
|
+
export type TreeNode = {
|
|
11
|
+
label: string;
|
|
12
|
+
/** Stable key; falls back to the index path when omitted. */
|
|
13
|
+
id?: string;
|
|
14
|
+
icon?: Snippet;
|
|
15
|
+
children?: TreeNode[];
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import IconChevronRight from "~icons/mdi/chevron-right";
|
|
22
|
+
import type { Variant } from "../../types/variants.js";
|
|
23
|
+
import { variantToken } from "../../types/variants.js";
|
|
24
|
+
import type { Size } from "../../types/sizes.js";
|
|
25
|
+
import { responsiveClasses, type Responsive } from "../../types/responsive.js";
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
items,
|
|
29
|
+
expanded = $bindable([]),
|
|
30
|
+
selected = $bindable(""),
|
|
31
|
+
onselect,
|
|
32
|
+
variant = "primary",
|
|
33
|
+
size = "md",
|
|
34
|
+
class: cls = "",
|
|
35
|
+
}: {
|
|
36
|
+
items: TreeNode[];
|
|
37
|
+
/** Keys of expanded nodes (bindable). */
|
|
38
|
+
expanded?: string[];
|
|
39
|
+
/** Key of the selected node (bindable). */
|
|
40
|
+
selected?: string;
|
|
41
|
+
onselect?: (node: TreeNode, key: string) => void;
|
|
42
|
+
variant?: Variant;
|
|
43
|
+
size?: Responsive<Size>;
|
|
44
|
+
class?: string;
|
|
45
|
+
} = $props();
|
|
46
|
+
|
|
47
|
+
const token = $derived(variantToken[variant]);
|
|
48
|
+
const rowH: Record<Size, string> = { sm: "h-7 text-xs", md: "h-8 text-sm", lg: "h-10 text-base" };
|
|
49
|
+
const iconCls: Record<Size, string> = {
|
|
50
|
+
sm: "[&_svg]:w-3.5 [&_svg]:h-3.5",
|
|
51
|
+
md: "[&_svg]:w-4 [&_svg]:h-4",
|
|
52
|
+
lg: "[&_svg]:w-5 [&_svg]:h-5",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type Flat = { key: string; node: TreeNode; level: number; hasChildren: boolean; open: boolean };
|
|
56
|
+
// Flattened list of VISIBLE nodes (children shown only under expanded parents) — drives rendering,
|
|
57
|
+
// roving tabindex, and arrow-key navigation.
|
|
58
|
+
const flat = $derived.by<Flat[]>(() => {
|
|
59
|
+
const out: Flat[] = [];
|
|
60
|
+
const walk = (nodes: TreeNode[], level: number, prefix: string) => {
|
|
61
|
+
nodes.forEach((node, i) => {
|
|
62
|
+
const key = node.id ?? `${prefix}${i}`;
|
|
63
|
+
const hasChildren = !!node.children?.length;
|
|
64
|
+
const open = expanded.includes(key);
|
|
65
|
+
out.push({ key, node, level, hasChildren, open });
|
|
66
|
+
if (hasChildren && open) walk(node.children!, level + 1, `${key}>`);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
walk(items, 0, "");
|
|
70
|
+
return out;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let rowRefs = $state<Record<string, HTMLElement | undefined>>({});
|
|
74
|
+
let activeKey = $state("");
|
|
75
|
+
const tabKey = $derived(
|
|
76
|
+
flat.some((f) => f.key === activeKey) ? activeKey : (flat.find((f) => !f.node.disabled)?.key ?? ""),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const toggle = (key: string) =>
|
|
80
|
+
(expanded = expanded.includes(key) ? expanded.filter((k) => k !== key) : [...expanded, key]);
|
|
81
|
+
function selectNode(f: Flat) {
|
|
82
|
+
if (f.node.disabled) return;
|
|
83
|
+
selected = f.key;
|
|
84
|
+
onselect?.(f.node, f.key);
|
|
85
|
+
if (f.hasChildren) toggle(f.key);
|
|
86
|
+
}
|
|
87
|
+
const focusKey = (key: string) => {
|
|
88
|
+
activeKey = key;
|
|
89
|
+
rowRefs[key]?.focus();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function onKeydown(e: KeyboardEvent) {
|
|
93
|
+
const idx = flat.findIndex((f) => f.key === tabKey);
|
|
94
|
+
if (idx < 0) return;
|
|
95
|
+
const f = flat[idx];
|
|
96
|
+
switch (e.key) {
|
|
97
|
+
case "ArrowDown":
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
if (idx < flat.length - 1) focusKey(flat[idx + 1].key);
|
|
100
|
+
break;
|
|
101
|
+
case "ArrowUp":
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
if (idx > 0) focusKey(flat[idx - 1].key);
|
|
104
|
+
break;
|
|
105
|
+
case "ArrowRight":
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
if (f.hasChildren && !f.open) toggle(f.key);
|
|
108
|
+
else if (f.hasChildren && flat[idx + 1]) focusKey(flat[idx + 1].key);
|
|
109
|
+
break;
|
|
110
|
+
case "ArrowLeft":
|
|
111
|
+
e.preventDefault();
|
|
112
|
+
if (f.hasChildren && f.open) toggle(f.key);
|
|
113
|
+
else {
|
|
114
|
+
// move to parent (previous node at a lower level)
|
|
115
|
+
for (let j = idx - 1; j >= 0; j--)
|
|
116
|
+
if (flat[j].level < f.level) {
|
|
117
|
+
focusKey(flat[j].key);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
case "Home":
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
focusKey(flat[0].key);
|
|
125
|
+
break;
|
|
126
|
+
case "End":
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
focusKey(flat[flat.length - 1].key);
|
|
129
|
+
break;
|
|
130
|
+
case "Enter":
|
|
131
|
+
case " ":
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
selectNode(f);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
140
|
+
<ul role="tree" class="flex flex-col {responsiveClasses(size, iconCls)} {cls}" onkeydown={onKeydown}>
|
|
141
|
+
{#each flat as f (f.key)}
|
|
142
|
+
{@const active = selected === f.key}
|
|
143
|
+
<li role="none" class="list-none">
|
|
144
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
145
|
+
<div
|
|
146
|
+
bind:this={rowRefs[f.key]}
|
|
147
|
+
role="treeitem"
|
|
148
|
+
aria-level={f.level + 1}
|
|
149
|
+
aria-selected={active}
|
|
150
|
+
aria-expanded={f.hasChildren ? f.open : undefined}
|
|
151
|
+
aria-disabled={f.node.disabled || undefined}
|
|
152
|
+
tabindex={f.key === tabKey ? 0 : -1}
|
|
153
|
+
onclick={() => selectNode(f)}
|
|
154
|
+
onfocus={() => (activeKey = f.key)}
|
|
155
|
+
style="padding-inline-start: {f.level * 1.25 + 0.5}rem; {active
|
|
156
|
+
? `background-color: color-mix(in srgb, var(${token}) 16%, transparent);`
|
|
157
|
+
: ''}"
|
|
158
|
+
class="group flex items-center gap-1.5 rounded-[var(--ui-border-radius)] pr-2 transition-colors {responsiveClasses(
|
|
159
|
+
size,
|
|
160
|
+
rowH,
|
|
161
|
+
)} {f.node.disabled
|
|
162
|
+
? 'opacity-50 cursor-not-allowed'
|
|
163
|
+
: 'cursor-pointer hover:[background-color:var(--ui-color-hover)]'} focus-visible:[outline:2px_solid_color-mix(in_srgb,var(--ui-color-text)_70%,transparent)] focus-visible:[outline-offset:-2px]"
|
|
164
|
+
>
|
|
165
|
+
{#if f.hasChildren}
|
|
166
|
+
<span
|
|
167
|
+
class="inline-flex shrink-0 items-center justify-center transition-transform duration-150 {f.open
|
|
168
|
+
? 'rotate-90'
|
|
169
|
+
: ''}"><IconChevronRight /></span>
|
|
170
|
+
{:else}
|
|
171
|
+
<span class="inline-block w-4 shrink-0"></span>
|
|
172
|
+
{/if}
|
|
173
|
+
{#if f.node.icon}<span class="inline-flex shrink-0">{@render f.node.icon()}</span>{/if}
|
|
174
|
+
<span class="truncate [color:var(--ui-color-text)]">{f.node.label}</span>
|
|
175
|
+
</div>
|
|
176
|
+
</li>
|
|
177
|
+
{/each}
|
|
178
|
+
</ul>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
export type TreeNode = {
|
|
3
|
+
label: string;
|
|
4
|
+
/** Stable key; falls back to the index path when omitted. */
|
|
5
|
+
id?: string;
|
|
6
|
+
icon?: Snippet;
|
|
7
|
+
children?: TreeNode[];
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
import type { Variant } from "../../types/variants.js";
|
|
11
|
+
import type { Size } from "../../types/sizes.js";
|
|
12
|
+
import { type Responsive } from "../../types/responsive.js";
|
|
13
|
+
type $$ComponentProps = {
|
|
14
|
+
items: TreeNode[];
|
|
15
|
+
/** Keys of expanded nodes (bindable). */
|
|
16
|
+
expanded?: string[];
|
|
17
|
+
/** Key of the selected node (bindable). */
|
|
18
|
+
selected?: string;
|
|
19
|
+
onselect?: (node: TreeNode, key: string) => void;
|
|
20
|
+
variant?: Variant;
|
|
21
|
+
size?: Responsive<Size>;
|
|
22
|
+
class?: string;
|
|
23
|
+
};
|
|
24
|
+
declare const Tree: import("svelte").Component<$$ComponentProps, {}, "selected" | "expanded">;
|
|
25
|
+
type Tree = ReturnType<typeof Tree>;
|
|
26
|
+
export default Tree;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Tree } from "./Tree.svelte";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { default as Alert } from "./components/Alert.svelte";
|
|
|
2
2
|
export { default as Banner } from "./components/Banner.svelte";
|
|
3
3
|
export { default as Badge } from "./components/Badge/Badge.svelte";
|
|
4
4
|
export { Chip } from "./components/Chip/index.js";
|
|
5
|
+
export { Kbd } from "./components/Kbd/index.js";
|
|
6
|
+
export { EmptyState } from "./components/EmptyState/index.js";
|
|
5
7
|
export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
|
|
6
8
|
export type { AvatarShape, AvatarStatus, AvatarItem } from "./components/Avatar/index.js";
|
|
7
9
|
export { default as Button } from "./components/Button.svelte";
|
|
@@ -10,6 +12,9 @@ export { default as Card } from "./components/Card.svelte";
|
|
|
10
12
|
export { default as CardHeader } from "./components/CardHeader.svelte";
|
|
11
13
|
export { default as CardBody } from "./components/CardBody.svelte";
|
|
12
14
|
export { default as CardFooter } from "./components/CardFooter.svelte";
|
|
15
|
+
export { Stat } from "./components/Stat/index.js";
|
|
16
|
+
export { Timeline } from "./components/Timeline/index.js";
|
|
17
|
+
export type { TimelineItem } from "./components/Timeline/index.js";
|
|
13
18
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
14
19
|
export { toast, getToasts, dismissToast, type ToastVariant, type ToastItem, } from "./components/Toast/toast.svelte.js";
|
|
15
20
|
export { default as Dialog } from "./components/Dialog.svelte";
|
|
@@ -45,6 +50,11 @@ export { default as ThemeSelector } from "./components/ThemeSelector.svelte";
|
|
|
45
50
|
export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
|
|
46
51
|
export { default as Popup } from "./components/Popup.svelte";
|
|
47
52
|
export { default as Menu } from "./components/Menu.svelte";
|
|
53
|
+
export { ContextMenu } from "./components/ContextMenu/index.js";
|
|
54
|
+
export { CommandPalette } from "./components/CommandPalette/index.js";
|
|
55
|
+
export type { Command } from "./components/CommandPalette/index.js";
|
|
56
|
+
export { Tree } from "./components/Tree/index.js";
|
|
57
|
+
export type { TreeNode } from "./components/Tree/index.js";
|
|
48
58
|
export type { MenuItem } from "./data/table/types.js";
|
|
49
59
|
export { ButtonGroup } from "./components/ButtonGroup/index.js";
|
|
50
60
|
export type { ButtonGroupItem, ButtonGroupSelect } from "./components/ButtonGroup/index.js";
|
|
@@ -54,6 +64,9 @@ export { default as Radio } from "./components/Radio.svelte";
|
|
|
54
64
|
export { default as FieldGroup } from "./components/FieldGroup.svelte";
|
|
55
65
|
export { default as Grid } from "./components/Grid.svelte";
|
|
56
66
|
export { Divider } from "./components/Divider/index.js";
|
|
67
|
+
export { ScrollArea } from "./components/ScrollArea/index.js";
|
|
68
|
+
export { Splitter } from "./components/Splitter/index.js";
|
|
69
|
+
export { Carousel } from "./components/Carousel/index.js";
|
|
57
70
|
export type { Columns, Gap } from "./components/field-group.js";
|
|
58
71
|
export { Stepper } from "./components/Stepper/index.js";
|
|
59
72
|
export type { StepItem } from "./components/Stepper/index.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export { default as Alert } from "./components/Alert.svelte";
|
|
|
3
3
|
export { default as Banner } from "./components/Banner.svelte";
|
|
4
4
|
export { default as Badge } from "./components/Badge/Badge.svelte";
|
|
5
5
|
export { Chip } from "./components/Chip/index.js";
|
|
6
|
+
export { Kbd } from "./components/Kbd/index.js";
|
|
7
|
+
export { EmptyState } from "./components/EmptyState/index.js";
|
|
6
8
|
export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
|
|
7
9
|
export { default as Button } from "./components/Button.svelte";
|
|
8
10
|
export { default as ButtonDropdown } from "./components/ButtonDropdown.svelte";
|
|
@@ -10,6 +12,8 @@ export { default as Card } from "./components/Card.svelte";
|
|
|
10
12
|
export { default as CardHeader } from "./components/CardHeader.svelte";
|
|
11
13
|
export { default as CardBody } from "./components/CardBody.svelte";
|
|
12
14
|
export { default as CardFooter } from "./components/CardFooter.svelte";
|
|
15
|
+
export { Stat } from "./components/Stat/index.js";
|
|
16
|
+
export { Timeline } from "./components/Timeline/index.js";
|
|
13
17
|
// Toast notifications — `toast.*` pushes from anywhere; mount <Toaster /> once at the app root.
|
|
14
18
|
export { default as Toaster } from "./components/Toast/Toaster.svelte";
|
|
15
19
|
export { toast, getToasts, dismissToast, } from "./components/Toast/toast.svelte.js";
|
|
@@ -48,6 +52,9 @@ export { default as ThemeToggle } from "./components/ThemeToggle.svelte";
|
|
|
48
52
|
// Utility Components
|
|
49
53
|
export { default as Popup } from "./components/Popup.svelte";
|
|
50
54
|
export { default as Menu } from "./components/Menu.svelte";
|
|
55
|
+
export { ContextMenu } from "./components/ContextMenu/index.js";
|
|
56
|
+
export { CommandPalette } from "./components/CommandPalette/index.js";
|
|
57
|
+
export { Tree } from "./components/Tree/index.js";
|
|
51
58
|
export { ButtonGroup } from "./components/ButtonGroup/index.js";
|
|
52
59
|
export { default as Checkbox } from "./components/Checkbox.svelte";
|
|
53
60
|
export { default as Toggle } from "./components/Toggle.svelte";
|
|
@@ -55,6 +62,9 @@ export { default as Radio } from "./components/Radio.svelte";
|
|
|
55
62
|
export { default as FieldGroup } from "./components/FieldGroup.svelte";
|
|
56
63
|
export { default as Grid } from "./components/Grid.svelte";
|
|
57
64
|
export { Divider } from "./components/Divider/index.js";
|
|
65
|
+
export { ScrollArea } from "./components/ScrollArea/index.js";
|
|
66
|
+
export { Splitter } from "./components/Splitter/index.js";
|
|
67
|
+
export { Carousel } from "./components/Carousel/index.js";
|
|
58
68
|
export { Stepper } from "./components/Stepper/index.js";
|
|
59
69
|
export { default as Disclosure } from "./components/Disclosure.svelte";
|
|
60
70
|
export { default as Accordion } from "./components/Accordion.svelte";
|
package/docs/components.md
CHANGED
|
@@ -617,3 +617,109 @@ src/lib/components/ComponentName/
|
|
|
617
617
|
```
|
|
618
618
|
src/lib/components/ComponentName.svelte
|
|
619
619
|
```
|
|
620
|
+
|
|
621
|
+
### Kbd
|
|
622
|
+
|
|
623
|
+
Keyboard-key display (`<kbd>` keycap).
|
|
624
|
+
|
|
625
|
+
- **Location**: `src/lib/components/Kbd/Kbd.svelte`
|
|
626
|
+
- **Axes**: `size`
|
|
627
|
+
- **Props**: `keys` (`string[]` combo, joined with `+`), `text`/`children` (single key), `class`
|
|
628
|
+
- **Theming**: `--ui-color-surface` fill + `--ui-border-color` border with a shallow inset bottom shadow
|
|
629
|
+
|
|
630
|
+
### EmptyState
|
|
631
|
+
|
|
632
|
+
Zero-data / empty placeholder — icon + title + description + optional action.
|
|
633
|
+
|
|
634
|
+
- **Location**: `src/lib/components/EmptyState/EmptyState.svelte`
|
|
635
|
+
- **Axes**: `variant` (icon-badge accent), `size`
|
|
636
|
+
- **Props**: `title`, `description`, `icon` (snippet; default inbox), `action` (CTA snippet),
|
|
637
|
+
`children`, `class`
|
|
638
|
+
|
|
639
|
+
### Stat
|
|
640
|
+
|
|
641
|
+
Metric/KPI card — label, value, delta, icon. Built on `Card`.
|
|
642
|
+
|
|
643
|
+
- **Location**: `src/lib/components/Stat/Stat.svelte`
|
|
644
|
+
- **Axes**: `variant` (icon-badge accent), `size` (value scale)
|
|
645
|
+
- **Props**: `label`, `value`, `delta` (number → arrow; string → as-is), `deltaLabel`, `invertDelta`
|
|
646
|
+
(negative is good), `icon` (snippet), `class`
|
|
647
|
+
- **Theming**: rising delta success / falling danger / flat muted; icon badge tints from the variant
|
|
648
|
+
|
|
649
|
+
### Timeline
|
|
650
|
+
|
|
651
|
+
Vertical event sequence — marker + connecting line + title/time/description.
|
|
652
|
+
|
|
653
|
+
- **Location**: `src/lib/components/Timeline/Timeline.svelte`
|
|
654
|
+
- **Axes**: `variant` (default marker accent; per-item override), `size`
|
|
655
|
+
- **Props**: `items` (`TimelineItem[]` = `{ title, time?, description?, icon?, variant?, content? }`), `class`
|
|
656
|
+
- **Theming**: icon marker = variant token @16% badge; dot = solid token; connector `--ui-border-color`
|
|
657
|
+
|
|
658
|
+
### ContextMenu
|
|
659
|
+
|
|
660
|
+
Right-click menu over any content; opens `Menu` at the pointer.
|
|
661
|
+
|
|
662
|
+
- **Location**: `src/lib/components/ContextMenu/ContextMenu.svelte`
|
|
663
|
+
- **Props**: `items` (`MenuItem[]`), `size`, `variant`, `disabled` (leaves the native menu), `class`,
|
|
664
|
+
`children` (the right-clickable content)
|
|
665
|
+
- **Composition**: `Menu` anchored to a 0-size point at the pointer; flips/shifts at edges
|
|
666
|
+
|
|
667
|
+
### Tree
|
|
668
|
+
|
|
669
|
+
Hierarchical, expandable tree view (ARIA `tree`).
|
|
670
|
+
|
|
671
|
+
- **Location**: `src/lib/components/Tree/Tree.svelte`
|
|
672
|
+
- **Axes**: `variant` (selected-row tint), `size`
|
|
673
|
+
- **Props**: `items` (`TreeNode[]` = `{ label, id?, icon?, children?, disabled? }`), `expanded`
|
|
674
|
+
(bindable `string[]`), `selected` (bindable key), `onselect`, `class`
|
|
675
|
+
- **Keyboard**: roving tabindex — ↑/↓ move, → expand/into child, ← collapse/to parent, Enter/Space
|
|
676
|
+
select, Home/End
|
|
677
|
+
- **Accessibility**: `role="tree"`/`treeitem` with `aria-level`/`-expanded`/`-selected`/`-disabled`
|
|
678
|
+
|
|
679
|
+
### ScrollArea
|
|
680
|
+
|
|
681
|
+
Scroll container with a themed thin scrollbar.
|
|
682
|
+
|
|
683
|
+
- **Location**: `src/lib/components/ScrollArea/ScrollArea.svelte`
|
|
684
|
+
- **Props**: `orientation` (`vertical`/`horizontal`/`both`), `maxHeight`/`height` (CSS length), `variant`
|
|
685
|
+
(thumb accent; default neutral text-tint), `class`, `children`
|
|
686
|
+
- **Theming**: thumb = accent @28% (hover @45%), transparent track, rounded; Firefox `scrollbar-color`/
|
|
687
|
+
`-width:thin` + WebKit `::-webkit-scrollbar` both styled
|
|
688
|
+
|
|
689
|
+
### Splitter
|
|
690
|
+
|
|
691
|
+
Two resizable panes with a draggable divider.
|
|
692
|
+
|
|
693
|
+
- **Location**: `src/lib/components/Splitter/Splitter.svelte`
|
|
694
|
+
- **Props**: `start`/`end` (snippets), `orientation` (`horizontal`/`vertical`), `size` (bindable %),
|
|
695
|
+
`min`/`max`/`step`, `variant` (handle accent), `disabled`, `class`
|
|
696
|
+
- **Interaction**: drag the handle or focus it and use ←/→ (or ↑/↓) + Home/End
|
|
697
|
+
- **Accessibility**: WAI-ARIA window-splitter — `role="separator"`, `aria-orientation`,
|
|
698
|
+
`aria-valuenow`/`min`/`max`, focusable
|
|
699
|
+
|
|
700
|
+
### Carousel
|
|
701
|
+
|
|
702
|
+
Sliding, one-at-a-time viewport with controls + dot indicators.
|
|
703
|
+
|
|
704
|
+
- **Location**: `src/lib/components/Carousel/Carousel.svelte`
|
|
705
|
+
- **Props**: `items` (`T[]`) + `slide` (`Snippet<[item, index]>`), `index` (bindable), `loop`,
|
|
706
|
+
`autoplay` (ms; pauses on hover/focus), `controls`, `indicators`, `variant`, `ariaLabel`, `class`
|
|
707
|
+
- **Interaction**: prev/next `Button`s, clickable dots, ←/→ keys; `loop` wraps or clamps at ends
|
|
708
|
+
- **Accessibility**: `role="region"` carousel + per-slide `role="group"` (`aria-label="N of M"`, hidden
|
|
709
|
+
when off); dots are `<button>`s with `aria-current`
|
|
710
|
+
|
|
711
|
+
### CommandPalette
|
|
712
|
+
|
|
713
|
+
⌘K/Ctrl+K fuzzy command launcher — a top-centered modal overlay with a search field + filtered,
|
|
714
|
+
grouped, keyboard-navigable command list.
|
|
715
|
+
|
|
716
|
+
- **Location**: `src/lib/components/CommandPalette/CommandPalette.svelte`
|
|
717
|
+
- **Props**: `open` (bindable), `commands` (`Command[]` = `{ label, keywords?, group?, icon?,
|
|
718
|
+
shortcut?, disabled?, onrun }`), `hotkey` (default `"mod+k"`; `mod` = ⌘ on macOS / Ctrl elsewhere;
|
|
719
|
+
`""` disables), `placeholder`, `empty` (no-match text), `onopen`
|
|
720
|
+
- **Interaction**: hotkey toggles open; typing filters (label + keywords); ↑/↓ move (wrapping), Enter
|
|
721
|
+
runs the highlighted command, Esc / backdrop close; shortcuts render as `Kbd`
|
|
722
|
+
- **Behavior**: portals to `<body>`; on open resets query/highlight, locks scroll, focuses input; on
|
|
723
|
+
close restores focus. Disabled commands are inert.
|
|
724
|
+
- **Composition**: `Input` (search) + `Kbd` (shortcuts); `role="dialog"`/`combobox`/`listbox` wiring
|
|
725
|
+
- **Exports**: `CommandPalette` + the `Command` type
|
package/docs/usage.md
CHANGED
|
@@ -776,6 +776,172 @@ A navigation trail; the last item is the current page. `maxItems` collapses the
|
|
|
776
776
|
<Breadcrumbs items={trail} maxItems={4} separator="/" />
|
|
777
777
|
```
|
|
778
778
|
|
|
779
|
+
### Tree
|
|
780
|
+
|
|
781
|
+
A hierarchical, expandable tree.
|
|
782
|
+
|
|
783
|
+
```svelte
|
|
784
|
+
<script>
|
|
785
|
+
import { Tree } from "@spaethtech/svelte-ui";
|
|
786
|
+
const items = [
|
|
787
|
+
{ label: "src", children: [{ label: "index.ts" }, { label: "app.css" }] },
|
|
788
|
+
{ label: "package.json" },
|
|
789
|
+
];
|
|
790
|
+
let expanded = $state(["0"]); // key = id or index path
|
|
791
|
+
let selected = $state("");
|
|
792
|
+
</script>
|
|
793
|
+
|
|
794
|
+
<Tree {items} bind:expanded bind:selected onselect={(n) => console.log(n.label)} />
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### ContextMenu
|
|
798
|
+
|
|
799
|
+
A right-click menu over any content.
|
|
800
|
+
|
|
801
|
+
```svelte
|
|
802
|
+
<script>
|
|
803
|
+
import { ContextMenu } from "@spaethtech/svelte-ui";
|
|
804
|
+
const items = [
|
|
805
|
+
{ label: "Open", onclick: () => {} },
|
|
806
|
+
{ label: "Delete", danger: true, onclick: () => {} },
|
|
807
|
+
];
|
|
808
|
+
</script>
|
|
809
|
+
|
|
810
|
+
<ContextMenu {items}>
|
|
811
|
+
<div>Right-click me</div>
|
|
812
|
+
</ContextMenu>
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Timeline
|
|
816
|
+
|
|
817
|
+
A vertical event sequence.
|
|
818
|
+
|
|
819
|
+
```svelte
|
|
820
|
+
<script>
|
|
821
|
+
import { Timeline } from "@spaethtech/svelte-ui";
|
|
822
|
+
const items = [
|
|
823
|
+
{ title: "Order placed", time: "Mon 9:14 AM", variant: "success" },
|
|
824
|
+
{ title: "Shipped", time: "Tue 8:05 AM", variant: "primary" },
|
|
825
|
+
{ title: "Out for delivery", time: "Wed" },
|
|
826
|
+
];
|
|
827
|
+
</script>
|
|
828
|
+
|
|
829
|
+
<Timeline {items} />
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### Carousel
|
|
833
|
+
|
|
834
|
+
A sliding viewport with controls + dots.
|
|
835
|
+
|
|
836
|
+
```svelte
|
|
837
|
+
<script>
|
|
838
|
+
import { Carousel } from "@spaethtech/svelte-ui";
|
|
839
|
+
const photos = [{ url: "/1.jpg" }, { url: "/2.jpg" }];
|
|
840
|
+
let index = $state(0);
|
|
841
|
+
</script>
|
|
842
|
+
|
|
843
|
+
<Carousel items={photos} bind:index autoplay={4000}>
|
|
844
|
+
{#snippet slide(photo, i)}
|
|
845
|
+
<img src={photo.url} class="w-full h-64 object-cover" />
|
|
846
|
+
{/snippet}
|
|
847
|
+
</Carousel>
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
### CommandPalette
|
|
851
|
+
|
|
852
|
+
A ⌘K/Ctrl+K fuzzy command launcher.
|
|
853
|
+
|
|
854
|
+
```svelte
|
|
855
|
+
<script>
|
|
856
|
+
import { CommandPalette, type Command } from "@spaethtech/svelte-ui";
|
|
857
|
+
let open = $state(false);
|
|
858
|
+
const commands: Command[] = [
|
|
859
|
+
{ label: "New file", group: "Actions", shortcut: ["⌘", "N"], onrun: () => create() },
|
|
860
|
+
{ label: "Open settings", group: "Actions", onrun: () => goto("/settings") },
|
|
861
|
+
{ label: "Toggle theme", keywords: ["dark", "light"], onrun: () => toggleTheme() },
|
|
862
|
+
];
|
|
863
|
+
</script>
|
|
864
|
+
|
|
865
|
+
<!-- ⌘K / Ctrl+K opens it anywhere; or bind:open from your own trigger -->
|
|
866
|
+
<CommandPalette bind:open {commands} />
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
### Splitter
|
|
870
|
+
|
|
871
|
+
Two resizable panes with a draggable divider.
|
|
872
|
+
|
|
873
|
+
```svelte
|
|
874
|
+
<script>
|
|
875
|
+
import { Splitter } from "@spaethtech/svelte-ui";
|
|
876
|
+
let size = $state(35);
|
|
877
|
+
</script>
|
|
878
|
+
|
|
879
|
+
<Splitter bind:size class="h-64">
|
|
880
|
+
{#snippet start()}<aside>Sidebar</aside>{/snippet}
|
|
881
|
+
{#snippet end()}<main>Content</main>{/snippet}
|
|
882
|
+
</Splitter>
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
### ScrollArea
|
|
886
|
+
|
|
887
|
+
A themed scroll container.
|
|
888
|
+
|
|
889
|
+
```svelte
|
|
890
|
+
<script>
|
|
891
|
+
import { ScrollArea } from "@spaethtech/svelte-ui";
|
|
892
|
+
</script>
|
|
893
|
+
|
|
894
|
+
<ScrollArea maxHeight="16rem" variant="primary">
|
|
895
|
+
<!-- long content scrolls with a themed thin scrollbar -->
|
|
896
|
+
</ScrollArea>
|
|
897
|
+
|
|
898
|
+
<ScrollArea orientation="horizontal">…</ScrollArea>
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Stat
|
|
902
|
+
|
|
903
|
+
Metric/KPI cards; lay several in a `Grid`.
|
|
904
|
+
|
|
905
|
+
```svelte
|
|
906
|
+
<script>
|
|
907
|
+
import { Stat, Grid } from "@spaethtech/svelte-ui";
|
|
908
|
+
</script>
|
|
909
|
+
|
|
910
|
+
<Grid columns={{ base: 1, sm: 3 }} gap={4}>
|
|
911
|
+
<Stat label="Revenue" value="$48,120" delta="+12.4%" deltaLabel="vs last month" variant="success" />
|
|
912
|
+
<Stat label="Active users" value="8,241" delta={-3.1} deltaLabel="vs last week" />
|
|
913
|
+
<Stat label="Error rate" value="0.8%" delta={-0.2} invertDelta />
|
|
914
|
+
</Grid>
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
### EmptyState
|
|
918
|
+
|
|
919
|
+
A zero-data placeholder with an optional action.
|
|
920
|
+
|
|
921
|
+
```svelte
|
|
922
|
+
<script>
|
|
923
|
+
import { EmptyState, Button } from "@spaethtech/svelte-ui";
|
|
924
|
+
</script>
|
|
925
|
+
|
|
926
|
+
<EmptyState title="No projects yet" description="Create your first project to get started.">
|
|
927
|
+
{#snippet action()}<Button text="New project" variant="primary" />{/snippet}
|
|
928
|
+
</EmptyState>
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### Kbd
|
|
932
|
+
|
|
933
|
+
Keyboard-key display.
|
|
934
|
+
|
|
935
|
+
```svelte
|
|
936
|
+
<script>
|
|
937
|
+
import { Kbd } from "@spaethtech/svelte-ui";
|
|
938
|
+
</script>
|
|
939
|
+
|
|
940
|
+
<Kbd text="Esc" />
|
|
941
|
+
<Kbd keys={["Ctrl", "K"]} />
|
|
942
|
+
<Kbd keys={["⌘", "⇧", "P"]} size="lg" />
|
|
943
|
+
```
|
|
944
|
+
|
|
779
945
|
### Chip
|
|
780
946
|
|
|
781
947
|
An interactive pill — filter/choice tokens, removable tags, clickable labels.
|