@pienter/ui 0.2.0 → 0.5.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 (89) hide show
  1. package/CHANGELOG.md +259 -0
  2. package/CONVENTIONS.md +297 -385
  3. package/README.md +3 -18
  4. package/components/feedback/toast/toast.css +1 -1
  5. package/components/form/combobox/Combobox.vue +21 -38
  6. package/components/form/combobox/combobox.css +1 -1
  7. package/components/form/form/Form.vue +1 -2
  8. package/components/form/label/label.css +1 -1
  9. package/components/form/number-field/NumberField.vue +1 -1
  10. package/components/form/number-field/number-field.css +1 -1
  11. package/components/form/radio-group/RadioGroup.vue +2 -5
  12. package/components/form/slider/slider.css +2 -3
  13. package/components/form/tags-input/tags-input.css +1 -2
  14. package/components/form/textarea/textarea.css +1 -1
  15. package/components/layout/accordion/Accordion.vue +6 -27
  16. package/components/layout/collapsible/Collapsible.vue +9 -19
  17. package/components/layout/table/table.css +3 -3
  18. package/components/navigation/pagination/Pagination.vue +1 -1
  19. package/components/navigation/pagination/PaginationFooter.vue +23 -0
  20. package/components/navigation/pagination/pagination.css +128 -29
  21. package/components/navigation/sidebar/Sidebar.vue +326 -84
  22. package/components/navigation/sidebar/SidebarMenuItem.vue +199 -0
  23. package/components/navigation/sidebar/context.ts +21 -0
  24. package/components/navigation/sidebar/sidebar.css +346 -78
  25. package/components/navigation/sidebar/types.ts +13 -1
  26. package/components/overlay/alert-dialog/AlertDialog.vue +10 -31
  27. package/components/overlay/command/Command.vue +10 -38
  28. package/components/overlay/command/command.css +1 -1
  29. package/components/overlay/dropdown-menu/DropdownMenu.vue +37 -62
  30. package/components/overlay/modal/Modal.vue +7 -28
  31. package/components/overlay/popover/Popover.vue +9 -31
  32. package/components/overlay/sheet/Sheet.vue +7 -28
  33. package/components/overlay/tooltip/Tooltip.vue +14 -19
  34. package/{utils/controllers/dialog.ts → composables/useDialog.ts} +90 -78
  35. package/composables/useDisclosure.ts +113 -0
  36. package/composables/useEventListener.ts +16 -0
  37. package/{utils/controllers/menu.ts → composables/useMenu.ts} +66 -102
  38. package/{utils/controllers/popover.ts → composables/usePopover.ts} +107 -120
  39. package/package.json +6 -38
  40. package/styles/0-settings/colors.css +8 -1
  41. package/styles/0-settings/layout.css +18 -0
  42. package/styles/0-settings/motion.css +2 -2
  43. package/styles/0-settings/spacing.css +3 -1
  44. package/utils/navigation/sidebar.ts +97 -0
  45. package/utils/validation/form.ts +6 -9
  46. package/components/action/button/Button.astro +0 -95
  47. package/components/action/button/IconButton.astro +0 -86
  48. package/components/display/avatar/Avatar.astro +0 -17
  49. package/components/display/avatar/AvatarStack.astro +0 -9
  50. package/components/display/badge/Badge.astro +0 -15
  51. package/components/display/empty/Empty.astro +0 -9
  52. package/components/display/icon/Icon.astro +0 -52
  53. package/components/feedback/alert/Alert.astro +0 -52
  54. package/components/feedback/progress/Progress.astro +0 -68
  55. package/components/feedback/skeleton/Skeleton.astro +0 -32
  56. package/components/feedback/spinner/Spinner.astro +0 -25
  57. package/components/feedback/toast/Toast.astro +0 -50
  58. package/components/form/checkbox/Checkbox.astro +0 -79
  59. package/components/form/date-input/DateInput.astro +0 -105
  60. package/components/form/form/Form.astro +0 -106
  61. package/components/form/input-otp/InputOTP.astro +0 -147
  62. package/components/form/label/Label.astro +0 -13
  63. package/components/form/number-field/NumberField.astro +0 -142
  64. package/components/form/radio-group/RadioGroup.astro +0 -105
  65. package/components/form/select/Select.astro +0 -105
  66. package/components/form/slider/Slider.astro +0 -205
  67. package/components/form/switch/Switch.astro +0 -75
  68. package/components/form/tags-input/TagsInput.astro +0 -153
  69. package/components/form/text-input/TextInput.astro +0 -84
  70. package/components/form/textarea/Textarea.astro +0 -86
  71. package/components/layout/card/Card.astro +0 -13
  72. package/components/layout/separator/Separator.astro +0 -31
  73. package/components/layout/table/Table.astro +0 -116
  74. package/components/navigation/breadcrumb/Breadcrumb.astro +0 -36
  75. package/components/navigation/navbar/Navbar.astro +0 -62
  76. package/components/navigation/sidebar/Sidebar.astro +0 -132
  77. package/components/navigation/sidebar/SidebarItemRender.astro +0 -83
  78. package/components/navigation/sidebar/SidebarItemRender.vue +0 -98
  79. package/components/overlay/alert-dialog/AlertDialog.astro +0 -112
  80. package/components/overlay/modal/Modal.astro +0 -66
  81. package/utils/controllers/disclosure.ts +0 -117
  82. package/utils/controllers/form.ts +0 -524
  83. package/utils/controllers/index.ts +0 -39
  84. package/utils/controllers/number-field.ts +0 -103
  85. package/utils/controllers/otp.ts +0 -252
  86. package/utils/controllers/sidebar.ts +0 -610
  87. package/utils/controllers/slider.ts +0 -336
  88. package/utils/controllers/tags-input.ts +0 -255
  89. package/utils/controllers/toast.ts +0 -426
