@spaethtech/svelte-ui 0.12.0 → 0.13.0

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 (34) hide show
  1. package/.claude/skills/svelte-ui/SKILL.md +13 -4
  2. package/dist/components/Avatar/Avatar.svelte +118 -0
  3. package/dist/components/Avatar/Avatar.svelte.d.ts +20 -0
  4. package/dist/components/Avatar/AvatarGroup.svelte +83 -0
  5. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +24 -0
  6. package/dist/components/Avatar/index.d.ts +4 -0
  7. package/dist/components/Avatar/index.js +2 -0
  8. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +131 -0
  9. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +19 -0
  10. package/dist/components/Breadcrumbs/index.d.ts +2 -0
  11. package/dist/components/Breadcrumbs/index.js +1 -0
  12. package/dist/components/ButtonGroup/ButtonGroup.svelte.d.ts +1 -1
  13. package/dist/components/Chip/Chip.svelte +139 -0
  14. package/dist/components/Chip/Chip.svelte.d.ts +22 -0
  15. package/dist/components/Chip/index.d.ts +1 -0
  16. package/dist/components/Chip/index.js +1 -0
  17. package/dist/components/Divider/Divider.svelte +48 -0
  18. package/dist/components/Divider/Divider.svelte.d.ts +11 -0
  19. package/dist/components/Divider/index.d.ts +1 -0
  20. package/dist/components/Divider/index.js +1 -0
  21. package/dist/components/Pagination/Pagination.svelte +147 -0
  22. package/dist/components/Pagination/Pagination.svelte.d.ts +20 -0
  23. package/dist/components/Pagination/index.d.ts +1 -0
  24. package/dist/components/Pagination/index.js +1 -0
  25. package/dist/components/Popup.svelte +12 -1
  26. package/dist/components/Popup.svelte.d.ts +6 -1
  27. package/dist/components/SideBarMenu/SideBarMenu.svelte +160 -76
  28. package/dist/index.d.ts +7 -0
  29. package/dist/index.js +5 -0
  30. package/dist/positioning/anchored.d.ts +4 -0
  31. package/dist/positioning/anchored.js +19 -4
  32. package/docs/components.md +64 -0
  33. package/docs/usage.md +109 -0
  34. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  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
+ export { Chip } from "./components/Chip/index.js";
5
+ export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
6
+ export type { AvatarShape, AvatarStatus, AvatarItem } from "./components/Avatar/index.js";
4
7
  export { default as Button } from "./components/Button.svelte";
5
8
  export { default as ButtonDropdown } from "./components/ButtonDropdown.svelte";
6
9
  export { default as Card } from "./components/Card.svelte";
@@ -45,14 +48,18 @@ export { default as Toggle } from "./components/Toggle.svelte";
45
48
  export { default as Radio } from "./components/Radio.svelte";
46
49
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
47
50
  export { default as Grid } from "./components/Grid.svelte";
51
+ export { Divider } from "./components/Divider/index.js";
48
52
  export type { Columns, Gap } from "./components/field-group.js";
49
53
  export { default as Disclosure } from "./components/Disclosure.svelte";
50
54
  export { default as Accordion } from "./components/Accordion.svelte";
51
55
  export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
52
56
  export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, SideBarMenuApi, SideBarMenuMode, SideBarMenuActiveRule, } from "./components/SideBarMenu/SideBarMenu.svelte";
57
+ export { Breadcrumbs } from "./components/Breadcrumbs/index.js";
58
+ export type { BreadcrumbItem } from "./components/Breadcrumbs/index.js";
53
59
  export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
54
60
  export type { TabDefinition } from "./components/TabStrip/TabStrip.svelte";
55
61
  export { default as DataTable } from "./components/DataTable.svelte";
62
+ export { Pagination } from "./components/Pagination/index.js";
56
63
  export { default as Query } from "./components/Query.svelte";
57
64
  export { anchored, computePlacement, resolveBoundary } from "./positioning/anchored.js";
