@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 +59 -0
- package/components/navigation/pagination/Pagination.vue +1 -1
- package/components/navigation/pagination/PaginationFooter.astro +24 -0
- package/components/navigation/pagination/PaginationFooter.vue +23 -0
- package/components/navigation/pagination/pagination.css +128 -29
- package/package.json +3 -1
- package/styles/0-settings/colors.css +8 -1
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
|
|
@@ -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:
|
|
5
|
-
|
|
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:
|
|
14
|
-
background:
|
|
52
|
+
border: 0;
|
|
53
|
+
background: transparent;
|
|
15
54
|
font: inherit;
|
|
16
55
|
font-size: var(--step--1);
|
|
17
|
-
font-weight: var(--fw-
|
|
18
|
-
|
|
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-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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-
|
|
127
|
+
.pui-pagination-footer__summary {
|
|
128
|
+
font-size: var(--step--1);
|
|
50
129
|
color: var(--text-clr-muted);
|
|
51
|
-
|
|
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.
|
|
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);
|