@pienter/ui 0.2.0 → 0.3.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 CHANGED
@@ -2,6 +2,65 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.0 - 2026-08-26
6
+
7
+ ### Added
8
+
9
+ - `--bg-clr-selected` — the neutral "selected" background rung, one step past
10
+ the hover fill so a chosen item stays distinct from a merely hovered one. It
11
+ rides `--clr-surface-1`, the only neutral that keeps a similar distance from
12
+ the surface in both themes: darker than the surface in light, lighter in dark.
13
+ Pair it with `--text-clr-base`. **Never with `--text-clr-inverse`** — that is
14
+ near-white in both themes and belongs on a fill or an inverted bar, so on a
15
+ light neutral it produces near-white on near-white in the light theme.
16
+ Previously the neutral ramp jumped straight from `--bg-clr-surface-2` (91%
17
+ lightness) to `--bg-clr-dark` (27%), so a selected state could only be
18
+ invisible or a heavy slab.
19
+ - `PaginationFooter` (Vue + Astro) — the bar that seats a result summary
20
+ opposite a pagination control: top border, summary at the leading edge,
21
+ control at the trailing edge. Pure markup with slots; the summary text comes
22
+ from a `#summary` slot so pluralization and locale stay with the consumer,
23
+ and the control comes from the default slot. Below a `30rem` container both
24
+ regions stack and centre, with the control leading and the summary reading as
25
+ a caption beneath it. `Pagination` itself gained no props.
26
+
27
+ ```vue
28
+ <PaginationFooter>
29
+ <template #summary>Showing 21–30 of 96 domains</template>
30
+ <Pagination v-model:current-page="page" :total-pages="10" />
31
+ </PaginationFooter>
32
+ ```
33
+
34
+ ### Changed
35
+
36
+ - `Pagination` is restyled to the admin-surface "table footer bar" direction.
37
+ The `<ul>` is now a single bordered strip whose cells share dividers, instead
38
+ of a row of individually bordered boxes separated by `--space-3xs`. Cells use
39
+ `--fw-control` (was `--fw-semibold`), tabular numerals, and
40
+ `--opacity-disabled` (was a hard-coded `0.4`). The public API, markup
41
+ contract, and ARIA are unchanged.
42
+ - `Pagination` is now `--control-height-sm` (30px) tall overall, so it lines up
43
+ with a `sm` `Button` placed beside it. It was effectively 32px: the height
44
+ was applied to the cells, and the strip's own 1px border sat outside them.
45
+ The height moved to the `<ul>` and the cells stretch to fill it, landing at
46
+ 28px — still clear of the 24px WCAG 2.5.8 target.
47
+ - `Pagination`'s current page is now `--bg-clr-selected` + `--text-clr-base`,
48
+ replacing `--bg-clr-dark` + `--text-clr-inverse`. The old pairing read as a
49
+ heavy slab in the light theme and sank into the bar in the dark one, because
50
+ `--bg-clr-dark` is a scrim colour rather than a selection colour.
51
+ - `Pagination`'s focus ring now uses a negative `outline-offset` plus a
52
+ `z-index` lift. The cells are adjacent now, so the previous positive offset
53
+ drew the ring underneath the neighbouring cell. Same token, same width, still
54
+ `:focus-visible`-only.
55
+ - `Pagination` reduces to just the prev / next arrows and the current page below
56
+ an `18rem` container. Previously the full page run stayed put and overflowed.
57
+ This is pure CSS — a container query on the component's own `<nav>` wrapper —
58
+ so it responds to the space the control is given rather than to the viewport,
59
+ and the page list itself is unchanged.
60
+ - `Pagination`'s `<nav>` wrapper now carries a `pui-pagination-nav` class, so it
61
+ can act as that query container. It is a new public child class and carries no
62
+ visual styling of its own.
63
+
5
64
  ## 0.2.0 - 2026-08-24
6
65
 
7
66
  ### Breaking
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <nav aria-label="Pagination">
2
+ <nav class="pui-pagination-nav" aria-label="Pagination">
3
3
  <ul class="pui-pagination">
4
4
  <li>
5
5
  <button