@@ -1,11 +1,18 @@
1
+ import {
2
+ type MaybeRefOrGetter,
3
+ type Ref,
4
+ onBeforeUnmount,
5
+ onMounted,
6
+ toValue,
7
+ watch,
8
+ } from 'vue';
9
+
1
10
  /**
2
- * Popover controller — shared by Popover, Tooltip, DropdownMenu,
3
- * Combobox listbox, and HoverCard per the locked controller-sharing
4
- * mapping in `CONVENTIONS.md`.
11
+ * Popover behavior — shared by Popover, Tooltip, DropdownMenu, and the
12
+ * Combobox listbox per the sharing mapping in `CONVENTIONS.md`.
5
13
  *
6
- * The v1 popover family is built on top of the **native HTML Popover
7
- * API** per the locked "Use native APIs where applicable" rule. The
8
- * browser already handles:
14
+ * Built on the **native HTML Popover API** per the locked "Use native
15
+ * APIs where applicable" rule. The browser already handles:
9
16
  *
10
17
  * - Top-layer rendering (popover escapes any ancestor stacking
11
18
  * context / `overflow: hidden`)
@@ -14,37 +21,27 @@
14
21
  * - The `:popover-open` CSS pseudo-class for open-state styling
15
22
  * - The `toggle` event (fires on every open/close transition with
16
23
  * `event.newState === 'open' | 'closed'`)
17
- * - Focus management (the popover is rendered in the top-layer; the
18
- * browser does NOT auto-trap focus, but light-dismiss closes the
19
- * popover when focus moves outside in `auto` mode)
20
24
  *
21
- * What the browser does NOT provide and we layer on top:
25
+ * What the browser does NOT provide and the composable layers on top:
22
26
  *
23
27
  * - **Anchor positioning** — CSS anchor positioning (`anchor-name`
24
28
  * + `position-anchor`) is the modern declarative approach but
25
- * Firefox support is incomplete in 2026. We compute position in
29
+ * Firefox support is incomplete in 2026. Position is computed in
26
30
  * JS from the anchor's `getBoundingClientRect()` + popover
27
31
  * dimensions, applied via inline `style.left` / `style.top`. This
28
32
  * keeps the support floor at Chrome 114+ / Safari 17+ / Firefox
29
33
  * 125+ (the Popover API floor itself).
30
34
  * - **Placement-flip on viewport collision** — if `bottom-start`
31
- * overflows the bottom edge, we flip to `top-start`. Cross-axis
35
+ * overflows the bottom edge, flip to `top-start`. Cross-axis
32
36
  * overflow is handled by shifting along that axis to keep the
33
37
  * popover inside the viewport.
34
38
  * - **Reposition-on-scroll/resize** — while the popover is open,
35
39
  * `scroll` (capture) and `resize` listeners recompute the
36
40
  * position so the popover tracks its anchor.
37
- * - A **callback** so framework code can reflect open/close state
38
- * back into reactive state (`update:open`).
39
41
  * - **Anchor auto-detection** — if the consumer wires a trigger
40
- * button declaratively via `popovertarget="<id>"`, the controller
41
- * finds it via `document.querySelector('[popovertarget="<id>"]')`
42
- * on mount. An explicit anchor element overrides the lookup.
43
- *
44
- * Mount on:
45
- * - The popover element itself (the element that carries the
46
- * `popover` attribute). The controller reads / sets the attribute
47
- * based on `config.modal`.
42
+ * button declaratively via `popovertarget="<id>"`, the composable
43
+ * finds it via `document.querySelector('[popovertarget="<id>"]')`.
44
+ * An explicit `anchor` option overrides the lookup.
48
45
  */