58
65
  export type { Side, Align, Boundary, PlaceOptions, Placement, AnchoredParams, } from "./positioning/anchored.js";
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
  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
+ export { Chip } from "./components/Chip/index.js";
6
+ export { Avatar, AvatarGroup } from "./components/Avatar/index.js";
5
7
  export { default as Button } from "./components/Button.svelte";
6
8
  export { default as ButtonDropdown } from "./components/ButtonDropdown.svelte";
7
9
  export { default as Card } from "./components/Card.svelte";
@@ -48,13 +50,16 @@ export { default as Toggle } from "./components/Toggle.svelte";
48
50
  export { default as Radio } from "./components/Radio.svelte";
49
51
  export { default as FieldGroup } from "./components/FieldGroup.svelte";
50
52
  export { default as Grid } from "./components/Grid.svelte";
53
+ export { Divider } from "./components/Divider/index.js";
51
54
  export { default as Disclosure } from "./components/Disclosure.svelte";
52
55
  export { default as Accordion } from "./components/Accordion.svelte";
53
56
  // Navigation Components
54
57
  export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
58
+ export { Breadcrumbs } from "./components/Breadcrumbs/index.js";
55
59
  export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
56
60
  // Data table (pairs with the `svelte-ui/data` headless layer)
57
61
  export { default as DataTable } from "./components/DataTable.svelte";
62
+ export { Pagination } from "./components/Pagination/index.js";
58
63
  export { default as Query } from "./components/Query.svelte";
59
64
  // Positioning engine (shared by Popup and tooltips)
60
65
  export { anchored, computePlacement, resolveBoundary } from "./positioning/anchored.js";
@@ -21,6 +21,10 @@ export interface PlaceOptions {
21
21
  margin?: number;
22
22
  /** Resolve the boundary rect the placement must stay within. Default the viewport. */
23
23
  boundary?: Boundary;
24
+ /** When >0, round the cross-axis *shift* (the amount the box is shoved to stay in the boundary) UP
25
+ * to a whole multiple of this many px. Lets a row-based menu shove by whole rows so it stays in
26
+ * phase with an adjacent list. Default 0 (free-px shift). */
27
+ snap?: number;
24
28
  }
