@apliteni/apliteni-ui 0.8.0 → 0.9.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/README.md +8 -3
- package/package.json +1 -1
- package/react/README.md +11 -0
- package/src/components/index.js +8 -0
- package/src/components/success.js +10 -0
- package/src/components/topbar.js +28 -7
- package/src/index.js +2 -0
- package/src/inline.js +2 -0
- package/src/styles/badge.css +5 -2
- package/src/styles/base.css +11 -3
- package/src/styles/callout.css +37 -19
- package/src/styles/dropdown.css +1 -1
- package/src/styles/empty.css +8 -3
- package/src/styles/nav.css +14 -8
- package/src/styles/table.css +13 -0
- package/src/styles/topbar.css +1 -1
- package/src/tokens/tokens.css +76 -10
package/README.md
CHANGED
|
@@ -105,7 +105,12 @@ import { DataTable, Modal } from '@apliteni/apliteni-ui/react';
|
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
The source lives in [`react/`](./react) — a private workspace with its own build
|
|
108
|
-
(tsup) and Storybook on port 6007.
|
|
108
|
+
(tsup) and Storybook on port 6007. When 6007 is taken Storybook moves to the next
|
|
109
|
+
free port, so the root Storybook does not trust the number: it probes the range
|
|
110
|
+
6007 can drift into and composes the first port that proves it is this workspace's
|
|
111
|
+
Storybook. A stranger on the port is never composed — the "React components"
|
|
112
|
+
section is absent instead, and the terminal says why. Details in
|
|
113
|
+
[`react/README.md`](./react/README.md).
|
|
109
114
|
|
|
110
115
|
## Theming
|
|
111
116
|
|
|
@@ -147,7 +152,7 @@ react/ # React components — private workspace, built to reac
|
|
|
147
152
|
|
|
148
153
|
```bash
|
|
149
154
|
npm install
|
|
150
|
-
npm run storybook # http://localhost:6006
|
|
155
|
+
npm run storybook # http://localhost:6006, or the next free port
|
|
151
156
|
npm run build-storybook # -> storybook-static/
|
|
152
157
|
node site/build.mjs # -> site/public/ (landing + kit.css + /storybook)
|
|
153
158
|
```
|
|
@@ -156,7 +161,7 @@ The React components build and test through the workspace (`npm install` at the
|
|
|
156
161
|
root covers them — there is no second install):
|
|
157
162
|
|
|
158
163
|
```bash
|
|
159
|
-
npm run storybook -w react # http://localhost:6007
|
|
164
|
+
npm run storybook -w react # http://localhost:6007, or the next free port
|
|
160
165
|
npm test -w react # vitest
|
|
161
166
|
npm run build # tsup -> react/dist/ (also runs on prepare)
|
|
162
167
|
```
|
package/package.json
CHANGED
package/react/README.md
CHANGED
|
@@ -41,6 +41,17 @@ npm run storybook -w react # http://localhost:6007
|
|
|
41
41
|
npm run build # tsup -> react/dist/
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
6007 is a request, not a promise: if something else already holds it, Storybook
|
|
45
|
+
walks upward to the next free port. The root Storybook composes these components
|
|
46
|
+
by following that drift — it probes 6007 through 6016 (the window Storybook's own
|
|
47
|
+
port-finder searches) and takes the first one whose `index.json` lists every story
|
|
48
|
+
file this workspace has on disk. A stranger on the port fails that check and is
|
|
49
|
+
never shown under "React components"; the section is simply absent, and the root
|
|
50
|
+
Storybook's terminal says which ports it tried and what it found.
|
|
51
|
+
|
|
52
|
+
The probe runs once, while the root Storybook boots. Start this one afterwards and
|
|
53
|
+
you have to restart the root Storybook before it appears.
|
|
54
|
+
|
|
44
55
|
The bare `@apliteni/apliteni-ui` specifier in this source resolves to the kit itself
|
|
45
56
|
once installed. In the repo there is no copy to resolve to, so `kit-alias.ts` points
|
|
46
57
|
vitest and Storybook straight at `../src/` — which is why the class-name parity tests
|
package/src/components/index.js
CHANGED
|
@@ -235,6 +235,14 @@ export function toast({
|
|
|
235
235
|
+ `<div class="ui-toast__body">${title ? `<div class="ui-toast__title">${esc(title)}</div>` : ''}${bodyHtml}</div>`
|
|
236
236
|
+ `${actBtn}${closeBtn}${timerBar}</div>`;
|
|
237
237
|
}
|
|
238
|
+
// The inline confirmation. A check, a title and one line of sub, dropped into
|
|
239
|
+
// the page the user is already on — under a form that just submitted, or inside
|
|
240
|
+
// a card. Two strings, no options; styles are .ui-success in styles/callout.css.
|
|
241
|
+
//
|
|
242
|
+
// The kit also publishes success() from components/success.js, which is the same
|
|
243
|
+
// idea at page size: layouts, a backdrop, follow-up buttons and an auto-redirect
|
|
244
|
+
// countdown. Pick by how much of the screen the confirmation owns. The two share
|
|
245
|
+
// no CSS, so changing one never moves the other.
|
|
238
246
|
export function successPanel({ title = 'Done', sub = '' } = {}) {
|
|
239
247
|
return `<div class="ui-success"><div class="ui-success__check">${icon('check')}</div><div class="ui-success__title">${esc(title)}</div>${sub ? `<div class="ui-success__sub">${esc(sub)}</div>` : ''}</div>`;
|
|
240
248
|
}
|
|
@@ -61,6 +61,16 @@ function countdownEl({ seconds = 5, label = 'Redirecting' } = {}) {
|
|
|
61
61
|
</div>`;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// The page-sized confirmation — what a flow lands on once it is over. It fills
|
|
65
|
+
// the space it is given, and carries the things a screen needs that a block does
|
|
66
|
+
// not: somewhere to go next, and optionally a countdown that takes the user
|
|
67
|
+
// there.
|
|
68
|
+
//
|
|
69
|
+
// For a confirmation that stays inside the page the user is already on, the kit
|
|
70
|
+
// publishes successPanel() from components/index.js — a check, a title and one
|
|
71
|
+
// line of sub, and nothing to configure. Pick by how much of the screen the
|
|
72
|
+
// confirmation owns. The two share no CSS (.ui-sx here, .ui-success there), so
|
|
73
|
+
// changing one never moves the other.
|
|
64
74
|
export function success({
|
|
65
75
|
layout = 'hero', // 'hero' | 'split' | 'compact'
|
|
66
76
|
backdrop = 'aurora', // 'aurora' | 'glow' | 'flat'
|
package/src/components/topbar.js
CHANGED
|
@@ -7,10 +7,28 @@ import { wireDropdown } from './dropdown.js';
|
|
|
7
7
|
|
|
8
8
|
const THEME_KEY = 'apliteni-strategy-theme';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// A stateful control in this kit reports the state it is IN, never the state a
|
|
11
|
+
// click would produce — the same reading as segmented()'s aria-pressed, the
|
|
12
|
+
// Deck/Text switch's aria-current, and the accent chips. So: moon while dark,
|
|
13
|
+
// sun while light. Storybook's own toolbar toggle (.storybook/theme-toggle.jsx)
|
|
14
|
+
// reads the same way, and topbar stories render directly beneath it.
|
|
15
|
+
export const themeIcon = (t) => (t === 'light' ? sun : moon);
|
|
16
|
+
|
|
17
|
+
// The name carries both the state and what the click does, which is why there
|
|
18
|
+
// is no aria-pressed and no role="switch": dark and light are peers, not on and
|
|
19
|
+
// off, and an ARIA state on top of this name would announce the theme twice.
|
|
20
|
+
// The button is icon-only, so `title` is the only sighted reading of the same
|
|
21
|
+
// fact and carries the identical string — nothing for WCAG 2.5.3 to disagree
|
|
22
|
+
// with. Both are rewritten by applyTheme on every flip; a name that is right
|
|
23
|
+
// once and never again tells a screen-reader user nothing.
|
|
24
|
+
export const themeName = (t) =>
|
|
25
|
+
(t === 'light' ? 'Theme: Light. Switch to dark.' : 'Theme: Dark. Switch to light.');
|
|
26
|
+
|
|
27
|
+
export function themeToggle(theme = 'dark') {
|
|
28
|
+
// Single icon-only switch. Icon and name are pre-filled for `theme` so the
|
|
29
|
+
// first paint is already truthful; applyTheme() keeps them so afterwards.
|
|
30
|
+
const name = esc(themeName(theme));
|
|
31
|
+
return `<button class="toggle" data-theme-toggle aria-label="${name}" title="${name}"><span class="ic" data-theme-icon>${themeIcon(theme)}</span></button>`;
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
export function deckTextSwitch(active = 'deck') {
|
|
@@ -69,11 +87,14 @@ export function topbar({
|
|
|
69
87
|
// ---- Behaviours (call once after markup mounts) --------------------------
|
|
70
88
|
export function applyTheme(t, root = document.documentElement) {
|
|
71
89
|
root.setAttribute('data-theme', t);
|
|
90
|
+
const name = themeName(t);
|
|
72
91
|
root.querySelectorAll('[data-theme-toggle]').forEach((btn) => {
|
|
73
92
|
const ic = btn.querySelector('[data-theme-icon]');
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
if (ic) ic.innerHTML = themeIcon(t);
|
|
94
|
+
// The announced name is the point of the control, so it moves with the
|
|
95
|
+
// theme like the glyph does.
|
|
96
|
+
btn.setAttribute('aria-label', name);
|
|
97
|
+
btn.setAttribute('title', name);
|
|
77
98
|
});
|
|
78
99
|
try { localStorage.setItem(THEME_KEY, t); } catch (e) { /* no-op */ }
|
|
79
100
|
}
|
package/src/index.js
CHANGED
|
@@ -7,8 +7,10 @@ export * from './components/nav.js';
|
|
|
7
7
|
export * from './components/drawer.js';
|
|
8
8
|
export * from './components/topbar.js';
|
|
9
9
|
export * from './components/shell.js';
|
|
10
|
+
export * from './components/footer.js';
|
|
10
11
|
export * from './components/feedback.js';
|
|
11
12
|
export * from './components/toasts.js';
|
|
13
|
+
export * from './components/success.js';
|
|
12
14
|
export * from './assets/icons.js';
|
|
13
15
|
export * from './assets/brand.js';
|
|
14
16
|
export * from './motion.js';
|
package/src/inline.js
CHANGED
|
@@ -39,6 +39,7 @@ export const styles = {
|
|
|
39
39
|
nav: read('styles/nav.css'),
|
|
40
40
|
drawer: read('styles/drawer.css'),
|
|
41
41
|
table: read('styles/table.css'),
|
|
42
|
+
empty: read('styles/empty.css'),
|
|
42
43
|
callout: read('styles/callout.css'),
|
|
43
44
|
code: read('styles/code.css'),
|
|
44
45
|
topbar: topbarCss,
|
|
@@ -63,6 +64,7 @@ export const cssText = [
|
|
|
63
64
|
styles.nav,
|
|
64
65
|
styles.drawer,
|
|
65
66
|
styles.table,
|
|
67
|
+
styles.empty,
|
|
66
68
|
styles.callout,
|
|
67
69
|
styles.code,
|
|
68
70
|
styles.topbar,
|
package/src/styles/badge.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
wash out and the ink has to deepen to clear AA, and that is a token decision. */
|
|
25
25
|
.ui-badge--live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
|
|
26
26
|
.ui-badge--soon { color: var(--purple-mid); background: var(--glow-purple); }
|
|
27
|
-
.ui-badge--info { color: var(--
|
|
27
|
+
.ui-badge--info { color: var(--chip-info-ink); background: var(--chip-info-fill); }
|
|
28
28
|
.ui-badge--warn { color: var(--chip-warn-ink); background: var(--chip-warn-fill); }
|
|
29
29
|
.ui-badge--danger { color: var(--chip-danger-ink); background: var(--chip-danger-fill); }
|
|
30
30
|
.ui-badge--archive { color: var(--muted); background: var(--surface); }
|
|
@@ -54,7 +54,10 @@
|
|
|
54
54
|
padding: 4px 11px;
|
|
55
55
|
font-weight: var(--weight-semibold);
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
/* The live pill is the same status paint as the live badge, so it takes the
|
|
58
|
+
same pair. On its own it was --green on --glow-green, which is 3.97:1 over
|
|
59
|
+
white — under AA, while the badge beside it cleared at 4.64:1. See issue #131. */
|
|
60
|
+
.ui-pill--live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
|
|
58
61
|
.ui-pill--soon { color: var(--purple-mid); background: var(--glow-purple); }
|
|
59
62
|
|
|
60
63
|
/* Status dot */
|
package/src/styles/base.css
CHANGED
|
@@ -101,9 +101,17 @@ a {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/* Sensible default size for inline icons that a parent rule doesn't size.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
A floor, not a ceiling: :where() holds the whole filter at zero specificity,
|
|
105
|
+
so this weighs (0,0,1) — one element, nothing more — and any component rule
|
|
106
|
+
that sizes an icon outranks it. Written bare, :not([width]):not([height])
|
|
107
|
+
would count both attribute selectors and weigh (0,2,1), which beats every
|
|
108
|
+
`.ui-btn svg`-shaped rule in the kit no matter what order the files load in.
|
|
109
|
+
It did, for every one of them bar three, and for `.ui-fbck` besides — that
|
|
110
|
+
one is a class on the svg itself, so it is easy to miss when you go looking.
|
|
111
|
+
The count is deliberately not written here: it moved once already, and the
|
|
112
|
+
rules outside this package (site/, the Iconography story) are not in it.
|
|
113
|
+
src/styles/icon-size.test.js keeps the ones in src/styles honest. */
|
|
114
|
+
svg:where(:not([width]):not([height])) {
|
|
107
115
|
width: 1.1em;
|
|
108
116
|
height: 1.1em;
|
|
109
117
|
flex: none;
|
package/src/styles/callout.css
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
/* ----------------------------------------------------------------------------
|
|
30
30
|
* Toast — a floating, dismissible notification.
|
|
31
31
|
* A status (colour) × style (surface) matrix, entirely token-driven:
|
|
32
|
-
* status success · danger · warn · info · neutral → sets --toast-accent/-glow/-on
|
|
32
|
+
* status success · danger · warn · info · neutral → sets --toast-accent/-glow/-on/-solid
|
|
33
33
|
* style soft (tinted) · solid (filled) · outline (bordered)
|
|
34
34
|
* -------------------------------------------------------------------------- */
|
|
35
35
|
.ui-toast {
|
|
@@ -47,13 +47,16 @@
|
|
|
47
47
|
animation: ui-toast-in 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/* status → the paint tokens every style below consumes
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.ui-toast--
|
|
56
|
-
.ui-toast--
|
|
50
|
+
/* status → the paint tokens every style below consumes.
|
|
51
|
+
--toast-accent is the status as a line or a small mark, --toast-glow its wash,
|
|
52
|
+
--toast-on the glyph ink on the accent circle. --toast-solid is separate on
|
|
53
|
+
purpose: a full fill is a different job from a 22px circle, and it is chosen
|
|
54
|
+
as a PAIR with --signal-solid-ink rather than against a global. See #149. */
|
|
55
|
+
.ui-toast--success { --toast-accent: var(--green); --toast-glow: var(--glow-green); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-success); }
|
|
56
|
+
.ui-toast--danger { --toast-accent: var(--pink); --toast-glow: var(--glow-pink); --toast-on: var(--danger-contrast); --toast-solid: var(--signal-solid-danger); }
|
|
57
|
+
.ui-toast--warn { --toast-accent: var(--amber); --toast-glow: color-mix(in srgb, var(--amber) 14%, transparent); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-warn); }
|
|
58
|
+
.ui-toast--info { --toast-accent: var(--cyan); --toast-glow: var(--glow-cyan); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-info); }
|
|
59
|
+
.ui-toast--neutral { --toast-accent: var(--muted); --toast-glow: var(--surface-2); --toast-on: var(--strong); --toast-solid: var(--signal-solid-neutral); }
|
|
57
60
|
|
|
58
61
|
/* left status marker (soft + outline; solid is already a full fill) */
|
|
59
62
|
.ui-toast--soft::before,
|
|
@@ -83,17 +86,32 @@
|
|
|
83
86
|
/* style: soft — tinted surface */
|
|
84
87
|
.ui-toast--soft { background: var(--toast-glow); box-shadow: var(--shadow-sm); }
|
|
85
88
|
|
|
86
|
-
/* style: solid —
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.ui-toast--solid
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
/* style: solid — the status at fill strength, inked with the pole opposite it.
|
|
90
|
+
The fill and the ink are one pair, taken together from --signal-solid-*: dark
|
|
91
|
+
fills are the bright signals and take near-black, light fills are the deepened
|
|
92
|
+
ones and take white. What this replaced picked the fill from the status and
|
|
93
|
+
the ink from one of two globals, on independent axes, so nothing made them
|
|
94
|
+
clear each other — and four of the ten status x theme combinations did not.
|
|
95
|
+
Gated for all ten in stories/signal-contrast.test.js. See #149. */
|
|
96
|
+
.ui-toast--solid {
|
|
97
|
+
--toast-ink: var(--signal-solid-ink);
|
|
98
|
+
background: var(--toast-solid);
|
|
99
|
+
color: var(--toast-ink);
|
|
100
|
+
box-shadow: var(--shadow-lg);
|
|
101
|
+
}
|
|
102
|
+
.ui-toast--solid .ui-toast__title { color: var(--toast-ink); }
|
|
103
|
+
/* The body copy is the quieter of the two, but it stays quiet by dilution the
|
|
104
|
+
pair can carry: 90% of the ink measures 4.71 on the worst fill in the matrix
|
|
105
|
+
(--chip-success-ink in light). The opacity: 0.82 this replaced measured 4.19
|
|
106
|
+
there, and 2.25 on the dark danger fill — the pair was correct and the
|
|
107
|
+
dilution then spent it. */
|
|
108
|
+
.ui-toast--solid .ui-toast__text { color: color-mix(in srgb, var(--toast-ink) 90%, transparent); }
|
|
109
|
+
.ui-toast--solid .ui-toast__icon { background: color-mix(in srgb, var(--toast-ink) 22%, transparent); color: var(--toast-ink); }
|
|
110
|
+
.ui-toast--solid .ui-toast__action { color: var(--toast-ink); }
|
|
111
|
+
.ui-toast--solid .ui-toast__action:hover { background: color-mix(in srgb, var(--toast-ink) 18%, transparent); }
|
|
112
|
+
.ui-toast--solid .ui-toast__close { color: color-mix(in srgb, var(--toast-ink) 68%, transparent); }
|
|
113
|
+
.ui-toast--solid .ui-toast__close:hover { color: var(--toast-ink); }
|
|
114
|
+
.ui-toast--solid .ui-toast__timer { background: color-mix(in srgb, var(--toast-ink) 55%, transparent); }
|
|
97
115
|
|
|
98
116
|
/* style: outline — elevated card with a status-tinted border */
|
|
99
117
|
.ui-toast--outline { background: var(--bg-elevated); box-shadow: var(--shadow-md); border: 1px solid color-mix(in srgb, var(--toast-accent) 40%, transparent); }
|
package/src/styles/dropdown.css
CHANGED
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
color: var(--muted);
|
|
103
103
|
background: var(--surface);
|
|
104
104
|
}
|
|
105
|
-
.ui-dropdown__badge.is-live { color: var(--
|
|
105
|
+
.ui-dropdown__badge.is-live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
|
|
106
106
|
.ui-dropdown__badge.is-accent { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
107
107
|
|
|
108
108
|
/* Disabled + danger rows */
|
package/src/styles/empty.css
CHANGED
|
@@ -67,10 +67,15 @@
|
|
|
67
67
|
margin-top: 12px;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/* Legacy line-icon slot — kept so existing .ui-empty__icon markup still works.
|
|
70
|
+
/* Legacy line-icon slot — kept so existing .ui-empty__icon markup still works.
|
|
71
|
+
44px until #148. The icon inside is `width: 100%`, and that rule lost to the
|
|
72
|
+
base.css reset for as long as this slot has existed, so the slot's number was
|
|
73
|
+
never the size anyone saw: it rendered at 15.94px. Once the reset stopped
|
|
74
|
+
winning, 44px was a size nobody had chosen with their eyes open. 32px is the
|
|
75
|
+
size that was chosen, against the alternatives, on a rendered comparison. */
|
|
71
76
|
.ui-empty__icon {
|
|
72
|
-
width:
|
|
73
|
-
height:
|
|
77
|
+
width: 32px;
|
|
78
|
+
height: 32px;
|
|
74
79
|
margin: 0 auto 14px;
|
|
75
80
|
color: var(--muted);
|
|
76
81
|
opacity: 0.6;
|
package/src/styles/nav.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* ========================================================================== */
|
|
7
7
|
|
|
8
8
|
.ui-nav { font-family: var(--font-sans); }
|
|
9
|
-
.ui-nav ul,
|
|
9
|
+
.ui-nav :where(ul, ol) { list-style: none; margin: 0; padding: 0; } /* floor, not ceiling */
|
|
10
10
|
|
|
11
11
|
/* -- Sidebar rail -------------------------------------------------------- */
|
|
12
12
|
.ui-nav--side {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
.ui-nav__item:hover { background: var(--surface-2); color: var(--strong); }
|
|
51
51
|
.ui-nav__item.is-active {
|
|
52
|
-
background: var(--surface);
|
|
52
|
+
background: var(--surface-3);
|
|
53
53
|
color: var(--strong);
|
|
54
54
|
font-weight: 500;
|
|
55
55
|
}
|
|
@@ -75,7 +75,9 @@
|
|
|
75
75
|
.ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: var(--accent); }
|
|
76
76
|
.ui-nav__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
77
77
|
|
|
78
|
-
.
|
|
78
|
+
/* Destructive row (sign out, revoke). Quiet at rest, --pink once the pointer
|
|
79
|
+
reaches it — same two-step as .ui-btn--danger and .ui-dropdown__item.is-danger. */
|
|
80
|
+
.ui-nav__item.is-danger { color: var(--muted); }
|
|
79
81
|
.ui-nav__item.is-danger:hover { background: var(--glow-pink); color: var(--pink); }
|
|
80
82
|
.ui-nav__item.is-disabled { color: var(--muted); opacity: 0.55; cursor: default; pointer-events: none; }
|
|
81
83
|
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
color: var(--dim);
|
|
97
99
|
}
|
|
98
100
|
.ui-nav__badge.is-accent { background: var(--glow-purple); color: var(--accent); }
|
|
99
|
-
.ui-nav__badge.is-live { background: var(--
|
|
101
|
+
.ui-nav__badge.is-live { background: var(--chip-success-fill); color: var(--chip-success-ink); }
|
|
100
102
|
.ui-nav__badge.is-danger { background: var(--glow-pink); color: var(--pink); }
|
|
101
103
|
.ui-nav__item.is-active .ui-nav__badge.is-neutral { background: var(--surface-3); color: var(--strong); }
|
|
102
104
|
|
|
@@ -115,14 +117,18 @@
|
|
|
115
117
|
display: flex;
|
|
116
118
|
flex-direction: column;
|
|
117
119
|
gap: 2px;
|
|
118
|
-
margin:
|
|
119
|
-
padding-left:
|
|
120
|
+
margin: var(--space-1) 0 var(--space-1) var(--space-4);
|
|
121
|
+
padding-left: var(--space-3);
|
|
120
122
|
border-left: 1px solid var(--border);
|
|
121
123
|
}
|
|
122
124
|
.ui-nav__sub[hidden] { display: none; }
|
|
123
125
|
.ui-nav__item--sub { font-size: 14px; padding: 7px 12px; }
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
/* No hairline on a nested current row — but only at rest. box-shadow carries
|
|
127
|
+
both the hairline and the focus ring, so a rule about the resting look must
|
|
128
|
+
say so, or it silently cancels the ring (WCAG 2.4.7). */
|
|
129
|
+
.ui-nav--side .ui-nav__item--sub.is-active:not(:focus-visible) { box-shadow: none; }
|
|
130
|
+
/* Hangs back onto the guide hairline the indent above draws. */
|
|
131
|
+
.ui-nav--side .ui-nav__item--sub.is-active::before { left: calc(-1 * var(--space-3)); height: 15px; }
|
|
126
132
|
|
|
127
133
|
/* Footer slot (divider + trailing links, e.g. sign-out) */
|
|
128
134
|
.ui-nav__foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
|
package/src/styles/table.css
CHANGED
|
@@ -68,6 +68,19 @@
|
|
|
68
68
|
.ui-table--zebra th { border-bottom-width: 2px; }
|
|
69
69
|
.ui-table--zebra td { border-bottom: 0; }
|
|
70
70
|
.ui-table--zebra tbody tr:nth-child(even) td { background: var(--surface-2); }
|
|
71
|
+
/* The stripe is full-bleed on purpose — the hover note above inset the hover fill for
|
|
72
|
+
* #71 and deliberately left the ledger stripe alone. But full-bleed only reads right if
|
|
73
|
+
* the end cells are inset from the tint's own edge, and the base rhythm does not inset
|
|
74
|
+
* them: `.ui-table td` sets `padding-left: 0`. Only `--dense` supplied the inset, so a
|
|
75
|
+
* `--zebra` table composed WITHOUT `--dense` put the first cell's content at exactly
|
|
76
|
+
* x=0 of the stripe. `DataTable` is that composition — it writes `ui-table
|
|
77
|
+
* ui-table--hover ui-table--zebra` and no `--dense` — so its select-all and per-row
|
|
78
|
+
* checkboxes sat flush against the tint with a measured 0px gap.
|
|
79
|
+
* Header and body are inset together, or the columns stop lining up. */
|
|
80
|
+
.ui-table--zebra th:first-child,
|
|
81
|
+
.ui-table--zebra td:first-child { padding-left: 12px; }
|
|
82
|
+
.ui-table--zebra th:last-child,
|
|
83
|
+
.ui-table--zebra td:last-child { padding-right: 12px; }
|
|
71
84
|
.ui-table--zebra.ui-table--hover tbody tr:hover td,
|
|
72
85
|
.ui-table--zebra tbody tr:hover td { background: color-mix(in srgb, var(--accent) 8%, transparent); }
|
|
73
86
|
|
package/src/styles/topbar.css
CHANGED
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
flex: none;
|
|
150
150
|
margin-top: 1px;
|
|
151
151
|
}
|
|
152
|
-
.vbadge.live { color: var(--
|
|
152
|
+
.vbadge.live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
|
|
153
153
|
.vbadge.arch { color: var(--muted); background: var(--surface); }
|
|
154
154
|
@media (max-width: 600px) { .vsw__btn .lbl { display: none; } }
|
|
155
155
|
|
package/src/tokens/tokens.css
CHANGED
|
@@ -109,7 +109,12 @@
|
|
|
109
109
|
--purple-mid: #b479ff;
|
|
110
110
|
--cyan: #20dcf5;
|
|
111
111
|
--green: #98ff8f;
|
|
112
|
-
--pink
|
|
112
|
+
/* Lighter than it looks like it should be, and deliberately so: --pink is read
|
|
113
|
+
mostly ON its own 16% glow, and that wash lifts the ground faster than the
|
|
114
|
+
hue lifts the ink. #e35b8f measured 3.82:1 on --glow-pink over a card —
|
|
115
|
+
under AA — so danger moved AWAY from the canvas, not toward it. Light moves
|
|
116
|
+
the opposite way for the same reason. See issue #131. */
|
|
117
|
+
--pink: #e97ca5;
|
|
113
118
|
--amber: #ffcf6a;
|
|
114
119
|
|
|
115
120
|
--bg: #16151f;
|
|
@@ -136,16 +141,25 @@
|
|
|
136
141
|
--ink: #e9e7f0;
|
|
137
142
|
|
|
138
143
|
/* Ink that reads on a SIGNAL colour once that colour becomes a fill — the
|
|
139
|
-
glyph in a success circle, the
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
glyph in a success circle, the glyph on a toast's status circle.
|
|
145
|
+
In dark every signal is a bright hue, so one near-black ink clears all
|
|
146
|
+
five: 15.87 on --green, 7.35 on --pink, 13.40 on --amber, 11.75 on --cyan
|
|
147
|
+
and 6.29 on --muted. Danger keeps a token of its own only because LIGHT
|
|
148
|
+
still needs it: there the signals are deepened to read as ink on white,
|
|
149
|
+
which parks them mid-luminance, and --pink is the one that comes out ahead
|
|
150
|
+
against white (5.78) rather than against near-black (3.39).
|
|
151
|
+
Dark danger was white until now, which was correct while --pink was
|
|
152
|
+
#e35b8f. Issue #131 lightened it to #e97ca5 to clear AA as INK on its own
|
|
153
|
+
glow, and that carried the fill up past the point where white could read on
|
|
154
|
+
it — 2.66 on the toast's status circle, under the 3:1 of WCAG 1.4.11. The
|
|
155
|
+
ten status x theme icon pairs are gated in stories/signal-contrast.test.js. */
|
|
142
156
|
--signal-contrast: #0c0c0c;
|
|
143
|
-
--danger-contrast:
|
|
157
|
+
--danger-contrast: var(--signal-contrast);
|
|
144
158
|
|
|
145
159
|
--glow-green: rgba(152, 255, 143, 0.16);
|
|
146
160
|
--glow-purple: rgba(155, 93, 255, 0.18);
|
|
147
161
|
--glow-cyan: rgba(32, 220, 245, 0.14);
|
|
148
|
-
--glow-pink: rgba(
|
|
162
|
+
--glow-pink: rgba(233, 124, 165, 0.16);
|
|
149
163
|
|
|
150
164
|
/* Status-chip paint — the ink + fill of a badge / pill. Dark tints with the
|
|
151
165
|
signal's own glow; light needs a solid, deepened pair (the glows wash out
|
|
@@ -156,6 +170,23 @@
|
|
|
156
170
|
--chip-warn-fill: color-mix(in srgb, var(--amber) 15%, transparent);
|
|
157
171
|
--chip-danger-ink: var(--pink);
|
|
158
172
|
--chip-danger-fill: var(--glow-pink);
|
|
173
|
+
--chip-info-ink: var(--cyan);
|
|
174
|
+
--chip-info-fill: var(--glow-cyan);
|
|
175
|
+
|
|
176
|
+
/* Solid signal surfaces — a status that has stopped being ink and become the
|
|
177
|
+
FILL (a solid toast). The fill and its ink are ONE pair, never two
|
|
178
|
+
independent picks: the fill is the status at this theme's extreme, and the
|
|
179
|
+
ink is the pole opposite it, so a single ink clears all five. Dark fills are
|
|
180
|
+
the bright signals, so the ink is near-black — it measures 15.87 on --green,
|
|
181
|
+
7.35 on --pink, 13.40 on --amber, 11.75 on --cyan and 6.29 on --muted.
|
|
182
|
+
Neutral needs no chip pair to take part: --muted is already its
|
|
183
|
+
fill-strength form. See issues #131 and #149. */
|
|
184
|
+
--signal-solid-ink: var(--signal-contrast);
|
|
185
|
+
--signal-solid-success: var(--green);
|
|
186
|
+
--signal-solid-danger: var(--pink);
|
|
187
|
+
--signal-solid-warn: var(--amber);
|
|
188
|
+
--signal-solid-info: var(--cyan);
|
|
189
|
+
--signal-solid-neutral: var(--muted);
|
|
159
190
|
|
|
160
191
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.28);
|
|
161
192
|
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
@@ -191,7 +222,11 @@
|
|
|
191
222
|
--purple-mid: #7a3be0;
|
|
192
223
|
--cyan: #0c8fa8;
|
|
193
224
|
--green: #1c8a2c;
|
|
194
|
-
|
|
225
|
+
/* Deeper than the dark pink for the same reason dark's is lighter: the wash
|
|
226
|
+
under it is white, so the ink has to come down to meet it. #d63c72 measured
|
|
227
|
+
3.87:1 on --glow-pink over white and 4.42:1 on plain white — the required
|
|
228
|
+
marker and the form error missed AA on nothing at all. See issue #131. */
|
|
229
|
+
--pink: #b63361;
|
|
195
230
|
--amber: #b5730a;
|
|
196
231
|
|
|
197
232
|
/* A WHITE app: the page and cards are both white; cards are delineated by a
|
|
@@ -219,20 +254,51 @@
|
|
|
219
254
|
--signal-contrast: #0c0c0c;
|
|
220
255
|
--danger-contrast: #ffffff;
|
|
221
256
|
|
|
222
|
-
--
|
|
257
|
+
/* Re-tinted from --green. It was rgba(30, 150, 50, .1) — a colour that exists
|
|
258
|
+
nowhere else in the kit, carried unedited since the initial commit while the
|
|
259
|
+
other three light glows are exact tints of their own token. The wash over
|
|
260
|
+
white moves #e9f5eb -> #e8f3ea, at most two levels on a channel, which is
|
|
261
|
+
0.86 of a deltaE — under the 1.0 that is normally called imperceptible. No
|
|
262
|
+
verdict moves with it: body copy on the success wash goes 9.29 -> 9.15
|
|
263
|
+
against the 4.5 AA bar, and the --green icon on it 3.97 -> 3.91 against the
|
|
264
|
+
3.0 bar WCAG 1.4.11 sets for a graphic. The success chips are untouched —
|
|
265
|
+
light's --chip-success-fill is the solid #dff3e4, not this. Gated for all
|
|
266
|
+
four glows in stories/signal-contrast.test.js. See issue #131. */
|
|
267
|
+
--glow-green: rgba(28, 138, 44, 0.1);
|
|
223
268
|
--glow-purple: rgba(106, 45, 204, 0.09);
|
|
224
269
|
--glow-cyan: rgba(12, 143, 168, 0.1);
|
|
225
|
-
--glow-pink: rgba(
|
|
270
|
+
--glow-pink: rgba(182, 51, 97, 0.1);
|
|
226
271
|
|
|
227
272
|
/* Solid chip pairs for the white app. Each ink is deepened past its signal
|
|
228
273
|
hue so 10px uppercase text clears WCAG AA on its own pale fill —
|
|
229
|
-
e.g. --pink (#
|
|
274
|
+
e.g. --pink (#b63361) on #fbe0ea is 4.66:1, #b7295f is 4.85:1. */
|
|
230
275
|
--chip-success-ink: #1f7a38;
|
|
231
276
|
--chip-success-fill: #dff3e4;
|
|
232
277
|
--chip-warn-ink: #8a5e00;
|
|
233
278
|
--chip-warn-fill: #fbedd2;
|
|
234
279
|
--chip-danger-ink: #b7295f;
|
|
235
280
|
--chip-danger-fill: #fbe0ea;
|
|
281
|
+
/* Info was the one signal family without a pair, so the info badge was still
|
|
282
|
+
painting --cyan (#0c8fa8) on --glow-cyan over white, which is 3.39:1 —
|
|
283
|
+
under AA. #0a7286 on #ddeff3 is 4.71:1, inside the 4.64–4.93 band the other
|
|
284
|
+
three pairs already sit in. See issue #131. */
|
|
285
|
+
--chip-info-ink: #0a7286;
|
|
286
|
+
--chip-info-fill: #ddeff3;
|
|
287
|
+
|
|
288
|
+
/* Light runs the same rule the other way. The signals are mid-tone here — a
|
|
289
|
+
solid --green fill measures 4.40 against near-black and 4.45 against white,
|
|
290
|
+
so NO ink rescues it and the fill is what has to move. It moves to the
|
|
291
|
+
deepened form the chip pair already carries, and the pole above it, white,
|
|
292
|
+
clears every one: #ffffff measures 5.39 on --chip-success-ink, 6.01 on
|
|
293
|
+
--chip-danger-ink, 5.70 on --chip-warn-ink, 5.58 on --chip-info-ink and 6.11
|
|
294
|
+
on --muted. A future status supplies one --signal-solid-* value dark enough
|
|
295
|
+
for white and inherits the ink. See issues #131 and #149. */
|
|
296
|
+
--signal-solid-ink: #ffffff;
|
|
297
|
+
--signal-solid-success: var(--chip-success-ink);
|
|
298
|
+
--signal-solid-danger: var(--chip-danger-ink);
|
|
299
|
+
--signal-solid-warn: var(--chip-warn-ink);
|
|
300
|
+
--signal-solid-info: var(--chip-info-ink);
|
|
301
|
+
--signal-solid-neutral: var(--muted);
|
|
236
302
|
|
|
237
303
|
--shadow-sm: 0 1px 3px rgba(30, 30, 50, 0.08);
|
|
238
304
|
--shadow-md: 0 8px 24px rgba(30, 30, 50, 0.1);
|