49
46
 
50
47
  /**
@@ -78,14 +75,13 @@ export type Placement =
78
75
  | 'left-end';
79
76
 
80
77
  /**
81
- * Imperative control surface returned by `mountPopover`. The
82
- * controller owns the popover's runtime state (open/closed,
83
- * positioning listeners) and lets the native Popover API handle
84
- * top-layer rendering and light-dismiss. Frameworks call
85
- * `show()` / `hide()` and watch incoming prop changes; they never
86
- * own state directly.
78
+ * Handle returned by `usePopover`. The composable owns the popover's
79
+ * runtime side-effects (positioning, listeners) and lets the native
80
+ * Popover API handle top-layer rendering and light-dismiss. The
81
+ * component's reactive prop stays the source of truth for the
82
+ * open/closed boolean.
87
83
  */
88
- export interface PopoverControl {
84
+ export interface PopoverHandle {
89
85
  /** Open the popover and position it against its anchor. */
90
86
  show(): void;
91
87
  /** Close the popover and tear down positioning listeners. */
@@ -96,33 +92,32 @@ export interface PopoverControl {
96
92
  isOpen(): boolean;
97
93
  /** Recompute position. Called automatically on scroll / resize while open. */
98
94
  reposition(): void;
99
- /** Release listeners and reset attributes. */
100
- teardown(): void;
101
95
  }
102
96
 
103
- export interface PopoverConfig {
97
+ export interface UsePopoverOptions {
104
98
  /**
105
99
  * The element that anchors positioning (typically the trigger
106
- * button). When `null`, the controller looks for a trigger via
100
+ * button). Resolved lazily on every reposition, so a getter may
101
+ * return `null` until the trigger exists. When absent, the
102
+ * composable looks for a trigger via
107
103
  * `document.querySelector('[popovertarget="<el.id>"]')`. If
108
104
  * neither is available the popover still opens but is positioned
109
- * at the viewport origin (0,0) — caller is responsible for
110
- * supplying an anchor before `show()` is meaningful.
105
+ * at the viewport origin (0,0).
111
106
  */
112
- anchor?: HTMLElement | null;
107
+ anchor?: MaybeRefOrGetter<HTMLElement | null>;
113
108
  /**
114
- * Initial placement. The controller flips to the opposite side
109
+ * Initial placement. The composable flips to the opposite side
115
110
  * automatically on viewport collision and shifts along the
116
111
  * cross-axis to keep the popover inside the viewport.
117
112
  * Default: `'bottom-start'`.
118
113
  */
119
- placement?: Placement;
114
+ placement?: MaybeRefOrGetter<Placement>;
120
115
  /**
121
116
  * Offset from the anchor edge in pixels. The popover is pushed
122
117
  * away from the anchor by this many pixels along the placement
123
118
  * axis. Default: `8`.
124
119
  */
125
- offset?: number;
120
+ offset?: MaybeRefOrGetter<number>;
126
121
  /**
127
122
  * When `true`, the popover uses `popover="manual"` — the user
128
123
  * cannot dismiss via outside click / focus loss / Escape; the
@@ -131,27 +126,27 @@ export interface PopoverConfig {
131
126
  * provides browser-native light-dismiss.
132
127
  */
133
128
  modal?: boolean;
129
+ /**
130
+ * Controlled open state. When the getter yields a boolean, the
131
+ * popover follows it: `true` shows, `false` hides, `undefined` is
132
+ * ignored. The initial value is applied on mount.
133
+ */
134
+ open?: () => boolean | undefined;
134
135
  /**
135
136
  * Called after the browser fires the `toggle` event with
136
- * `newState === 'open'`. The framework reflects this into
137
+ * `newState === 'open'`. The component reflects this into
137
138
  * reactive state (e.g. `emit('update:open', true)`).
138
139
  */
139
140
  onOpen?: () => void;
140
141
  /**
141
142
  * Called after the browser fires the `toggle` event with
142
143
  * `newState === 'closed'` — including light-dismiss closures
143
- * the controller did not initiate. The framework reflects this
144
+ * the composable did not initiate. The component reflects this
144
145
  * into reactive state (e.g. `emit('update:open', false)`).
145
146
  */
146
147
  onClose?: () => void;
147
148
  }
148
149
 
149
- /**
150
- * The DOM CSSStyleDeclaration assignment shape we use for
151
- * positioning. Inline styles win over the popover's own CSS for
152
- * `left` / `top`, which is what we want — the CSS supplies the
153
- * surface look, the controller supplies the position.
154
- */
155
150
  interface ComputedPosition {
156
151
  left: number;
157
152
  top: number;
@@ -283,60 +278,31 @@ function computePosition(
283
278
  }
284
279
 
285
280
  /**
286
- * Mount the popover controller on `el`, which is the popover
287
- * element itself (the element carrying the `popover` attribute).
288
- * The native Popover API provides top-layer rendering, light
289
- * dismiss, Escape handling, and the `:popover-open` pseudo-class;
290
- * this controller layers on anchor positioning, placement flip on
291
- * viewport collision, scroll/resize tracking, and `data-state`
292
- * reflection so the existing CSS conventions keep working.
293
- *
294
- * Lifecycle:
295
- * 1. On mount: set the `popover` attribute (`'auto'` for light
296
- * dismiss; `'manual'` for explicit-close-only). Wire the
297
- * `toggle` event so the framework hears about open/close
298
- * transitions. Resolve the anchor (explicit `config.anchor`,
299
- * else `document.querySelector('[popovertarget="<el.id>"]')`).
300
- * 2. `show()`: call `el.showPopover()` (native top-layer + light
301
- * dismiss). Compute position via `computePosition` and apply
302
- * inline `style.left` / `style.top`. Wire scroll (capture) and
303
- * resize listeners that call `reposition()`.
304
- * 3. `hide()`: call `el.hidePopover()` (native focus restoration
305
- * if focus was inside the popover). Remove scroll/resize
306
- * listeners.
307
- * 4. `teardown()`: ensures hide ran, removes the `toggle`
308
- * listener.
281
+ * Popover behavior for the element carrying the `popover` attribute,
282
+ * rendered by the calling component. Wires itself on mount and cleans
283
+ * up before unmount.
309
284
  *
310
- * The controller does NOT decide when to close — `onOpen` /
311
- * `onClose` are invoked when the browser fires the native `toggle`
312
- * event. The caller is responsible for reflecting that into
313
- * framework state. The framework's reactive prop is the source of
314
- * truth for the open/closed boolean; the controller is the source
315
- * of truth for runtime side-effects (positioning, listeners).
285
+ * The composable does NOT decide when to close — `onOpen` / `onClose`
286
+ * are invoked when the browser fires the native `toggle` event, which
287
+ * catches both composable-initiated transitions (show/hide) and
288
+ * browser-initiated ones (light-dismiss, declarative `popovertarget`
289
+ * button click). The component's reactive prop is the source of truth
290
+ * for the open/closed boolean; the composable is the source of truth
291
+ * for runtime side-effects (positioning, listeners).
316
292
  */
317
- export function mountPopover(
318
- el: HTMLElement,
319
- config: PopoverConfig = {},
320
- ): PopoverControl {
321
- const placement: Placement = config.placement ?? 'bottom-start';
322
- const offset = config.offset ?? 8;
323
- const modal = config.modal ?? false;
324
-
325
- // `popover="auto"` opts into native light-dismiss: outside click,
326
- // focus loss, and Escape all close the popover. `popover="manual"`
327
- // means the popover stays until explicitly hidden via JS — used
328
- // for non-dismissable surfaces (e.g., a modal-style popover that
329
- // requires a button click to close).
330
- el.setAttribute('popover', modal ? 'manual' : 'auto');
331
- el.setAttribute('data-state', 'closed');
332
-
333
- // Anchor resolution. Explicit `config.anchor` wins; otherwise
293
+ export function usePopover(
294
+ elRef: Ref<HTMLElement | null>,
295
+ options: UsePopoverOptions = {},
296
+ ): PopoverHandle {
297
+ // Anchor resolution. An explicit `anchor` option wins; otherwise
334
298
  // look up the trigger via the native `popovertarget` attribute.
335
- // Resolved lazily on each show() so the consumer can swap the
336
- // trigger between mount and first open without re-mounting.
299
+ // Resolved lazily on every reposition so the consumer can supply
300
+ // the trigger after mount without re-wiring.
337
301
  function resolveAnchor(): HTMLElement | null {
338
- if (config.anchor) return config.anchor;
339
- if (!el.id) return null;
302
+ const el = elRef.value;
303
+ const anchor = toValue(options.anchor);
304
+ if (anchor) return anchor;
305
+ if (!el?.id) return null;
340
306
  return document.querySelector<HTMLElement>(
341
307
  `[popovertarget="${CSS.escape(el.id)}"]`,
342
308
  );
@@ -345,13 +311,14 @@ export function mountPopover(
345
311
  let scrollListenersWired = false;
346
312
 
347
313
  function reposition(): void {
314
+ const el = elRef.value;
348
315
  const anchor = resolveAnchor();
349
- if (!anchor) return;
316
+ if (!el || !anchor) return;
350
317
  const { left, top, resolvedPlacement } = computePosition(
351
318
  anchor,
352
319
  el,
353
- placement,
354
- offset,
320
+ toValue(options.placement) ?? 'bottom-start',
321
+ toValue(options.offset) ?? 8,
355
322
  );
356
323
  el.style.left = `${left}px`;
357
324
  el.style.top = `${top}px`;
@@ -379,56 +346,76 @@ export function mountPopover(
379
346
  }
380
347
 
381
348
  // Native `toggle` event fires on every open/close transition
382
- // with `event.newState` set to `'open'` or `'closed'`. We use it
383
- // as the single source of truth for `data-state` reflection and
384
- // for invoking the framework callbacks — this catches both
385
- // controller-initiated transitions (show/hide) and
386
- // browser-initiated ones (light-dismiss, declarative
387
- // `popovertarget` button click).
349
+ // with `event.newState` set to `'open'` or `'closed'`. It is the
350
+ // single source of truth for `data-state` reflection and for
351
+ // invoking the component callbacks.
388
352
  function handleToggle(event: Event): void {
353
+ const el = elRef.value;
354
+ if (!el) return;
389
355
  const evt = event as ToggleEvent;
390
356
  if (evt.newState === 'open') {
391
357
  el.setAttribute('data-state', 'open');
392
358
  // First reposition runs after the popover has been laid out
393
359
  // by the browser (toggle fires after layout). Wire the
394
- // scroll/resize listeners now so subsequent movement
395
- // tracks.
360
+ // scroll/resize listeners now so subsequent movement tracks.
396
361
  reposition();
397
362
  wireScrollListeners();
398
- config.onOpen?.();
363
+ options.onOpen?.();
399
364
  } else {
400
365
  el.setAttribute('data-state', 'closed');
401
366
  unwireScrollListeners();
402
- config.onClose?.();
367
+ options.onClose?.();
403
368
  }
404
369
  }
405
370
 
406
- el.addEventListener('toggle', handleToggle);
407
-
408
371
  function show(): void {
409
- if (el.matches(':popover-open')) return; // idempotent
372
+ const el = elRef.value;
373
+ if (!el || el.matches(':popover-open')) return; // idempotent
410
374
  el.showPopover();
411
375
  }
412
376
 
413
377
  function hide(): void {
414
- if (!el.matches(':popover-open')) return; // idempotent
378
+ const el = elRef.value;
379
+ if (!el || !el.matches(':popover-open')) return; // idempotent
415
380
  el.hidePopover();
416
381
  }
417
382
 
418
383
  function toggle(): void {
419
- if (el.matches(':popover-open')) hide();
384
+ if (isOpen()) hide();
420
385
  else show();
421
386
  }
422
387
 
423
388
  function isOpen(): boolean {
424
- return el.matches(':popover-open');
389
+ return elRef.value?.matches(':popover-open') ?? false;
425
390
  }
426
391
 
427
- function teardown(): void {
428
- if (isOpen()) hide();
392
+ onMounted(() => {
393
+ const el = elRef.value;
394
+ if (!el) return;
395
+ // `popover="auto"` opts into native light-dismiss: outside click,
396
+ // focus loss, and Escape all close the popover. `popover="manual"`
397
+ // means the popover stays until explicitly hidden via JS.
398
+ el.setAttribute('popover', options.modal ? 'manual' : 'auto');
399
+ el.setAttribute('data-state', 'closed');
400
+ el.addEventListener('toggle', handleToggle);
401
+ if (options.open && toValue(options.open)) show();
402
+ });
403
+
404
+ if (options.open) {
405
+ watch(options.open, (value) => {
406
+ if (!elRef.value || value === undefined) return;
407
+ if (value) show();
408
+ else hide();
409
+ });
410
+ }
411
+
412
+ onBeforeUnmount(() => {
413
+ const el = elRef.value;
414
+ if (!el) return;
415
+ if (el.matches(':popover-open')) el.hidePopover();
429
416
  unwireScrollListeners();
430
417
  el.removeEventListener('toggle', handleToggle);
431
- }
418
+ });
432
419
 
433
- return { show, hide, toggle, isOpen, reposition, teardown };
420
+ return { show, hide, toggle, isOpen, reposition };
434
421
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pienter/ui",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared Pienter UI components, styles, icons, and browser utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,90 +27,62 @@
27
27
  "default": "./styles/main.css"
28
28
  },
29
29
  "./styles/*": "./styles/*",
30
- "./components/Button.astro": "./components/action/button/Button.astro",
31
30
  "./components/Button.vue": "./components/action/button/Button.vue",
32
- "./components/IconButton.astro": "./components/action/button/IconButton.astro",
33
31
  "./components/IconButton.vue": "./components/action/button/IconButton.vue",
34
32
  "./components/Toggle.vue": "./components/action/toggle/Toggle.vue",
35
33
  "./components/ToggleGroup.vue": "./components/action/toggle-group/ToggleGroup.vue",
36
- "./components/Avatar.astro": "./components/display/avatar/Avatar.astro",
37
34
  "./components/Avatar.vue": "./components/display/avatar/Avatar.vue",
38
- "./components/AvatarStack.astro": "./components/display/avatar/AvatarStack.astro",
39
35
  "./components/AvatarStack.vue": "./components/display/avatar/AvatarStack.vue",
40
- "./components/Badge.astro": "./components/display/badge/Badge.astro",
41
36
  "./components/Badge.vue": "./components/display/badge/Badge.vue",
42
- "./components/Empty.astro": "./components/display/empty/Empty.astro",
43
37
  "./components/Empty.vue": "./components/display/empty/Empty.vue",
44
- "./components/Icon.astro": "./components/display/icon/Icon.astro",
45
38
  "./components/Icon.vue": "./components/display/icon/Icon.vue",
46
- "./components/Alert.astro": "./components/feedback/alert/Alert.astro",
47
39
  "./components/Alert.vue": "./components/feedback/alert/Alert.vue",
48
- "./components/Progress.astro": "./components/feedback/progress/Progress.astro",
49
40
  "./components/Progress.vue": "./components/feedback/progress/Progress.vue",
50
- "./components/Skeleton.astro": "./components/feedback/skeleton/Skeleton.astro",
51
41
  "./components/Skeleton.vue": "./components/feedback/skeleton/Skeleton.vue",
52
- "./components/Spinner.astro": "./components/feedback/spinner/Spinner.astro",
53
42
  "./components/Spinner.vue": "./components/feedback/spinner/Spinner.vue",
54
- "./components/Toast.astro": "./components/feedback/toast/Toast.astro",
55
43
  "./components/Toast.vue": "./components/feedback/toast/Toast.vue",
56
- "./components/Checkbox.astro": "./components/form/checkbox/Checkbox.astro",
57
44
  "./components/Checkbox.vue": "./components/form/checkbox/Checkbox.vue",
58
45
  "./components/Combobox.vue": "./components/form/combobox/Combobox.vue",
59
- "./components/DateInput.astro": "./components/form/date-input/DateInput.astro",
60
46
  "./components/DateInput.vue": "./components/form/date-input/DateInput.vue",
61
- "./components/Form.astro": "./components/form/form/Form.astro",
62
47
  "./components/Form.vue": "./components/form/form/Form.vue",
63
- "./components/InputOTP.astro": "./components/form/input-otp/InputOTP.astro",
64
48
  "./components/InputOTP.vue": "./components/form/input-otp/InputOTP.vue",
65
- "./components/Label.astro": "./components/form/label/Label.astro",
66
49
  "./components/Label.vue": "./components/form/label/Label.vue",
67
- "./components/NumberField.astro": "./components/form/number-field/NumberField.astro",
68
50
  "./components/NumberField.vue": "./components/form/number-field/NumberField.vue",
69
- "./components/RadioGroup.astro": "./components/form/radio-group/RadioGroup.astro",
70
51
  "./components/RadioGroup.vue": "./components/form/radio-group/RadioGroup.vue",
71
52
  "./components/RadioGroup.types": "./components/form/radio-group/types.ts",
72
53
  "./components/Segmented.vue": "./components/form/select/Segmented.vue",
73
- "./components/Select.astro": "./components/form/select/Select.astro",
74
54
  "./components/Select.vue": "./components/form/select/Select.vue",
75
- "./components/Slider.astro": "./components/form/slider/Slider.astro",
76
55
  "./components/Slider.vue": "./components/form/slider/Slider.vue",
77
- "./components/Switch.astro": "./components/form/switch/Switch.astro",
78
56
  "./components/Switch.vue": "./components/form/switch/Switch.vue",
79
- "./components/TagsInput.astro": "./components/form/tags-input/TagsInput.astro",
80
57
  "./components/TagsInput.vue": "./components/form/tags-input/TagsInput.vue",
81
- "./components/TextInput.astro": "./components/form/text-input/TextInput.astro",
82
58
  "./components/TextInput.vue": "./components/form/text-input/TextInput.vue",
83
- "./components/Textarea.astro": "./components/form/textarea/Textarea.astro",
84
59
  "./components/Textarea.vue": "./components/form/textarea/Textarea.vue",
85
60
  "./components/Accordion.vue": "./components/layout/accordion/Accordion.vue",
86
- "./components/Card.astro": "./components/layout/card/Card.astro",
87
61
  "./components/Card.vue": "./components/layout/card/Card.vue",
88
62
  "./components/Collapsible.vue": "./components/layout/collapsible/Collapsible.vue",
89
- "./components/Separator.astro": "./components/layout/separator/Separator.astro",
90
63
  "./components/Separator.vue": "./components/layout/separator/Separator.vue",
91
- "./components/Table.astro": "./components/layout/table/Table.astro",
92
64
  "./components/Table.vue": "./components/layout/table/Table.vue",
93
65
  "./components/TableRow.vue": "./components/layout/table/TableRow.vue",
94
66
  "./components/DataTable.vue": "./components/layout/table/DataTable.vue",
95
67
  "./components/Table.types": "./components/layout/table/types.ts",
96
- "./components/Breadcrumb.astro": "./components/navigation/breadcrumb/Breadcrumb.astro",
97
68
  "./components/Breadcrumb.vue": "./components/navigation/breadcrumb/Breadcrumb.vue",
98
- "./components/Navbar.astro": "./components/navigation/navbar/Navbar.astro",
99
69
  "./components/Navbar.vue": "./components/navigation/navbar/Navbar.vue",
100
70
  "./components/Pagination.vue": "./components/navigation/pagination/Pagination.vue",
101
- "./components/Sidebar.astro": "./components/navigation/sidebar/Sidebar.astro",
71
+ "./components/PaginationFooter.vue": "./components/navigation/pagination/PaginationFooter.vue",
102
72
  "./components/Sidebar.vue": "./components/navigation/sidebar/Sidebar.vue",
103
73
  "./components/Sidebar.types": "./components/navigation/sidebar/types.ts",
104
74
  "./components/Tabs.vue": "./components/navigation/tabs/Tabs.vue",
105
- "./components/AlertDialog.astro": "./components/overlay/alert-dialog/AlertDialog.astro",
106
75
  "./components/AlertDialog.vue": "./components/overlay/alert-dialog/AlertDialog.vue",
107
76
  "./components/Command.vue": "./components/overlay/command/Command.vue",
108
77
  "./components/DropdownMenu.vue": "./components/overlay/dropdown-menu/DropdownMenu.vue",
109
- "./components/Modal.astro": "./components/overlay/modal/Modal.astro",
110
78
  "./components/Modal.vue": "./components/overlay/modal/Modal.vue",
111
79
  "./components/Popover.vue": "./components/overlay/popover/Popover.vue",
112
80
  "./components/Sheet.vue": "./components/overlay/sheet/Sheet.vue",
113
81
  "./components/Tooltip.vue": "./components/overlay/tooltip/Tooltip.vue",
82
+ "./composables/useDialog": "./composables/useDialog.ts",
83
+ "./composables/useDisclosure": "./composables/useDisclosure.ts",
84
+ "./composables/useMenu": "./composables/useMenu.ts",
85
+ "./composables/usePopover": "./composables/usePopover.ts",
114
86
  "./composables/useUrlSort": "./composables/useUrlSort.ts",
115
87
  "./icons": "./icons/index.ts",
116
88
  "./icons/*": "./icons/*",
@@ -129,14 +101,10 @@
129
101
  "!components/**/AUDIT.md"
130
102
  ],
131
103
  "peerDependencies": {
132
- "astro": ">=5.0.0",
133
104
  "vue": ">=3.5.0",
134
105
  "vue-router": ">=4.0.0"
135
106
  },
136
107
  "peerDependenciesMeta": {
137
- "astro": {
138
- "optional": true
139
- },
140
108
  "vue": {
141
109
  "optional": true
142
110
  },
@@ -14,7 +14,7 @@
14
14
  --clr-base panels and cards (the front-most surface)
15
15
  --clr-mantle the page behind them
16
16
  --clr-surface-0 accent panels, hover fills, table stripes
17
- --clr-surface-1 hairline borders
17
+ --clr-surface-1 hairline borders, and the neutral selected fill
18
18
  --clr-overlay input borders (clears 3:1)
19
19
  --clr-subtext secondary text (clears 4.5:1)
20
20
  --clr-text body text
@@ -197,6 +197,13 @@
197
197
  --bg-clr-surface: var(--clr-base);
198
198
  --bg-clr-surface-2: var(--clr-surface-0);
199
199
  --bg-clr-accent: var(--clr-surface-0);
200
+ /* The neutral "selected" rung — one step past the hover fill, so a chosen
201
+ item stays distinct from a merely hovered one. It rides --clr-surface-1
202
+ because that is the only neutral that keeps a similar distance from the
203
+ surface in BOTH themes: darker than the surface in light, lighter in
204
+ dark. Pair it with --text-clr-base, never --text-clr-inverse — inverse
205
+ is near-white in both themes, so it would vanish here in the light one. */
206
+ --bg-clr-selected: var(--clr-surface-1);
200
207
  --bg-clr-brand: var(--clr-blue-fill);
201
208
  --bg-clr-brand-hover: var(--clr-blue-fill-strong);
202
209
  --bg-clr-brand-ink: var(--clr-inverse);
@@ -36,11 +36,29 @@
36
36
 
37
37
  --opacity-disabled: 0.45;
38
38
 
39
+ /* Stacking ladder. Steps of 10 leave room to slot one in. A rung only
40
+ ranks siblings inside the SAME stacking context — it is not a promise
41
+ about the page; escape one by raising the root, not the layer. */
42
+ --layer-below: -1; /* behind the surface that renders it — a sentinel */
43
+ --layer-0: 0; /* in flow */
44
+ --layer-1: 10; /* raised within a component */
45
+ --layer-2: 20; /* sticky furniture */
46
+ --layer-3: 30; /* content-anchored overlays */
47
+ --layer-4: 40; /* scrims and backdrops */
48
+ --layer-5: 50; /* modal surfaces */
49
+ --layer-top: 1000; /* above everything, including a modal — a sentinel */
50
+
39
51
  /* Soft elevation. The shadow reads as depth, not as a drawn edge. */
40
52
  --shadow-sm: 0 1px 2px
41
53
  color-mix(in srgb, var(--shadow-clr) 14%, transparent);
42
54
  --shadow-md: 0 3px 8px
43
55
  color-mix(in srgb, var(--shadow-clr) 18%, transparent);
56
+ /* Overlay elevation — a surface that covers page content rather than
57
+ sitting on it. --shadow-md is tuned for a card in the flow and reads
58
+ flat under something as large as a sidebar flyout, so this one spreads
59
+ further and sits lighter. */
60
+ --shadow-lg: 0 8px 24px
61
+ color-mix(in srgb, var(--shadow-clr) 16%, transparent);
44
62
  --shadow-brand: 0 3px 8px
45
63
  color-mix(in srgb, var(--outline-clr-base) 18%, transparent);
46
64
 
@@ -1,6 +1,6 @@
1
1
  :root {
2
- --duration-fast: 80ms; /* hover and press feedback */
3
- --duration-base: 120ms; /* colour and background transitions */
2
+ --duration-fast: 120ms; /* hover and press feedback */
3
+ --duration-base: 250ms; /* colour and background transitions */
4
4
  --duration-slow: 2s; /* one spinner revolution — deliberately unhurried */
5
5
 
6
6
  --ease-base: ease;
@@ -11,5 +11,7 @@
11
11
 
12
12
  /* Layout-shell widths (used by Sidebar et al.) */
13
13
  --space-sidebar-width: 16rem;
14
- --space-sidebar-width-collapsed: 4rem;
14
+ /* Wide enough for an icon over a `--step--3` caption. At 4rem real
15
+ labels ("Dashboard", "Components") ellipsised to two characters. */
16
+ --space-sidebar-width-collapsed: 4.75rem;
15
17
  }