25
29
  export interface Placement {
26
30
  x: number;
@@ -25,7 +25,7 @@ export function resolveBoundary(boundary = "viewport") {
25
25
  * vertically per `align`. Flips to the opposite side, then scans, when the preferred side won't fit.
26
26
  */
27
27
  export function computePlacement(anchor, size, opts = {}) {
28
- const { side = "bottom", align = "start", offset = 4, margin = 8 } = opts;
28
+ const { side = "bottom", align = "start", offset = 4, margin = 8, snap = 0 } = opts;
29
29
  const b = resolveBoundary(opts.boundary);
30
30
  const { width: fw, height: fh } = size;
31
31
  const fits = (s) => {
@@ -75,9 +75,24 @@ export function computePlacement(anchor, size, opts = {}) {
75
75
  ? anchor.bottom - fh
76
76
  : anchor.top;
77
77
  }
78
- // Clamp into the boundary (shift), so the box never spills past the edges.
79
- x = Math.min(Math.max(x, b.left + margin), b.right - margin - fw);
80
- y = Math.min(Math.max(y, b.top + margin), b.bottom - margin - fh);
78
+ // Clamp into the boundary (shift), so the box never spills past the edges. The CROSS axis (the one
79
+ // perpendicular to the chosen side) can `snap` its shift to whole steps; the main axis never snaps.
80
+ const clampSnap = (v, min, max) => {
81
+ const c = Math.min(Math.max(v, min), max);
82
+ if (snap > 0 && c !== v) {
83
+ // Shoved by (c - v); round the magnitude UP to a whole step, keep direction, re-clamp.
84
+ const shoved = v + Math.sign(c - v) * Math.ceil(Math.abs(c - v) / snap) * snap;
85
+ return Math.min(Math.max(shoved, min), max);
86
+ }
87
+ return c;
88
+ };
89
+ const crossVertical = chosen === "left" || chosen === "right";
90
+ x = crossVertical
91
+ ? Math.min(Math.max(x, b.left + margin), b.right - margin - fw)
92
+ : clampSnap(x, b.left + margin, b.right - margin - fw);
93
+ y = crossVertical
94
+ ? clampSnap(y, b.top + margin, b.bottom - margin - fh)
95
+ : Math.min(Math.max(y, b.top + margin), b.bottom - margin - fh);
81
96
  return { x, y, side: chosen, maxHeight };
82
97
  }
83
98
  /**
@@ -182,6 +182,16 @@ per-child prop). Works with any content. Uniform cells only (for variable widths
182
182
  - **Props**: `columns` (`number | { base, md, … }`, 1–6, default `1`), `gap` (`number | { x, y }`,
183
183
  default `4`), `class`, `children`. Shares `gridColsClass`/`gapClass` with `FieldGroup`.
184
184
 
185
+ ### Divider
186
+
187
+ A thin separating rule.
188
+
189
+ - **Location**: `src/lib/components/Divider/Divider.svelte`
190
+ - **Props**: `orientation` (`horizontal`/`vertical`), `label`/`children` (inline label, horizontal),
191
+ `labelAlign` (`start`/`center`/`end`), `class`. No `variant`/`size`.
192
+ - **Accessibility**: `role="separator"` + `aria-orientation` (plain horizontal uses `<hr>`)
193
+ - **Theming**: line = `--ui-border-color`; label muted. Vertical stretches to the row via `self-stretch`.
194
+
185
195
  ### Disclosure
186
196
 
187
197
  A single expand/collapse section — a `<Button>` header (title + rotating chevron) over a content
@@ -260,6 +270,19 @@ snaps to canonical on blur.
260
270
 
261
271
  ## Navigation
262
272
 
273
+ ### Breadcrumbs
274
+
275
+ Navigation trail (root → current); composes `Button` (links) + `Menu` (overflow).
276
+
277
+ - **Location**: `src/lib/components/Breadcrumbs/Breadcrumbs.svelte`
278
+ - **Axes**: `size`
279
+ - **Props**: `items` (`BreadcrumbItem[]` = `{ label, href?, onclick?, icon? }`; last = current page),
280
+ `separator` (Snippet | string, default chevron), `maxItems` (collapse middle → `⋯` Menu; needs ≥3),
281
+ `size`, `class`
282
+ - **Accessibility**: `<nav aria-label="Breadcrumb">` + `<ol>`; current crumb `aria-current="page"` and
283
+ not a link; ellipsis button has `aria-haspopup`/`aria-expanded`
284
+ - **Composition**: link crumbs are `Button variant="ghost"`; overflow crumbs live in a `Menu`
285
+
263
286
  ### TabStrip
264
287
 
265
288
  A horizontal tab bar (renders as a `tablist` of buttons, or a `navigation` of links when any tab has
@@ -301,6 +324,19 @@ dialog (operators, casts, dynamic `now()` dates, and a worked examples table). T
301
324
  - **Note**: URL sync is intentionally not built in — observe `dataset.applied` and sync yourself.
302
325
  Built on `Input` + `Button` + `Popup` + `Dialog`.
303
326
 
327
+ ### Pagination
328
+
329
+ Standalone page navigator — the reusable extraction of `DataTable`'s footer pager.
330
+
331
+ - **Location**: `src/lib/components/Pagination/Pagination.svelte`
332
+ - **Axes**: `variant` (active page fill), `size`
333
+ - **Props**: `page` (bindable), `total`, `perPage` (bindable), `perPageOptions` (→ rows-per-page
334
+ `Select`), `siblingCount`/`boundaryCount`, `showEdges` (first/last), `showTotal`, `disabled`, `class`
335
+ - **Behavior**: numbered buttons with ellipsis for long ranges (active = filled, rest `ghost`);
336
+ prev/next/first/last disable at the ends; choosing `perPage` resets to page 1; `page` clamps to range
337
+ - **Accessibility**: `<nav aria-label="Pagination">`; active page `aria-current="page"`
338
+ - **Composition**: `Button` + `Select` only (no raw `<button>`)
339
+
304
340
  ## Overlays
305
341
 
306
342
  ### Dialog
@@ -390,6 +426,20 @@ Shared placement engine — a Svelte action plus `computePlacement` / `resolveBo
390
426
 
391
427
  ## Display / Theme
392
428
 
429
+ ### Avatar / AvatarGroup
430
+
431
+ Person/entity likeness with a graceful fallback chain; `AvatarGroup` stacks with a `+N` overflow.
432
+
433
+ - **Location**: `src/lib/components/Avatar/Avatar.svelte`, `AvatarGroup.svelte`
434
+ - **Axes**: `size` (`sm`/`md`/`lg` = 32/40/48px), `variant` (fallback fill)
435
+ - **Avatar props**: `src`, `name` (initials + alt), `alt`, `icon` (snippet), `shape`
436
+ (`circle`/`square`), `status` (`online`/`offline`/`busy`/`away`), `class`. Fallback order:
437
+ `src` (until load error) → `name` initials → `icon` → default glyph.
438
+ - **AvatarGroup props**: `items` (`AvatarItem[]` = `{ src?, name?, alt?, variant?, icon? }`), `max`
439
+ (→ `+N` chip), `size`/`shape`/`variant` defaults, or `children` (hand-authored, no overflow), `class`
440
+ - **Theming**: fallback fill = variant token @20% + strong token text; status dot = semantic token;
441
+ overlap ring = `--ui-color-background`. No per-component tokens.
442
+
393
443
  ### Spinner
394
444
 
395
445
  Indeterminate loading ring — the primitive `Button`'s `loading` state and the loading family build on.
@@ -426,6 +476,20 @@ Colour-neutral content placeholder for loading states.
426
476
  - **Theming**: neutral text-tint base + CSS shimmer; corners use `--ui-border-radius`. Compose several
427
477
  to sketch a card/list row.
428
478
 
479
+ ### Chip
480
+
481
+ Interactive pill — the interactive counterpart to `Badge` (display-only).
482
+
483
+ - **Location**: `src/lib/components/Chip/Chip.svelte`
484
+ - **Axes**: `variant`, `size` (`sm`/`md`/`lg` = 24/32/40px)
485
+ - **Props**: `text`/`children`, `icon` (leading), `selected` (toggle → filled), `removable` +
486
+ `onremove` (× ; also Backspace/Delete when focused), `onclick` (→ `role="button"` + `aria-pressed`),
487
+ `href` (→ `<a>`), `disabled`, `rounded` (pill, default), `class`
488
+ - **HTML**: root is a `span`/`a` (never a `<button>`) so a removable chip's × can be a real nested
489
+ `<button>` without invalid nesting
490
+ - **Theming**: selected = variant token fill + white text; unselected = `--ui-tint-soft` fill +
491
+ `--ui-tint-border` border + `--ui-tint-strong` text (same vocabulary as `Badge`). No per-component tokens.
492
+
429
493
  ### Badge
430
494
 
431
495
  Status indicator / label.
package/docs/usage.md CHANGED
@@ -631,6 +631,115 @@ An edge-anchored panel that slides in — same modal mechanics as `Dialog`, pinn
631
631
  </Badge>
632
632
  ```
633
633
 
634
+ ### Divider
635
+
636
+ A thin separating rule — horizontal (optionally labelled) or vertical (toolbars).
637
+
638
+ ```svelte
639
+ <script>
640
+ import { Divider } from "@spaethtech/svelte-ui";
641
+ </script>
642
+
643
+ <Divider />
644
+ <Divider label="OR" />
645
+ <Divider label="Recent" labelAlign="start" />
646
+
647
+ <!-- Vertical, inside a flex row -->
648
+ <div class="flex items-center gap-3 h-8">
649
+ <span>Edit</span>
650
+ <Divider orientation="vertical" />
651
+ <span>Share</span>
652
+ </div>
653
+ ```
654
+
655
+ ### Pagination
656
+
657
+ A standalone page navigator (numbered buttons + ellipsis, prev/next, optional per-page + total).
658
+
659
+ ```svelte
660
+ <script>
661
+ import { Pagination } from "@spaethtech/svelte-ui";
662
+ let page = $state(1);
663
+ let perPage = $state(10);
664
+ </script>
665
+
666
+ <Pagination bind:page bind:perPage total={247} perPageOptions={[10, 25, 50, 100]} showTotal />
667
+
668
+ <!-- Compact: numbers only, with first/last controls -->
669
+ <Pagination bind:page total={200} showEdges />
670
+ ```
671
+
672
+ ### Breadcrumbs
673
+
674
+ A navigation trail; the last item is the current page. `maxItems` collapses the middle into a `⋯` menu.
675
+
676
+ ```svelte
677
+ <script>
678
+ import { Breadcrumbs } from "@spaethtech/svelte-ui";
679
+
680
+ const trail = [
681
+ { label: "Home", href: "/" },
682
+ { label: "Components", href: "/components" },
683
+ { label: "Breadcrumbs" }, // last = current page (not a link)
684
+ ];
685
+ </script>
686
+
687
+ <Breadcrumbs items={trail} />
688
+
689
+ <!-- Collapse a long trail; custom separator -->
690
+ <Breadcrumbs items={trail} maxItems={4} separator="/" />
691
+ ```
692
+
693
+ ### Chip
694
+
695
+ An interactive pill — filter/choice tokens, removable tags, clickable labels.
696
+
697
+ ```svelte
698
+ <script>
699
+ import { Chip } from "@spaethtech/svelte-ui";
700
+
701
+ const FILTERS = ["All", "Active", "Archived"];
702
+ let active = $state("Active");
703
+
704
+ let tags = $state(["design", "svelte", "ui"]);
705
+ const remove = (t) => (tags = tags.filter((x) => x !== t));
706
+ </script>
707
+
708
+ <!-- Filter / choice: selected fills with the variant (consumer owns which is on) -->
709
+ {#each FILTERS as f}
710
+ <Chip text={f} selected={active === f} variant="primary" onclick={() => (active = f)} />
711
+ {/each}
712
+
713
+ <!-- Removable tags: the × fires onremove (Backspace/Delete on a focused chip also removes) -->
714
+ {#each tags as t (t)}
715
+ <Chip text={t} removable onremove={() => remove(t)} />
716
+ {/each}
717
+ ```
718
+
719
+ ### Avatar
720
+
721
+ A likeness with a fallback chain (image → initials → icon → glyph). `AvatarGroup` stacks with `+N`.
722
+
723
+ ```svelte
724
+ <script>
725
+ import { Avatar, AvatarGroup } from "@spaethtech/svelte-ui";
726
+
727
+ const team = [
728
+ { name: "Jane Doe", src: "/1.jpg" },
729
+ { name: "Ravi Patel", src: "/2.jpg" },
730
+ { name: "Mia Chen" }, // no src → initials "MC"
731
+ ];
732
+ </script>
733
+
734
+ <Avatar src="/jane.jpg" name="Jane Doe" />
735
+ <Avatar name="Jane Doe" variant="primary" /> <!-- initials fallback -->
736
+ <Avatar /> <!-- default glyph -->
737
+ <Avatar name="Jane Doe" status="online" /> <!-- status dot -->
738
+
739
+ <!-- Overlapped group; extras beyond max collapse into a +N chip -->
740
+ <AvatarGroup items={team} max={4} />
741
+ ```
742
+
634
743
  ### TextArea
635
744
 
636
745
  ```svelte
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"