@apliteni/apliteni-ui 0.23.1 → 0.23.2
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/package.json +1 -1
- package/src/components/confirm.js +27 -28
- package/src/components/drawer.js +27 -28
- package/src/components/dropdown.js +26 -23
- package/src/components/index.js +9 -13
- package/src/components/nav.js +7 -20
- package/src/components/overlay.js +7 -16
- package/src/components/success.js +8 -17
- package/src/components/topbar.js +8 -12
- package/src/styles/base.css +4 -17
- package/src/styles/callout.css +4 -12
- package/src/styles/confirm.css +9 -12
- package/src/styles/loading.css +9 -24
- package/src/styles/motion.css +12 -20
- package/src/styles/reduced-motion.css +8 -19
- package/src/styles/table.css +5 -11
- package/src/tokens/accents.css +9 -13
- package/src/tokens/tokens.css +4 -12
package/package.json
CHANGED
|
@@ -3,18 +3,13 @@
|
|
|
3
3
|
// container.innerHTML = confirm({ title, body, confirmLabel, cancelLabel });
|
|
4
4
|
// wireConfirm(container); // scrim/Esc/answers + focus trap
|
|
5
5
|
//
|
|
6
|
-
// A page trigger opens it by id:
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// A page trigger opens it by id: <button data-confirm-open="ID">. Answering is
|
|
7
|
+
// the caller's job — a listener on [data-confirm-accept] is where the
|
|
8
|
+
// destructive work goes — and focus opens on the SAFE answer, so a reader who
|
|
9
|
+
// hits Enter out of habit keeps what they have.
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// the SAFE answer, never the destructive one, so a reader who hits Enter out of
|
|
13
|
-
// habit keeps what they have.
|
|
14
|
-
//
|
|
15
|
-
// Inertness, Escape and the focus trap come from ./overlay.js — one stack for
|
|
16
|
-
// every overlay on the page, so a confirm over a drawer never has to guess which
|
|
17
|
-
// of the two the keyboard belongs to.
|
|
11
|
+
// Inertness, Escape and the focus trap come from ./overlay.js: one stack for
|
|
12
|
+
// every overlay on the page.
|
|
18
13
|
import { button, esc } from './index.js';
|
|
19
14
|
import { OVERLAY_LAYER, adoptOverlay, focusablesIn, popOverlay, pushOverlay, returnFocus, syncOverlays } from './overlay.js';
|
|
20
15
|
|
|
@@ -24,23 +19,27 @@ const cx = (...a) => a.filter(Boolean).join(' ');
|
|
|
24
19
|
let _uid = 0;
|
|
25
20
|
const nextId = (p = 'confirm') => `${p}-${++_uid}`;
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The public factory. Returns an HTML string; wire it with wireConfirm().
|
|
24
|
+
*
|
|
25
|
+
* `specimen` renders open as a *picture* of the dialog — same markup, minus the
|
|
26
|
+
* data-confirm hook and aria-modal, so no wiring and no key handler can reach
|
|
27
|
+
* it. A documentation page shows several at once and none of them may own the
|
|
28
|
+
* page or answer Escape: an answered specimen would erase itself with nothing to
|
|
29
|
+
* bring it back, and three modal dialogs on one page trap the reader in the
|
|
30
|
+
* first. Use `open` when the dialog is real.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} [o]
|
|
33
|
+
* @param {string} [o.title] the question — also the accessible name
|
|
34
|
+
* @param {string} [o.body] the consequence — the accessible description
|
|
35
|
+
* @param {string} [o.confirmLabel] the destructive answer (default 'Confirm')
|
|
36
|
+
* @param {string} [o.cancelLabel] the safe answer (default 'Cancel')
|
|
37
|
+
* @param {string} [o.variant] 'danger' (default) | 'primary'
|
|
38
|
+
* @param {boolean} [o.open] render already-open, as a real dialog
|
|
39
|
+
* @param {boolean} [o.specimen] render open as a picture of the dialog
|
|
40
|
+
* @param {string} [o.id] root id a [data-confirm-open] trigger targets
|
|
41
|
+
* @returns {string} html
|
|
42
|
+
*/
|
|
44
43
|
export function confirm({
|
|
45
44
|
title = 'Are you sure?', body = '', confirmLabel = 'Confirm', cancelLabel = 'Cancel',
|
|
46
45
|
variant = 'danger', id, open = false, specimen = false,
|
package/src/components/drawer.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
// Drawer — the kit's edge-anchored overlay panel
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// left|right, full-width for top|bottom.
|
|
1
|
+
// Drawer — the kit's edge-anchored overlay panel: a panel that slides in from
|
|
2
|
+
// any screen edge over a scrim. Header / scrollable body / footer actions, sizes
|
|
3
|
+
// sm|md|lg along the slide axis.
|
|
5
4
|
//
|
|
6
5
|
// container.innerHTML = drawer({ side: 'right', title: 'Filters', body });
|
|
7
6
|
// wireDrawer(container); // scrim/Esc/close-button + focus trap
|
|
8
7
|
//
|
|
9
|
-
// A page trigger opens it by id:
|
|
10
|
-
//
|
|
11
|
-
// open, or `specimen: true` for a picture of one on a documentation page.
|
|
12
|
-
//
|
|
13
|
-
// Inertness, Escape and the focus trap are shared with confirm(): both are
|
|
8
|
+
// A page trigger opens it by id: <button data-drawer-open="ID">. Inertness,
|
|
9
|
+
// Escape and the focus trap are shared with confirm(): both are
|
|
14
10
|
// "content over a scrim, focus-trapped, Esc-dismissable", and they push onto one
|
|
15
|
-
// stack in ./overlay.js so the two can never disagree about which of them the
|
|
16
|
-
// keyboard
|
|
11
|
+
// stack in ./overlay.js, so the two can never disagree about which of them the
|
|
12
|
+
// keyboard belongs to.
|
|
17
13
|
import { esc, icon } from './index.js';
|
|
18
14
|
import { OVERLAY_LAYER, adoptOverlay, focusablesIn, popOverlay, pushOverlay, returnFocus, syncOverlays } from './overlay.js';
|
|
19
15
|
|
|
@@ -23,23 +19,26 @@ const cx = (...a) => a.filter(Boolean).join(' ');
|
|
|
23
19
|
let _uid = 0;
|
|
24
20
|
const nextId = (p = 'drawer') => `${p}-${++_uid}`;
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The public factory. Returns an HTML string; wire it with wireDrawer().
|
|
24
|
+
*
|
|
25
|
+
* `specimen` renders open as a *picture* of the panel — see confirm() for why a
|
|
26
|
+
* documentation page wants that. Use `open` when the drawer is real.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} [o]
|
|
29
|
+
* @param {string} [o.side] 'right' (default) | 'left' | 'top' | 'bottom'
|
|
30
|
+
* @param {string} [o.size] 'sm' | 'md' (default) | 'lg', along the slide axis
|
|
31
|
+
* @param {string} [o.title] header heading, also the accessible name
|
|
32
|
+
* @param {string} [o.body] scrollable body HTML (trusted markup)
|
|
33
|
+
* @param {string} [o.footer] pinned footer actions HTML (trusted markup)
|
|
34
|
+
* @param {boolean} [o.open] render already-open, as a real panel
|
|
35
|
+
* @param {boolean} [o.specimen] render open as a picture of the panel
|
|
36
|
+
* @param {string} [o.id] root id a [data-drawer-open] trigger targets
|
|
37
|
+
* @param {string} [o.ariaLabel] accessible name when there is no visible title
|
|
38
|
+
* @param {boolean} [o.dismissible] close button + scrim/Esc dismiss (default true)
|
|
39
|
+
* @param {string} [o.closeLabel] accessible name for the close button
|
|
40
|
+
* @returns {string} html
|
|
41
|
+
*/
|
|
43
42
|
export function drawer({
|
|
44
43
|
side = 'right', size = 'md', title, body = '', footer = '',
|
|
45
44
|
open = false, specimen = false, id, ariaLabel, dismissible = true, closeLabel = 'Close',
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
// Dropdown — the kit's one popover-list primitive. A trigger
|
|
2
|
-
//
|
|
3
|
-
// description, optional leading icon, optional trailing badge, selected +
|
|
4
|
-
// disabled state). Two flavours share the same panel + the same open/close JS:
|
|
1
|
+
// Dropdown — the kit's one popover-list primitive. A trigger opens a panel of
|
|
2
|
+
// item rows; two flavours share the same panel and the same open/close JS:
|
|
5
3
|
//
|
|
6
4
|
// variant: 'select' → role="listbox" / role="option", value shown in trigger
|
|
7
5
|
// variant: 'menu' → role="menu" / role="menuitem", action list
|
|
8
6
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// one open/close/click-outside/Esc/keyboard implementation in the kit.
|
|
7
|
+
// The name is deliberate: `menu` implies actions only and `select` implies a
|
|
8
|
+
// form control bound to a value, and this factory is the umbrella both are
|
|
9
|
+
// specialisations of. The topbar's version switcher and account menu are thin
|
|
10
|
+
// consumers of the SAME wiring, so there is one open/close/Esc/keyboard
|
|
11
|
+
// implementation in the kit.
|
|
15
12
|
//
|
|
16
13
|
// container.innerHTML = dropdown({ label: 'version:', value: '…', items });
|
|
17
|
-
// wireDropdown(container); // or let wireTopbar() do it
|
|
14
|
+
// wireDropdown(container); // or let wireTopbar() do it
|
|
18
15
|
import { esc, icon } from './index.js';
|
|
19
16
|
|
|
20
17
|
const cx = (...a) => a.filter(Boolean).join(' ');
|
|
@@ -66,18 +63,24 @@ function ddBody({ items, sections }, listbox) {
|
|
|
66
63
|
return (items || []).map((it) => ddItem(it, listbox)).join('');
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The public factory. Returns an HTML string; wire it with wireDropdown().
|
|
68
|
+
*
|
|
69
|
+
* @param {object} [o]
|
|
70
|
+
* @param {string} [o.label] muted prefix in the trigger (e.g. "version:")
|
|
71
|
+
* @param {string} [o.value] current value shown in the trigger
|
|
72
|
+
* @param {string} [o.placeholder] shown when there is no value
|
|
73
|
+
* @param {string} [o.variant] 'select' (listbox) | 'menu' (inferred from items)
|
|
74
|
+
* @param {Array} [o.items] [{ label, value?, description?, icon?, badge?, selected?, disabled?, href?, danger? }]
|
|
75
|
+
* @param {Array} [o.sections] [{ label, items }] — grouped alternative to items
|
|
76
|
+
* @param {string} [o.header] raw HTML pinned to the top of the panel
|
|
77
|
+
* @param {string} [o.footer] raw HTML pinned to the bottom of the panel
|
|
78
|
+
* @param {string} [o.align] 'start' (default) | 'end' — the edge the panel hugs
|
|
79
|
+
* @param {boolean|number} [o.scroll] true, or a maxHeight in px, to cap and scroll
|
|
80
|
+
* @param {boolean} [o.open] render already-open (handy for screenshots)
|
|
81
|
+
* @param {string} [o.ariaLabel] accessible name for the panel and trigger
|
|
82
|
+
* @returns {string} html
|
|
83
|
+
*/
|
|
81
84
|
export function dropdown({
|
|
82
85
|
label, value, placeholder = 'Select…', variant, items, sections,
|
|
83
86
|
header = '', footer = '', triggerContent, triggerClass = '', chevron = true,
|
package/src/components/index.js
CHANGED
|
@@ -66,21 +66,17 @@ export function card({ title, sub, body = '', variant, pad, icon: ic } = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// ---- Segmented control ---------------------------------------------------
|
|
69
|
-
// A strip of mutually exclusive toggle buttons
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// options, Home/End to jump to the ends, and `aria-pressed` to say which is on.
|
|
74
|
-
// wireTopbar() ships that keyboard behaviour (see topbar.js); the markup here
|
|
75
|
-
// carries the state it reads.
|
|
69
|
+
// A strip of mutually exclusive toggle buttons.
|
|
70
|
+
// It is NOT a tablist: it controls no panel, so it must not announce one.
|
|
71
|
+
// `role="toolbar"` with a roving tabindex is the honest shape, and wireTopbar()
|
|
72
|
+
// ships the keyboard behaviour.
|
|
76
73
|
//
|
|
77
|
-
// When the pill drives a real change of view
|
|
78
|
-
//
|
|
74
|
+
// When the pill drives a real change of view, reach for tabs() instead —
|
|
75
|
+
// that one owns panels and earns the tab announcement.
|
|
79
76
|
//
|
|
80
|
-
// `ariaLabel` names the strip. `name` seeds data-seg
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
// switchToggle() defaults its label.
|
|
77
|
+
// `ariaLabel` names the strip. `name` seeds data-seg and is deliberately
|
|
78
|
+
// not an accessible name — it is an identifier, not prose, defaulting to
|
|
79
|
+
// "Options" so a strip is never left unlabelled.
|
|
84
80
|
export function segmented({ options = [], active = 0, size, block, name = 'seg', ariaLabel = 'Options' } = {}) {
|
|
85
81
|
const cls = cx('ui-seg', size && `ui-seg--${size}`, block && 'ui-seg--block');
|
|
86
82
|
// Exactly one option holds the Tab stop. If `active` points nowhere (nothing
|
package/src/components/nav.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
// Navigation — the kit's primary wayfinding primitives.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Navigation — the kit's primary wayfinding primitives. `nav({ variant })`
|
|
2
|
+
// dispatches to sidebarNav(), navTabs() and breadcrumbs(), each also exported
|
|
3
|
+
// directly: the three share tokens and classes but almost no markup, so a single
|
|
4
|
+
// options bag would mean wildly different fields per variant.
|
|
4
5
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// variant: 'tabs' → navTabs() horizontal page tabs with an active
|
|
9
|
-
// underline (default) or pill.
|
|
10
|
-
// variant: 'breadcrumbs' → breadcrumbs() the `Finance / Payouts` trail.
|
|
11
|
-
//
|
|
12
|
-
// Why an umbrella AND named exports (unlike dropdown, which is one factory): the
|
|
13
|
-
// three variants don't share a body the way select/menu share a panel, so a
|
|
14
|
-
// single options bag would mean wildly different fields per variant. The named
|
|
15
|
-
// exports keep each call site honest; `nav()` stays the discoverable entry the
|
|
16
|
-
// issue asks for and the one thing to import when the variant is data-driven.
|
|
17
|
-
//
|
|
18
|
-
// These are NAVIGATION controls (links between locations), not tab panels — so
|
|
19
|
-
// tabs render as <nav> + <a aria-current="page">, not role="tablist" (that's
|
|
20
|
-
// what segmented() is for). Only the collapsible sidebar groups need JS; wire
|
|
21
|
-
// them once after mount with wireNav() (preview.js does this for Storybook).
|
|
6
|
+
// These are NAVIGATION controls, not tab panels — so tabs render as
|
|
7
|
+
// <nav> + <a aria-current="page">, not role="tablist" (that is what segmented()
|
|
8
|
+
// is for). Only the collapsible sidebar groups need JS; wire with wireNav().
|
|
22
9
|
import { esc, icon } from './index.js';
|
|
23
10
|
|
|
24
11
|
const cx = (...a) => a.filter(Boolean).join(' ');
|
|
@@ -154,23 +154,14 @@ export function pushOverlay(root, panel, dismiss, layer) {
|
|
|
154
154
|
/**
|
|
155
155
|
* Take on a root that arrived already open — markup rendered with `open: true`,
|
|
156
156
|
* which nobody called open…() for. Without this its aria-modal is a claim the
|
|
157
|
-
* page contradicts
|
|
157
|
+
* page contradicts.
|
|
158
158
|
*
|
|
159
|
-
* It goes in by
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* E, steps 8 and 9). That comparison earns its keep whenever adoption order and
|
|
166
|
-
* document order disagree: a root inserted above one already on the stack and
|
|
167
|
-
* wired after it, and — in a browser only — a consumer stylesheet that lifts the
|
|
168
|
-
* drawer onto the confirm's layer, where wiring runs drawer-first however the
|
|
169
|
-
* markup is ordered. A browser refines the layer from the live z-index; JSDOM has
|
|
170
|
-
* none to give, so the tests hold OVERLAY_LAYER to the sheets instead. A root that
|
|
171
|
-
* renders closed is left alone: wiring is not an opening, and it waits for the one
|
|
172
|
-
* that is. A specimen never reaches here at all — it carries no hook for the
|
|
173
|
-
* wiring to find.
|
|
159
|
+
* It goes in by PAINT ORDER, because wiring has no history to order by. Document
|
|
160
|
+
* position only separates two overlays on the same layer, and there it is the
|
|
161
|
+
* right answer rather than a fallback: at equal stack levels the later root
|
|
162
|
+
* paints on top (CSS 2.2 Appendix E, steps 8 and 9).
|
|
163
|
+
*
|
|
164
|
+
* A root that renders closed is left alone — wiring is not an opening.
|
|
174
165
|
*/
|
|
175
166
|
export function adoptOverlay(root, panel, dismiss, layer) {
|
|
176
167
|
if (!root.classList.contains('is-open')) return;
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
// Success / confirmation surface
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// no confetti, no sweep). Accent-aware via the kit tokens; the success mark
|
|
6
|
-
// stays on the --green family. Styles ship in styles/success.css.
|
|
1
|
+
// Success / confirmation surface. One factory, three layouts and three
|
|
2
|
+
// backdrops, an SVG check that draws itself in, optional confetti and an
|
|
3
|
+
// optional auto-redirect countdown. Every motion path is reduced-motion safe.
|
|
4
|
+
// Accent-aware, but the success mark stays on the --green family.
|
|
7
5
|
//
|
|
8
|
-
// container.innerHTML = success({
|
|
9
|
-
// title: 'Feedback sent',
|
|
10
|
-
// body: 'It goes straight to the strategy owner.',
|
|
11
|
-
// actions: [
|
|
12
|
-
// { label: 'Back to strategy', variant: 'primary', icon: 'compass' },
|
|
13
|
-
// { label: 'Send another', variant: 'ghost' },
|
|
14
|
-
// ],
|
|
15
|
-
// });
|
|
6
|
+
// container.innerHTML = success({ title, body, actions: [{ label, variant }] });
|
|
16
7
|
//
|
|
17
|
-
// The check animation is pure CSS, so string-rendered markup animates on its
|
|
18
|
-
//
|
|
19
|
-
//
|
|
8
|
+
// The check animation is pure CSS, so string-rendered markup animates on its own
|
|
9
|
+
// once mounted. A live countdown is opt-in via wireSuccess(); the markup alone
|
|
10
|
+
// shows the ring sweep and a static number.
|
|
20
11
|
import { esc, button } from './index.js';
|
|
21
12
|
|
|
22
13
|
// Self-drawing check: a faint track disc, a filled accent disc that springs in,
|
package/src/components/topbar.js
CHANGED
|
@@ -52,19 +52,15 @@ export function versionSwitcher(versions = [], activeIdx = 0) {
|
|
|
52
52
|
`<div class="vsw__menu" data-dropdown-panel role="listbox" aria-label="Version">${opts}</div></div>`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// `nav`
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// drift #127 was filed about. Every field below is interpolated raw, so what
|
|
60
|
-
// arrives has to arrive escaped — accountMenuNav() is what does that.
|
|
55
|
+
// `nav` mirrors the account sidebar, DERIVED from the one ACCOUNT_NAV definition
|
|
56
|
+
// rather than restated: a second literal agreed with it by hand about the icon
|
|
57
|
+
// and disagreed about the encoding, which is the drift #127 was filed about.
|
|
58
|
+
// Every field below is interpolated raw, so what arrives has to arrive escaped.
|
|
61
59
|
//
|
|
62
|
-
// `initials` is the avatar
|
|
63
|
-
//
|
|
64
|
-
// the
|
|
65
|
-
//
|
|
66
|
-
// from the caller's own strings and passes it down beside them. Left out, it is
|
|
67
|
-
// computed here from `name` and `email`, exactly where it always came from.
|
|
60
|
+
// `initials` is the avatar. A derived value has to be derived BEFORE the
|
|
61
|
+
// escaping — `<Ada>` and `<Ada>` do not begin with the same character — so
|
|
62
|
+
// shell.js computes the mark from the caller's own strings and passes it down
|
|
63
|
+
// beside them. Left out, it is computed here from `name` and `email`.
|
|
68
64
|
export function accountMenu({
|
|
69
65
|
name = 'Ada Lovelace', email = 'ada@apliteni.com', active = 'prefs', nav, initials: mark,
|
|
70
66
|
} = {}) {
|
package/src/styles/base.css
CHANGED
|
@@ -102,23 +102,10 @@ a {
|
|
|
102
102
|
|
|
103
103
|
/* Sensible default size for inline icons that a parent rule doesn't size.
|
|
104
104
|
A floor, not a ceiling: :where() holds the whole filter at zero specificity,
|
|
105
|
-
so this weighs (0,0,1)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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. Two gates
|
|
112
|
-
keep the rules that compete with this one honest, each over the ground it
|
|
113
|
-
actually sweeps: src/styles/icon-size.test.js reads the stylesheets
|
|
114
|
-
src/index.css imports, and scripts/icon-size-surfaces.test.js reads the
|
|
115
|
-
surfaces the kit renders — the landing site's pages and the Storybook stories.
|
|
116
|
-
Neither claims more, and the second one's header lists what it declines to
|
|
117
|
-
claim; read it before trusting a green run. Two gaps are worth knowing here: a
|
|
118
|
-
reset scoped to an ancestor that a subject's own selector does not name is out
|
|
119
|
-
of reach of both, and .storybook/preview.js imports src/index.css into every
|
|
120
|
-
story iframe, which makes .storybook/ a third rendering surface neither gate
|
|
121
|
-
sweeps. Nothing in there sizes an icon today. */
|
|
105
|
+
so this weighs (0,0,1) and any component rule that sizes an icon outranks it.
|
|
106
|
+
The count of those rules is deliberately not written here — it moved once
|
|
107
|
+
already, and two gates hold it over the ground each actually sweeps.
|
|
108
|
+
why: CONTRIBUTING.md#the-reset-is-a-floor-and-its-specificity-is-the-whole-of-that */
|
|
122
109
|
svg:where(:not([width]):not([height])) {
|
|
123
110
|
width: 1.1em;
|
|
124
111
|
height: 1.1em;
|
package/src/styles/callout.css
CHANGED
|
@@ -49,18 +49,10 @@
|
|
|
49
49
|
|
|
50
50
|
/* status → the paint tokens every style below consumes.
|
|
51
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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
--toast-action-ink is separate from --toast-accent for the same reason the
|
|
57
|
-
fill is: the accent is a graphic colour, sized for a 3px rule and a 22px
|
|
58
|
-
circle, and in the light theme it is not a colour text can be set in. The
|
|
59
|
-
trailing action is the one part of a toast that is BOTH the status colour and
|
|
60
|
-
a piece of text, so it takes the chip inks — the text-grade version of the
|
|
61
|
-
same five statuses, and the same value as the accent in dark by construction.
|
|
62
|
-
neutral's --toast-on is --signal-solid-ink because the neutral circle IS
|
|
63
|
-
--signal-solid-neutral: one fill, so one ink, chosen once. #206 */
|
|
52
|
+
--toast-on the glyph ink on the accent circle. --toast-solid and
|
|
53
|
+
--toast-action-ink are separate from the accent on purpose: a fill and a piece
|
|
54
|
+
of text are different jobs from a 22px circle.
|
|
55
|
+
why: docs/specification.md#colour-and-contrast */
|
|
64
56
|
.ui-toast--success { --toast-accent: var(--green); --toast-action-ink: var(--chip-success-ink); --toast-glow: var(--glow-green); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-success); }
|
|
65
57
|
.ui-toast--danger { --toast-accent: var(--pink); --toast-action-ink: var(--chip-danger-ink); --toast-glow: var(--glow-pink); --toast-on: var(--danger-contrast); --toast-solid: var(--signal-solid-danger); }
|
|
66
58
|
.ui-toast--warn { --toast-accent: var(--amber); --toast-action-ink: var(--chip-warn-ink); --toast-glow: color-mix(in srgb, var(--amber) 14%, transparent); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-warn); }
|
package/src/styles/confirm.css
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* re-themes across accents + light/dark. Shares its scrim + focus-trap
|
|
5
|
-
* behaviour with the drawer (see overlay.js).
|
|
1
|
+
/* Confirm — the kit's confirmation dialog: a centred panel over a scrim, for the
|
|
2
|
+
* question a destructive action has to ask. Fully token-driven, and it shares
|
|
3
|
+
* its scrim and focus-trap behaviour with the drawer (see overlay.js).
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
* switched rather than transitioned. A transitioned visibility is still
|
|
5
|
+
* `visibility` is SWITCHED rather than transitioned: transitioned, it is still
|
|
9
6
|
* `hidden` in the frame the class lands, so openConfirm()'s focus() would have
|
|
10
|
-
* nothing to focus
|
|
11
|
-
* switch; the panel and scrim inherit it.
|
|
7
|
+
* nothing to focus. The root owns the switch; panel and scrim inherit it.
|
|
12
8
|
*
|
|
13
|
-
* Local tokens
|
|
9
|
+
* Local tokens so the panel and scrim re-theme cleanly:
|
|
14
10
|
* --confirm-surface panel background --confirm-shadow panel elevation
|
|
15
|
-
* --confirm-scrim backdrop colour
|
|
16
|
-
*
|
|
11
|
+
* --confirm-scrim backdrop colour
|
|
12
|
+
*
|
|
13
|
+
* why: docs/specification.md#motion */
|
|
17
14
|
|
|
18
15
|
.ui-confirm {
|
|
19
16
|
--confirm-surface: var(--surface-2);
|
package/src/styles/loading.css
CHANGED
|
@@ -1,30 +1,15 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
1
|
+
/* Loading and permission-denied — the screen-scale states. Markup comes from
|
|
2
|
+
* components/loading.js; this file owns layout and nothing else.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The shimmer is .m-skeleton from styles/motion.css, so reduced motion is
|
|
5
|
+
* already handled by the global net and there is one shimmer in the kit.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* <span class="ui-skel__bar m-skeleton"></span>…
|
|
7
|
+
* .ui-denied deliberately borrows every colour .ui-empty uses: to a reader the
|
|
8
|
+
* two are the same event — what you came for is not here — and giving denial its
|
|
9
|
+
* own red made it read as a fault the reader had committed. The lock says which
|
|
10
|
+
* of the two it is.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* <div class="ui-denied__seal">…lock…</div>
|
|
14
|
-
* <div class="ui-denied__title">You don’t have access</div>
|
|
15
|
-
* <div class="ui-denied__sub">…</div>
|
|
16
|
-
* <div class="ui-denied__need">Needs <code class="ui-code">reports.read</code></div>
|
|
17
|
-
* <div class="ui-denied__actions">…</div>
|
|
18
|
-
*
|
|
19
|
-
* The shimmer is .m-skeleton from styles/motion.css — this file owns layout and
|
|
20
|
-
* nothing else about the animation, so reduced motion is already handled by the
|
|
21
|
-
* global block at the end of that file and there is one shimmer in the kit.
|
|
22
|
-
*
|
|
23
|
-
* .ui-denied deliberately borrows every colour .ui-empty uses. To a reader the
|
|
24
|
-
* two are the same event — what you came for is not here — and giving denial
|
|
25
|
-
* its own red made it read as a fault the reader had committed. The lock says
|
|
26
|
-
* which of the two it is.
|
|
27
|
-
* ========================================================================== */
|
|
12
|
+
* why: docs/specification.md#pending-and-denied-states */
|
|
28
13
|
|
|
29
14
|
/* Visually hidden, still read aloud. The kit had no such utility, and the live
|
|
30
15
|
region needs one: its message is for assistive tech only — the sighted
|
package/src/styles/motion.css
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* A small, token-driven, framework-agnostic motion system: reusable entrance,
|
|
5
|
-
* micro-interaction, attention, and scroll-reveal effects as plain classes.
|
|
6
|
-
* Every timing reads the brand motion vocabulary (--duration-* / --easing-*,
|
|
7
|
-
* synced from design-system) so motion re-themes and stays consistent.
|
|
1
|
+
/* apliteni-ui — motion library. Reusable entrance, micro-interaction, attention
|
|
2
|
+
* and scroll-reveal effects as plain classes, every timing reading the brand
|
|
3
|
+
* motion vocabulary.
|
|
8
4
|
*
|
|
9
5
|
* Per-effect overrides via custom properties on the element:
|
|
10
|
-
* --m-dur
|
|
11
|
-
* --m-
|
|
12
|
-
*
|
|
13
|
-
*
|
|
6
|
+
* --m-dur duration --m-ease timing function
|
|
7
|
+
* --m-delay start delay --m-dist slide travel (default 16px)
|
|
8
|
+
*
|
|
9
|
+
* Scroll reveals need `initReveal()` from apliteni-ui/motion. Without JS,
|
|
10
|
+
* [data-reveal] content stays fully visible.
|
|
14
11
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* The global prefers-reduced-motion net lives in reduced-motion.css and is NOT
|
|
13
|
+
* @imported from here — the icon gates read each stylesheet on its own and
|
|
14
|
+
* refuse a sheet that pulls in another.
|
|
18
15
|
*
|
|
19
|
-
*
|
|
20
|
-
* any component animation in the kit — lives in reduced-motion.css, because the
|
|
21
|
-
* React bundle needs the same net and two copies would drift. index.css imports
|
|
22
|
-
* it beside this file; it is NOT @imported from here, because the icon gates read
|
|
23
|
-
* each stylesheet on its own and refuse a sheet that pulls in another.
|
|
24
|
-
* ========================================================================== */
|
|
16
|
+
* why: docs/specification.md#motion */
|
|
25
17
|
|
|
26
18
|
/* -- Entrances -------------------------------------------------------------
|
|
27
19
|
* `both` fill so the element starts at the "from" frame (hidden) and holds
|
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
1
|
+
/* apliteni-ui — reduced-motion net. One file, because it has to reach every
|
|
2
|
+
* bundle the kit publishes and a second copy would drift; a consumer who imports
|
|
3
|
+
* both bundles gets it twice, which costs nothing, since every rule here is
|
|
4
|
+
* idempotent and `!important`.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* carries it; `react/src/index.ts` imports it, so `apliteni-ui/react/css` carries
|
|
7
|
-
* it too. A consumer who imports both gets it twice, which costs nothing: every
|
|
8
|
-
* rule here is idempotent and `!important`.
|
|
6
|
+
* `iteration-count: 1` lets one-shot animations settle on their final frame — a
|
|
7
|
+
* checkmark stays drawn — instead of snapping back to the start.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* feedback check), and smooth scroll. `iteration-count: 1` lets one-shot
|
|
13
|
-
* animations settle on their final frame (a checkmark stays drawn) instead of
|
|
14
|
-
* snapping back to the start.
|
|
15
|
-
*
|
|
16
|
-
* `0.01ms` is the kill-switch idiom, not a duration: it is short enough that
|
|
17
|
-
* nothing is perceptible and non-zero so `transitionend`/`animationend` still
|
|
18
|
-
* fire, which is what scripts that wait for a close animation depend on. It is
|
|
19
|
-
* the one time literal in the kit that is not a timing — see
|
|
20
|
-
* docs/specification.md#motion.
|
|
21
|
-
* ========================================================================== */
|
|
9
|
+
* why: docs/specification.md#reduced-motion-travels-with-the-stylesheet
|
|
10
|
+
* why: docs/specification.md#motion */
|
|
22
11
|
|
|
23
12
|
@media (prefers-reduced-motion: reduce) {
|
|
24
13
|
html { scroll-behavior: auto !important; }
|
package/src/styles/table.css
CHANGED
|
@@ -53,18 +53,12 @@
|
|
|
53
53
|
.ui-table--hover:not(.ui-table--zebra) tbody tr:hover td:last-child {
|
|
54
54
|
border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
|
|
55
55
|
|
|
56
|
-
/*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* across and --space-2 down, one step in from the base table's
|
|
60
|
-
* --space-4 across and --space-4 down. Both of its old numbers sat
|
|
61
|
-
* exactly between two steps — 14px between 12 and 16, 10px between 8
|
|
62
|
-
* and 12 — and both round DOWN, because the modifier exists to fit
|
|
56
|
+
/* Data-table modifiers, composable: .ui-table--dense --zebra --hover.
|
|
57
|
+
* --dense --space-3 across, --space-2 down — one step in from the base
|
|
58
|
+
* table, and both round DOWN, because the modifier exists to fit
|
|
63
59
|
* more rows and rounding up spends the distinction it is for.
|
|
64
|
-
* --zebra row striping
|
|
65
|
-
*
|
|
66
|
-
* Cell helpers: .ui-table__num (right, tabular), .ui-table__code (plain
|
|
67
|
-
* monospace id — never a boxed code chip inside a table).
|
|
60
|
+
* --zebra row striping; 2px header rule; the hover tint out-ranks the stripe
|
|
61
|
+
* Cell helpers: .ui-table__num (right, tabular), .ui-table__code.
|
|
68
62
|
* why: docs/specification.md#spacing-and-rhythm
|
|
69
63
|
* held by: stories/table-rhythm.test.js */
|
|
70
64
|
.ui-table--dense th { padding: 0 var(--space-3) var(--space-2) 0; }
|
package/src/tokens/accents.css
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
3
|
-
* `data-theme` (dark/light). Set both on <html>:
|
|
1
|
+
/* Accent sub-themes — an orthogonal `data-accent` dimension on top of
|
|
2
|
+
* `data-theme`. Set both on <html>:
|
|
4
3
|
*
|
|
5
4
|
* <html data-theme="dark" data-accent="phoenix">
|
|
6
5
|
*
|
|
7
|
-
* Each sub-theme only re-points the accent family
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* every component follows with zero component-level changes.
|
|
6
|
+
* Each sub-theme only re-points the accent family. Surfaces, text and signal
|
|
7
|
+
* colours (green = live, pink = danger) stay put, so every accent works in
|
|
8
|
+
* light AND dark and every component follows with no component-level change.
|
|
11
9
|
*
|
|
12
|
-
* --ring is NOT here
|
|
13
|
-
* re-pointing --accent
|
|
14
|
-
* rgba() rings used to sit in this file and every one of them missed the 3:1
|
|
15
|
-
* of WCAG 1.4.11 — 1.35:1 at worst. See #218 and
|
|
16
|
-
* docs/specification.md#the-focus-ring.
|
|
10
|
+
* --ring is NOT here: it is declared once in tokens.css as var(--accent), so
|
|
11
|
+
* re-pointing --accent re-points the focus ring too.
|
|
17
12
|
*
|
|
18
13
|
* Selectors carry two attributes + :root, so they always out-specify the
|
|
19
14
|
* single-attribute theme blocks in tokens.css regardless of import order.
|
|
20
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* why: docs/specification.md#the-focus-ring */
|
|
21
17
|
|
|
22
18
|
/* ---- Phoenix — ember / rising fire (the strategy's namesake) ------------- */
|
|
23
19
|
:root[data-theme="dark"][data-accent="phoenix"] {
|
package/src/tokens/tokens.css
CHANGED
|
@@ -184,18 +184,10 @@
|
|
|
184
184
|
--ink: #e9e7f0;
|
|
185
185
|
|
|
186
186
|
/* Ink that reads on a SIGNAL colour once that colour becomes a fill — the
|
|
187
|
-
glyph in a success circle, the glyph on a toast's status circle.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
still needs it: there the signals are deepened to read as ink on white,
|
|
192
|
-
which parks them mid-luminance, and --pink is the one that comes out ahead
|
|
193
|
-
against white (5.78) rather than against near-black (3.39).
|
|
194
|
-
Dark danger was white until now, which was correct while --pink was
|
|
195
|
-
#e35b8f. Issue #131 lightened it to #e97ca5 to clear AA as INK on its own
|
|
196
|
-
glow, and that carried the fill up past the point where white could read on
|
|
197
|
-
it — 2.66 on the toast's status circle, under the 3:1 of WCAG 1.4.11. The
|
|
198
|
-
ten status x theme icon pairs are gated in stories/signal-contrast.test.js. */
|
|
187
|
+
glyph in a success circle, the glyph on a toast's status circle. One
|
|
188
|
+
near-black ink clears all five in dark; danger keeps a token of its own
|
|
189
|
+
because LIGHT still needs one.
|
|
190
|
+
why: docs/specification.md#colour-and-contrast */
|
|
199
191
|
--signal-contrast: #0c0c0c;
|
|
200
192
|
--danger-contrast: var(--signal-contrast);
|
|
201
193
|
|