@@ -0,0 +1,24 @@
1
+ ---
2
+ import './pagination.css';
3
+
4
+ // Pure markup with slots. Pagination itself has no Astro tier (it needs
5
+ // interactivity — see AUDIT.md), so an Astro consumer mounts `Pagination.vue`
6
+ // as an island inside the default slot.
7
+ type Props = Record<string, unknown>;
8
+
9
+ const { ...rest } = Astro.props as Props;
10
+ const hasSummary = Astro.slots.has('summary');
11
+ ---
12
+
13
+ <div class="pui-pagination-footer" {...rest}>
14
+ {
15
+ hasSummary && (
16
+ <div class="pui-pagination-footer__summary">
17
+ <slot name="summary" />
18
+ </div>
19
+ )
20
+ }
21
+ <div class="pui-pagination-footer__control">
22
+ <slot />
23
+ </div>
24
+ </div>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div class="pui-pagination-footer">
3
+ <div v-if="$slots.summary" class="pui-pagination-footer__summary">
4
+ <slot name="summary" />
5
+ </div>
6
+ <div class="pui-pagination-footer__control">
7
+ <slot />
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ // Pure markup with slots — no props, no state, no ARIA of its own. The
14
+ // landmark lives on the `<nav>` that `Pagination` renders inside the default
15
+ // slot; a second landmark here would be noise.
16
+ //
17
+ // A plain `<div>`, not `<footer>`: dropped at body level a `<footer>` becomes
18
+ // the `contentinfo` landmark, which this is not.
19
+ </script>
20
+
21
+ <style>
22
+ @import './pagination.css';
23
+ </style>
@@ -1,53 +1,152 @@
1
+ /* `.pui-pagination` is the bare control, `.pui-pagination-footer` the bar that
2
+ seats it opposite a result summary — one visual family, one file. Rationale
3
+ for the less obvious rules lives in AUDIT.md. */
1
4
  @layer components {
5
+ /* ===== bare control ===== */
6
+
2
7
  .pui-pagination {
8
+ /* The STRIP owns the control height, not the cells: `box-sizing` is
9
+ border-box, so this matches a `sm` Button's 30px outer box instead of
10
+ standing 2px taller once the strip's own border is added. Cells then
11
+ stretch to fill whatever is left inside it.
12
+ No `overflow: hidden` to clip the cell corners — it clips the focus
13
+ ring too, so the end cells carry the radius instead. */
3
14
  display: inline-flex;
4
- align-items: center;
5
- gap: var(--space-3xs);
6
- list-style: none;
7
- padding: 0;
15
+ align-items: stretch;
16
+ height: var(--control-height-sm);
8
17
  margin: 0;
18
+ padding: 0;
19
+ list-style: none;
20
+ border: var(--stroke-sm) solid var(--border-clr-base);
21
+ border-radius: var(--radius-sm);
22
+ background: var(--bg-clr-surface);
23
+
24
+ & > li {
25
+ display: flex;
26
+ }
27
+
28
+ & > li + li :is(.pui-pagination__item, .pui-pagination__ellipsis) {
29
+ border-inline-start: var(--stroke-sm) solid var(--border-clr-base);
30
+ }
31
+
32
+ /* First and last are always the prev / next buttons. */
33
+ & > li:first-child .pui-pagination__item {
34
+ border-start-start-radius: var(--radius-sm);
35
+ border-end-start-radius: var(--radius-sm);
36
+ }
37
+
38
+ & > li:last-child .pui-pagination__item {
39
+ border-start-end-radius: var(--radius-sm);
40
+ border-end-end-radius: var(--radius-sm);
41
+ }
9
42
  }
10
43
 
11
44
  .pui-pagination__item {
45
+ /* box → typography → colour → transition */
46
+ display: inline-grid;
47
+ place-items: center;
48
+ min-width: var(--control-height-sm);
49
+ height: 100%;
50
+ padding-inline: var(--space-2xs);
12
51
  appearance: none;
13
- border: var(--stroke-sm) solid var(--border-clr-base);
14
- background: var(--bg-clr-surface);
52
+ border: 0;
53
+ background: transparent;
15
54
  font: inherit;
16
55
  font-size: var(--step--1);
17
- font-weight: var(--fw-semibold);
18
- min-width: 2.2rem;
19
- height: 2.2rem;
20
- display: inline-grid;
21
- place-items: center;
22
- cursor: pointer;
23
- border-radius: var(--radius-sm);
24
- text-decoration: none;
56
+ font-weight: var(--fw-control);
57
+ font-variant-numeric: tabular-nums;
25
58
  color: var(--text-clr-base);
59
+ text-decoration: none;
60
+ cursor: pointer;
61
+ transition: background-color var(--duration-base) var(--ease-base);
62
+
63
+ &:hover:not(:disabled, [aria-disabled='true']) {
64
+ background: var(--bg-clr-surface-2);
65
+ }
66
+
67
+ &:focus-visible {
68
+ /* Inset and lifted, or the neighbouring cell overdraws the ring. */
69
+ position: relative;
70
+ z-index: 1;
71
+ outline: var(--outline-width) solid var(--outline-clr-base);
72
+ outline-offset: calc(var(--outline-offset) * -1);
73
+ }
74
+
75
+ /* Never `--text-clr-inverse` here: it is near-white in both themes and
76
+ only pairs with a fill or an inverted bar. See AUDIT.md. */
77
+ &[aria-current='page'] {
78
+ background: var(--bg-clr-selected);
79
+ color: var(--text-clr-base);
80
+ }
81
+
82
+ &:disabled,
83
+ &[aria-disabled='true'] {
84
+ opacity: var(--opacity-disabled);
85
+ cursor: not-allowed;
86
+ }
26
87
  }
27
88
 
28
- .pui-pagination__item:hover {
29
- background: var(--bg-clr-surface-2);
89
+ .pui-pagination__ellipsis {
90
+ display: inline-grid;
91
+ place-items: center;
92
+ min-width: var(--control-height-sm);
93
+ height: 100%;
94
+ font-size: var(--step--1);
95
+ color: var(--text-clr-muted);
30
96
  }
31
97
 
32
- .pui-pagination__item:focus-visible {
33
- outline: 3px solid var(--outline-clr-base);
34
- outline-offset: 2px;
98
+ /* ===== narrow reduction ===== */
99
+
100
+ /* The `<nav>` is the query container, not the `<ul>` — the `<ul>` is
101
+ inline-flex, so `inline-size` containment would collapse it. */
102
+ .pui-pagination-nav {
103
+ container-type: inline-size;
35
104
  }
36
105
 
37
- .pui-pagination__item[aria-current='page'] {
38
- background: var(--bg-clr-dark);
39
- color: var(--text-clr-inverse);
40
- border-color: var(--border-clr-strong);
106
+ /* Keep the arrows and the current page, drop the rest of the run. */
107
+ @container (max-width: 18rem) {
108
+ .pui-pagination
109
+ > li:not(:first-child, :last-child, :has(> [aria-current='page'])) {
110
+ display: none;
111
+ }
41
112
  }
42
113
 
43
- .pui-pagination__item:disabled,
44
- .pui-pagination__item[aria-disabled='true'] {
45
- opacity: 0.4;
46
- cursor: not-allowed;
114
+ /* ===== footer bar ===== */
115
+
116
+ .pui-pagination-footer {
117
+ container-type: inline-size;
118
+ display: flex;
119
+ flex-wrap: wrap;
120
+ align-items: center;
121
+ gap: var(--space-xs) var(--space-s);
122
+ padding: var(--space-xs) var(--space-s);
123
+ border-block-start: var(--stroke-sm) solid var(--border-clr-base);
124
+ background: var(--bg-clr-surface);
47
125
  }
48
126
 
49
- .pui-pagination__ellipsis {
127
+ .pui-pagination-footer__summary {
128
+ font-size: var(--step--1);
50
129
  color: var(--text-clr-muted);
51
- padding-inline: var(--space-2xs);
130
+ font-variant-numeric: tabular-nums;
131
+ }
132
+
133
+ .pui-pagination-footer__control {
134
+ /* Block, so the `<nav>` inside inherits a width to query; `flex: 1`
135
+ holds it at the trailing edge with or without a summary. */
136
+ flex: 1;
137
+ text-align: end;
138
+ }
139
+
140
+ @container (max-width: 30rem) {
141
+ .pui-pagination-footer__summary,
142
+ .pui-pagination-footer__control {
143
+ flex: 1 0 100%;
144
+ text-align: center;
145
+ }
146
+
147
+ /* Stacked, the summary reads as a caption under the control. */
148
+ .pui-pagination-footer__summary {
149
+ order: 1;
150
+ }
52
151
  }
53
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pienter/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared Pienter UI components, styles, icons, and browser utilities.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -98,6 +98,8 @@
98
98
  "./components/Navbar.astro": "./components/navigation/navbar/Navbar.astro",
99
99
  "./components/Navbar.vue": "./components/navigation/navbar/Navbar.vue",
100
100
  "./components/Pagination.vue": "./components/navigation/pagination/Pagination.vue",
101
+ "./components/PaginationFooter.astro": "./components/navigation/pagination/PaginationFooter.astro",
102
+ "./components/PaginationFooter.vue": "./components/navigation/pagination/PaginationFooter.vue",
101
103
  "./components/Sidebar.astro": "./components/navigation/sidebar/Sidebar.astro",
102
104
  "./components/Sidebar.vue": "./components/navigation/sidebar/Sidebar.vue",
103
105
  "./components/Sidebar.types": "./components/navigation/sidebar/types.ts",
@@ -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);