@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.
- package/CHANGELOG.md +259 -0
- package/CONVENTIONS.md +297 -385
- package/README.md +3 -18
- package/components/feedback/toast/toast.css +1 -1
- package/components/form/combobox/Combobox.vue +21 -38
- package/components/form/combobox/combobox.css +1 -1
- package/components/form/form/Form.vue +1 -2
- package/components/form/label/label.css +1 -1
- package/components/form/number-field/NumberField.vue +1 -1
- package/components/form/number-field/number-field.css +1 -1
- package/components/form/radio-group/RadioGroup.vue +2 -5
- package/components/form/slider/slider.css +2 -3
- package/components/form/tags-input/tags-input.css +1 -2
- package/components/form/textarea/textarea.css +1 -1
- package/components/layout/accordion/Accordion.vue +6 -27
- package/components/layout/collapsible/Collapsible.vue +9 -19
- package/components/layout/table/table.css +3 -3
- package/components/navigation/pagination/Pagination.vue +1 -1
- package/components/navigation/pagination/PaginationFooter.vue +23 -0
- package/components/navigation/pagination/pagination.css +128 -29
- package/components/navigation/sidebar/Sidebar.vue +326 -84
- package/components/navigation/sidebar/SidebarMenuItem.vue +199 -0
- package/components/navigation/sidebar/context.ts +21 -0
- package/components/navigation/sidebar/sidebar.css +346 -78
- package/components/navigation/sidebar/types.ts +13 -1
- package/components/overlay/alert-dialog/AlertDialog.vue +10 -31
- package/components/overlay/command/Command.vue +10 -38
- package/components/overlay/command/command.css +1 -1
- package/components/overlay/dropdown-menu/DropdownMenu.vue +37 -62
- package/components/overlay/modal/Modal.vue +7 -28
- package/components/overlay/popover/Popover.vue +9 -31
- package/components/overlay/sheet/Sheet.vue +7 -28
- package/components/overlay/tooltip/Tooltip.vue +14 -19
- package/{utils/controllers/dialog.ts → composables/useDialog.ts} +90 -78
- package/composables/useDisclosure.ts +113 -0
- package/composables/useEventListener.ts +16 -0
- package/{utils/controllers/menu.ts → composables/useMenu.ts} +66 -102
- package/{utils/controllers/popover.ts → composables/usePopover.ts} +107 -120
- package/package.json +6 -38
- package/styles/0-settings/colors.css +8 -1
- package/styles/0-settings/layout.css +18 -0
- package/styles/0-settings/motion.css +2 -2
- package/styles/0-settings/spacing.css +3 -1
- package/utils/navigation/sidebar.ts +97 -0
- package/utils/validation/form.ts +6 -9
- package/components/action/button/Button.astro +0 -95
- package/components/action/button/IconButton.astro +0 -86
- package/components/display/avatar/Avatar.astro +0 -17
- package/components/display/avatar/AvatarStack.astro +0 -9
- package/components/display/badge/Badge.astro +0 -15
- package/components/display/empty/Empty.astro +0 -9
- package/components/display/icon/Icon.astro +0 -52
- package/components/feedback/alert/Alert.astro +0 -52
- package/components/feedback/progress/Progress.astro +0 -68
- package/components/feedback/skeleton/Skeleton.astro +0 -32
- package/components/feedback/spinner/Spinner.astro +0 -25
- package/components/feedback/toast/Toast.astro +0 -50
- package/components/form/checkbox/Checkbox.astro +0 -79
- package/components/form/date-input/DateInput.astro +0 -105
- package/components/form/form/Form.astro +0 -106
- package/components/form/input-otp/InputOTP.astro +0 -147
- package/components/form/label/Label.astro +0 -13
- package/components/form/number-field/NumberField.astro +0 -142
- package/components/form/radio-group/RadioGroup.astro +0 -105
- package/components/form/select/Select.astro +0 -105
- package/components/form/slider/Slider.astro +0 -205
- package/components/form/switch/Switch.astro +0 -75
- package/components/form/tags-input/TagsInput.astro +0 -153
- package/components/form/text-input/TextInput.astro +0 -84
- package/components/form/textarea/Textarea.astro +0 -86
- package/components/layout/card/Card.astro +0 -13
- package/components/layout/separator/Separator.astro +0 -31
- package/components/layout/table/Table.astro +0 -116
- package/components/navigation/breadcrumb/Breadcrumb.astro +0 -36
- package/components/navigation/navbar/Navbar.astro +0 -62
- package/components/navigation/sidebar/Sidebar.astro +0 -132
- package/components/navigation/sidebar/SidebarItemRender.astro +0 -83
- package/components/navigation/sidebar/SidebarItemRender.vue +0 -98
- package/components/overlay/alert-dialog/AlertDialog.astro +0 -112
- package/components/overlay/modal/Modal.astro +0 -66
- package/utils/controllers/disclosure.ts +0 -117
- package/utils/controllers/form.ts +0 -524
- package/utils/controllers/index.ts +0 -39
- package/utils/controllers/number-field.ts +0 -103
- package/utils/controllers/otp.ts +0 -252
- package/utils/controllers/sidebar.ts +0 -610
- package/utils/controllers/slider.ts +0 -336
- package/utils/controllers/tags-input.ts +0 -255
- 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
|
|
3
|
-
* Combobox listbox
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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.
|
|
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,
|
|
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
|
|
41
|
-
* finds it via `document.querySelector('[popovertarget="<id>"]')
|
|
42
|
-
*
|
|
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
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
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
|
|
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
|
|
97
|
+
export interface UsePopoverOptions {
|
|
104
98
|
/**
|
|
105
99
|
* The element that anchors positioning (typically the trigger
|
|
106
|
-
* button).
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
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
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
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
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
):
|
|
321
|
-
|
|
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
|
|
336
|
-
// trigger
|
|
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
|
-
|
|
339
|
-
|
|
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'`.
|
|
383
|
-
//
|
|
384
|
-
//
|
|
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
|
-
|
|
363
|
+
options.onOpen?.();
|
|
399
364
|
} else {
|
|
400
365
|
el.setAttribute('data-state', 'closed');
|
|
401
366
|
unwireScrollListeners();
|
|
402
|
-
|
|
367
|
+
options.onClose?.();
|
|
403
368
|
}
|
|
404
369
|
}
|
|
405
370
|
|
|
406
|
-
el.addEventListener('toggle', handleToggle);
|
|
407
|
-
|
|
408
371
|
function show(): void {
|
|
409
|
-
|
|
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
|
-
|
|
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 (
|
|
384
|
+
if (isOpen()) hide();
|
|
420
385
|
else show();
|
|
421
386
|
}
|
|
422
387
|
|
|
423
388
|
function isOpen(): boolean {
|
|
424
|
-
return
|
|
389
|
+
return elRef.value?.matches(':popover-open') ?? false;
|
|
425
390
|
}
|
|
426
391
|
|
|
427
|
-
|
|
428
|
-
|
|
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
|
|
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.
|
|
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/
|
|
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:
|
|
3
|
-
--duration-base:
|
|
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
|
-
--
|
|
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
|
}
|