@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,265 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.0 - 2026-08-30
6
+
7
+ ### Breaking
8
+
9
+ - The framework-agnostic controllers are gone; their behavior now lives in
10
+ Vue composables. `@pienter/ui/utils/controllers/*` (`mountDialog`,
11
+ `mountPopover`, `mountDisclosure`, `mountMenu` and their types) no longer
12
+ exists — import `useDialog`, `usePopover`, `useDisclosure`, and `useMenu`
13
+ from `@pienter/ui/composables/<name>` instead. The composables carry the
14
+ same behavior and options but own the lifecycle themselves: they take an
15
+ element ref plus options at setup, wire on mount, follow a controlled
16
+ `open` getter, and clean up on unmount, so the mount/watch/teardown dance
17
+ every component repeated is gone. `Placement` moved to
18
+ `@pienter/ui/composables/usePopover`. Component behavior is unchanged.
19
+
20
+ ## 0.4.0 - 2026-08-28
21
+
22
+ ### Breaking
23
+
24
+ - The library is now Vue-only. The Astro tier is removed in full: every
25
+ `*.astro` component export, the Astro-only controllers (`mountForm`,
26
+ `mountNumberField`, `mountInputOTP`, `mountSlider`, `mountTagsInput`,
27
+ `mountToast`) and their types, and the `Sidebar.mount` entrypoint no longer
28
+ exist, and `astro` is no longer an optional peer dependency. Every removed
29
+ component has a Vue counterpart — migrate
30
+ `@pienter/ui/components/X.astro` imports to
31
+ `@pienter/ui/components/X.vue`; there is no Astro migration path. The
32
+ shared controllers Vue consumes (`mountDialog`, `mountDisclosure`,
33
+ `mountPopover`, `mountMenu`) are unchanged.
34
+ - `Sidebar` follows the D13 layered split instead of a shared controller, so
35
+ `mountSidebar` is no longer exported from `@pienter/ui/utils/controllers`.
36
+ `utils/controllers/` is for code both tiers run; Sidebar's imperative
37
+ orchestration is Astro-only and now lives beside the component, exported as
38
+ `@pienter/ui/components/Sidebar.mount`. Vue no longer wraps it at all —
39
+ `Sidebar.vue` is reactive throughout, which is what D13 already required
40
+ ("wrapping an imperative DOM controller from Vue fights Vue's data flow…
41
+ the controller mutates the DOM, Vue patches it back, and race conditions
42
+ ensue"). Both tiers now derive from shared primitives in
43
+ `@pienter/ui/utils/navigation/sidebar`: `isHrefActive`, `deriveGroupKey`,
44
+ `computeSubmenuPosition`, `isSubmenuActive`, `groupContainsActive`.
45
+ `isHrefActive` and `deriveGroupKey` moved there from the controller barrel.
46
+ - `Sidebar`'s `items` prop is now `topItems`, alongside a new `bottomItems`.
47
+ The column has two nav regions — the top nav, which takes the leftover
48
+ height and scrolls, and the bottom nav, pinned to the foot below a divider
49
+ for settings, account and the like. Entries in either get identical wiring:
50
+ active-href matching, keyboard navigation, and collapsed flyouts for their
51
+ groups. The `#footer` slot is renamed `#bottom` and still takes arbitrary
52
+ content beside `bottomItems`. Migrate `:items="nav"` to `:top-items="nav"`.
53
+ - The private recursive renderer is `SidebarMenuItem` (was
54
+ `SidebarItemRender`), matching the `data-sidebar-menu-item` hook it emits.
55
+ Internal to the component — no export changed — but the file names did.
56
+ - The nav list classes are `.pui-sidebar__top-nav` and
57
+ `.pui-sidebar__bottom-nav` (was `.pui-sidebar__menu`), and the foot wrapper
58
+ is `.pui-sidebar__bottom` (was `.pui-sidebar__footer`). Consumers styling
59
+ those internals will need to follow.
60
+ - `Sidebar` no longer uses a roving tabindex. Every menu entry is its own tab
61
+ stop, as an ordinary `<nav>` full of links should be. Roving left the whole
62
+ sidebar with a **single** tab stop, so a rail of four destinations had three
63
+ that Tab could never reach. This also matches CONVENTIONS.md, which reserves
64
+ roving for "single-focus-per-group controls … where one item represents the
65
+ entire group's focus target" — a navigation landmark is not that. Arrow keys,
66
+ Home and End still work, now as an accelerator on top of the natural order
67
+ rather than a replacement for it. Consumers relying on exactly one sidebar tab
68
+ stop will see more; nothing else changes.
69
+
70
+ ### Changed
71
+
72
+ - `--space-sidebar-width-collapsed` is `4.75rem` (was `4rem`). At 4rem the new
73
+ rail captions had about 2rem of usable width once the panel's padding was
74
+ taken out, and real labels ellipsised to two characters.
75
+ - The `Sidebar` focus ring colour follows the entry's tone via
76
+ `--pui-sidebar-ring`, matching how `--btn-ring` works for Button: neutral by
77
+ default, brand on the entry wearing the brand wash, so the ring no longer
78
+ fights the fill underneath it.
79
+ - The collapsed rail marks which icon the open flyout belongs to. Previously
80
+ only hover did, so a flyout opened from the keyboard left nothing pointing
81
+ back at its icon once focus moved into the panel.
82
+ - The collapsed submenu flyout now sits on `--layer-below`, so it genuinely
83
+ passes under the rail rather than across it — the panel's own background
84
+ hides where the slide starts. The open-flyout rule pairs the rung with
85
+ `isolation: isolate`, because "behind the rail" only holds while the sidebar
86
+ is the stacking context containing the flyout; without it the panel would go
87
+ behind the *page* in any shell that doesn't happen to give the root a
88
+ context.
89
+ - The collapsed submenu flyout slides in from behind the rail when it opens,
90
+ and disappears instantly when it closes. The transition is declared only on
91
+ the open state — a transition belongs to the state being moved *to* — so
92
+ there is nothing to undeclare for the closing direction. An exit animation on
93
+ a dismissal only delays the content behind it, and the panel covers page
94
+ content. `visibility` stays untransitioned so the entries are focusable the
95
+ instant the group takes focus; under `prefers-reduced-motion` the panel
96
+ simply appears.
97
+ - The collapsed `Sidebar` rail stacks a caption under each icon instead of
98
+ going icon-only, so it is navigable without hovering. The rail sets its own
99
+ inline padding rather than inheriting the expanded panel's, and captions wrap
100
+ to a second line instead of truncating — an ellipsis hides the one thing a
101
+ caption is for.
102
+ - A collapsed rail group whose subtree contains the current page carries the
103
+ brand wash. On the rail its children are hidden, so the group is the only
104
+ indicator of location, and a tint alone lost to the hover fill — a hovered
105
+ entry read as more current than the current one. The wash is reverted in
106
+ drawer mode, where the sidebar is a full expanded list: there the group sits
107
+ directly above its own current child and two identical fills merged into a
108
+ single block. Brand ink alone carries "this section holds the current page"
109
+ there, and the wash is left to the entry that actually is the current page.
110
+ - The `Sidebar` focus ring uses `--outline-width` / `--outline-offset` instead
111
+ of hard-coded `3px` / `2px`. It is the standard outset ring, so it reads as a
112
+ ring around the entry rather than a line cutting into it and stays legible
113
+ over the selected entry's wash; the collapsed rail reserves inline padding so
114
+ the column edge never clips it.
115
+ - A `Sidebar` link's selected state uses `--bg-clr-brand-soft` rather than
116
+ `--bg-clr-accent`, which is what hover already uses — the current page was
117
+ indistinguishable from whatever the pointer was over.
118
+
119
+ ### Fixed
120
+
121
+ - `Sidebar`'s focus ring is no longer clipped in the top nav. That list is a
122
+ scroll container — `overflow-y: auto` forces `overflow-x: auto` — and a
123
+ scroll container clips at its padding box, so an entry filling it
124
+ edge-to-edge had the ring sliced off both sides. The list now reserves the
125
+ ring's width as `padding-inline` with a matching negative `margin-inline`,
126
+ so nothing moves.
127
+ - Consumers must import `@pienter/ui/styles` before any component. Each
128
+ component stylesheet is a bare `@layer components { … }`, and layer order is
129
+ fixed by first encounter — so a component loading first registers
130
+ `components` ahead of `base`, and every base element rule starts outranking
131
+ component rules. In the Vue playground that made base's `a { color: … }` beat
132
+ `.pui-sidebar__link`, turning sidebar links brand-coloured. The playground
133
+ entry point now imports the stylesheet first; the library contract is
134
+ unchanged (main.css declares the order once).
135
+ - The mobile `Sidebar` drawer no longer paints under the consumer's app header.
136
+ The rule that lifts the sidebar while a collapsed flyout is open applied at
137
+ every width, and a `z-index` other than `auto` makes a **grid or flex item** a
138
+ stacking context even while `position: static` — which is what a sidebar in a
139
+ layout shell normally is. The drawer's own `z-index: 50` was therefore sealed
140
+ inside that context and lost to any header above 30. The lift is now scoped
141
+ above the drawer breakpoint, where the flyout actually exists, and the drawer
142
+ lifts the root itself while open.
143
+ - `Sidebar`'s focus handler no longer rewrites every entry's `tabindex`. The
144
+ roving pattern was removed from the initial render but survived in the
145
+ `focusin` handler, which demoted every other entry to `-1` the moment one was
146
+ focused — so the sidebar looked fully tabbable until you actually tabbed into
147
+ it, and then collapsed to a single stop.
148
+ - A collapsed submenu flyout's entries are reachable by Tab. `visibility` was in
149
+ the flyout's transition list, so it flipped partway through the fade and the
150
+ entries were still unfocusable when Tab arrived — landing on a group jumped
151
+ straight past its flyout to the next rail item. `visibility` now steps
152
+ immediately on open and is delayed only on close, so the fade-out still plays.
153
+ - The collapsed rail no longer stays visible below `drawerBreakpoint`. The
154
+ drawer's `width: 0` sat on a bare `.pui-sidebar` inside a media query, which
155
+ grants no specificity, so `.pui-sidebar[data-state='collapsed']` outranked it
156
+ and left an empty bordered strip down the page on mobile.
157
+ - The collapsed submenu flyout aligns flush with the top of the sidebar column
158
+ instead of the menu region. The menu's box starts inside the panel's padding,
159
+ which left a visible seam above the flyout. With a `#brand` slot filled the
160
+ flyout starts below the brand, so it still never covers the wordmark.
161
+ - `Icon.astro` forwards a `class` prop instead of silently dropping it. It
162
+ hardcoded `class="pui-icon"` and spread the rest of the props after, so an
163
+ incoming class was discarded — Vue applied it, Astro did not. One casualty
164
+ was `Sidebar`'s `pui-sidebar__group-caret` hook, which meant the caret never
165
+ rotated on expand in Astro.
166
+ - A `Sidebar` brand slot no longer spills across the page when collapsed. The
167
+ rule that was meant to hide everything without `data-keep-when-collapsed` was
168
+ an empty placeholder, so a wordmark kept its full width in a rail narrower
169
+ than most product names.
170
+ - The collapsed submenu flyout no longer renders behind page content. It is
171
+ `position: fixed`, which escapes overflow but not a stacking context, and
172
+ consumers routinely make the sidebar `position: sticky` for scroll — which
173
+ creates one. The root now raises its own z-index while a flyout is open.
174
+ - A closed flyout is positioned as soon as it enters panel mode. A fixed
175
+ element with no coordinates resolves to its static position inside the rail,
176
+ so anything that made it briefly visible flashed the panel over the icons.
177
+
178
+ ### Added
179
+
180
+ - `Sidebar` takes a `bottomItems` prop — the bottom nav. Same
181
+ `SidebarItem[]` shape as `topItems`, rendered at the foot of the column
182
+ below a divider, for settings / account / help. See the Breaking entry for
183
+ the accompanying `items` → `topItems` rename.
184
+ - `Sidebar` takes a `collapsedSubmenu` prop (`'hidden' | 'panel'`, default
185
+ `'hidden'`). With `'panel'`, a collapsed rail's groups are no longer a dead
186
+ end: hover, focus, or click opens a group's children in a full-height flyout
187
+ beside the rail. Escape closes it and returns focus to the group toggle;
188
+ clicking the toggle pins the flyout so it survives pointer-out; only one
189
+ flyout is open at a time. The default keeps the previous behavior, so
190
+ existing consumers are unaffected.
191
+ - `mountSidebar` gains `openSubmenu(key)`, `closeSubmenu()`, and
192
+ `openSubmenuKey()`, plus a `collapsedSubmenu` config field. They are no-ops
193
+ unless the policy is active — which needs the sidebar collapsed and the
194
+ viewport above `drawerBreakpoint`, since below it there is no rail.
195
+ - A stacking ladder: `--layer-below`, `--layer-0` … `--layer-5`, `--layer-top`.
196
+ Every positioned layer in the library names a rung instead of inventing a
197
+ number, so the whole order is legible in one place and two components cannot
198
+ accidentally tie. `Toast`, `Table`'s loading pane, `NumberField`'s focused
199
+ step button and every `Sidebar` layer moved onto it; the numbers they
200
+ resolve to are unchanged.
201
+ - `--shadow-lg`, for a surface that covers page content rather than sitting on
202
+ it. `--shadow-md` is tuned for a card in the flow and reads flat under
203
+ something as large as a sidebar flyout.
204
+
205
+ ## 0.3.0 - 2026-08-26
206
+
207
+ ### Added
208
+
209
+ - `--bg-clr-selected` — the neutral "selected" background rung, one step past
210
+ the hover fill so a chosen item stays distinct from a merely hovered one. It
211
+ rides `--clr-surface-1`, the only neutral that keeps a similar distance from
212
+ the surface in both themes: darker than the surface in light, lighter in dark.
213
+ Pair it with `--text-clr-base`. **Never with `--text-clr-inverse`** — that is
214
+ near-white in both themes and belongs on a fill or an inverted bar, so on a
215
+ light neutral it produces near-white on near-white in the light theme.
216
+ Previously the neutral ramp jumped straight from `--bg-clr-surface-2` (91%
217
+ lightness) to `--bg-clr-dark` (27%), so a selected state could only be
218
+ invisible or a heavy slab.
219
+ - `PaginationFooter` (Vue + Astro) — the bar that seats a result summary
220
+ opposite a pagination control: top border, summary at the leading edge,
221
+ control at the trailing edge. Pure markup with slots; the summary text comes
222
+ from a `#summary` slot so pluralization and locale stay with the consumer,
223
+ and the control comes from the default slot. Below a `30rem` container both
224
+ regions stack and centre, with the control leading and the summary reading as
225
+ a caption beneath it. `Pagination` itself gained no props.
226
+
227
+ ```vue
228
+ <PaginationFooter>
229
+ <template #summary>Showing 21–30 of 96 domains</template>
230
+ <Pagination v-model:current-page="page" :total-pages="10" />
231
+ </PaginationFooter>
232
+ ```
233
+
234
+ ### Changed
235
+
236
+ - `Pagination` is restyled to the admin-surface "table footer bar" direction.
237
+ The `<ul>` is now a single bordered strip whose cells share dividers, instead
238
+ of a row of individually bordered boxes separated by `--space-3xs`. Cells use
239
+ `--fw-control` (was `--fw-semibold`), tabular numerals, and
240
+ `--opacity-disabled` (was a hard-coded `0.4`). The public API, markup
241
+ contract, and ARIA are unchanged.
242
+ - `Pagination` is now `--control-height-sm` (30px) tall overall, so it lines up
243
+ with a `sm` `Button` placed beside it. It was effectively 32px: the height
244
+ was applied to the cells, and the strip's own 1px border sat outside them.
245
+ The height moved to the `<ul>` and the cells stretch to fill it, landing at
246
+ 28px — still clear of the 24px WCAG 2.5.8 target.
247
+ - `Pagination`'s current page is now `--bg-clr-selected` + `--text-clr-base`,
248
+ replacing `--bg-clr-dark` + `--text-clr-inverse`. The old pairing read as a
249
+ heavy slab in the light theme and sank into the bar in the dark one, because
250
+ `--bg-clr-dark` is a scrim colour rather than a selection colour.
251
+ - `Pagination`'s focus ring now uses a negative `outline-offset` plus a
252
+ `z-index` lift. The cells are adjacent now, so the previous positive offset
253
+ drew the ring underneath the neighbouring cell. Same token, same width, still
254
+ `:focus-visible`-only.
255
+ - `Pagination` reduces to just the prev / next arrows and the current page below
256
+ an `18rem` container. Previously the full page run stayed put and overflowed.
257
+ This is pure CSS — a container query on the component's own `<nav>` wrapper —
258
+ so it responds to the space the control is given rather than to the viewport,
259
+ and the page list itself is unchanged.
260
+ - `Pagination`'s `<nav>` wrapper now carries a `pui-pagination-nav` class, so it
261
+ can act as that query container. It is a new public child class and carries no
262
+ visual styling of its own.
263
+
5
264
  ## 0.2.0 - 2026-08-24
6
265
 
7
266
  ### Breaking