@apliteni/apliteni-ui 0.23.1 → 0.23.3
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 +28 -53
- package/src/components/shell.js +48 -76
- 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 +13 -29
- package/src/tokens/accents.css +15 -29
- package/src/tokens/tokens.css +77 -145
package/src/components/shell.js
CHANGED
|
@@ -10,9 +10,8 @@ import { sidebarNav, breadcrumbs } from './nav.js';
|
|
|
10
10
|
import { prism } from '../assets/brand.js';
|
|
11
11
|
import { ACCOUNT_NAV, toMenuTuple, initials } from './account-nav.js';
|
|
12
12
|
|
|
13
|
-
// The one account navigation definition lives in account-nav.js
|
|
14
|
-
//
|
|
15
|
-
// here so the published name docs/library.md documents keeps working.
|
|
13
|
+
// The one account navigation definition lives in account-nav.js because topbar.js needs
|
|
14
|
+
// it too; re-exported here so the name docs/library.md publishes keeps working.
|
|
16
15
|
export { ACCOUNT_NAV };
|
|
17
16
|
|
|
18
17
|
const str = (v) => (v == null ? '' : String(v));
|
|
@@ -20,32 +19,24 @@ const isRecord = (v) => typeof v === 'object' && v !== null;
|
|
|
20
19
|
|
|
21
20
|
// ---- the options bag, settled once --------------------------------------
|
|
22
21
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
// accountShell() has always taken its nav as [id, icon, label, href?, target?].
|
|
33
|
-
// Accept that shape and nav.js's object shape side by side, so a consumer's
|
|
34
|
-
// existing tuples and the exported ACCOUNT_NAV both work. A nav that is not a
|
|
35
|
-
// list at all falls back to the default; an entry inside one that is neither
|
|
36
|
-
// shape is dropped, because sideItem() reads `.items` off whatever it is given.
|
|
37
|
-
// An empty list is an answer, not a mistake — it stays empty.
|
|
22
|
+
// A default parameter covers `undefined` and nothing else, so `nav: null` from an
|
|
23
|
+
// /auth/me and a `crumbs` string from somebody reading the migration note both arrived
|
|
24
|
+
// as they were. A shell that throws mid-render takes the page with it, so each option is
|
|
25
|
+
// settled here, before the first sink sees it. Adding one to appShell() means adding it
|
|
26
|
+
// to SHAPES or deciding in the open that it needs nothing.
|
|
27
|
+
|
|
28
|
+
// accountShell()'s tuple nav — [id, icon, label, href?, target?] — and nav.js's object
|
|
29
|
+
// shape are accepted side by side. A nav that is not a list falls back to the default;
|
|
30
|
+
// an entry that is neither shape is dropped. An empty list is an answer and stays.
|
|
38
31
|
const toItems = (nav) => (Array.isArray(nav) ? nav : ACCOUNT_NAV)
|
|
39
32
|
.filter(isRecord)
|
|
40
33
|
.map((n) => (Array.isArray(n)
|
|
41
34
|
? { id: n[0], icon: n[1], label: n[2], href: n[3], target: n[4] }
|
|
42
35
|
: n));
|
|
43
36
|
|
|
44
|
-
// The trail is the caller's, and `crumbs`
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// read as a one-crumb trail — that would draw a plausible page and hide the
|
|
48
|
-
// migration mistake — so it is no trail at all, which is the visible answer. A
|
|
37
|
+
// The trail is the caller's, and `crumbs` changed shape this release: `crumb: 'Payouts'`
|
|
38
|
+
// became `crumbs: [{ label }]`, one letter apart. A non-list is NOT read as a one-crumb
|
|
39
|
+
// trail — that hides the migration mistake — so it is no trail at all, which shows. A
|
|
49
40
|
// crumb with no label would draw an empty cell, so it goes too.
|
|
50
41
|
const toCrumbs = (crumbs) => (Array.isArray(crumbs) ? crumbs : [])
|
|
51
42
|
.filter((c) => isRecord(c) && !Array.isArray(c) && str(c.label) !== '');
|
|
@@ -56,23 +47,16 @@ const toReader = (a) => (isRecord(a) ? { name: str(a.name), email: str(a.email)
|
|
|
56
47
|
|
|
57
48
|
// ---- the topbar, which interpolates where the rail escapes ----------------
|
|
58
49
|
//
|
|
59
|
-
// brand() writes `word` straight into its markup and accountMenu() does the
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// `initials` travels with them because it is derived, and a derived value comes
|
|
71
|
-
// from what the caller passed, not from the entities made of it: `<Ada>` and
|
|
72
|
-
// `<Ada>` do not start with the same character. The entries land in the
|
|
73
|
-
// same sink and toMenuTuple() is what escapes them; it reads item objects, so
|
|
74
|
-
// tuples go through toItems() first. A nav nobody passed stays unpassed, and
|
|
75
|
-
// accountMenu() falls back to its own derived default rather than to nothing.
|
|
50
|
+
// brand() writes `word` straight into its markup and accountMenu() does the same with
|
|
51
|
+
// the reader's name, address and menu entries, so the escaping is here on the one path
|
|
52
|
+
// into productTopbar() rather than in each caller. The caller passes text either way.
|
|
53
|
+
|
|
54
|
+
// The reader the menu draws. Both fields are always written, empty when the caller gave
|
|
55
|
+
// none: accountMenu()'s defaults are a demo identity, so a key dropped here is a key its
|
|
56
|
+
// fixture fills in. `initials` is derived from what the caller passed rather than from
|
|
57
|
+
// the entities made of it — `<Ada>` and `<Ada>` do not start with the same
|
|
58
|
+
// character. toMenuTuple() escapes the entries and reads item objects, so tuples go
|
|
59
|
+
// through toItems() first; a nav nobody passed stays unpassed.
|
|
76
60
|
const toMenuReader = (a) => {
|
|
77
61
|
const { name, email } = toReader(a);
|
|
78
62
|
const rest = isRecord(a) && !Array.isArray(a) ? a : {};
|
|
@@ -89,12 +73,11 @@ const toTopbar = (t) => {
|
|
|
89
73
|
return out;
|
|
90
74
|
};
|
|
91
75
|
|
|
92
|
-
// `maxWidth` lands inside a style attribute, so a length is all this accepts —
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
// drops the column to `none`, the full track.
|
|
76
|
+
// `maxWidth` lands inside a style attribute, so a length is all this accepts — a number
|
|
77
|
+
// and a unit, or `none`. Anything else yields '' and the caller writes no style
|
|
78
|
+
// attribute, falling through to --measure. It must REMOVE the property rather than pass
|
|
79
|
+
// a bad value on: a custom property accepts any token stream, so garbage is a valid
|
|
80
|
+
// declaration that drops the column to `none`, the full track.
|
|
98
81
|
// why: docs/specification.md#widths
|
|
99
82
|
const LENGTH = /^(?:\d+|\d*\.\d+)(?:px|rem|em|ch|%|vw)$/;
|
|
100
83
|
const mainMax = (v) => {
|
|
@@ -108,11 +91,9 @@ const SHAPES = {
|
|
|
108
91
|
nav: toItems, crumbs: toCrumbs, account: toReader, maxWidth: mainMax, topbar: toTopbar,
|
|
109
92
|
};
|
|
110
93
|
|
|
111
|
-
// The text options settle
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
// left the brand link with no accessible name at all. Dropping the key is what
|
|
115
|
-
// lets the declared default apply.
|
|
94
|
+
// The text options settle by the same argument. `body: null` from a record with no
|
|
95
|
+
// description drew the word "null" on the page and `word: null` left the brand link with
|
|
96
|
+
// no accessible name; dropping the key is what lets the declared default apply.
|
|
116
97
|
const TEXT = ['word', 'brandHref', 'navLabel', 'title', 'sub', 'body', 'signOutHref', 'active'];
|
|
117
98
|
|
|
118
99
|
function settle(options) {
|
|
@@ -122,23 +103,18 @@ function settle(options) {
|
|
|
122
103
|
return out;
|
|
123
104
|
}
|
|
124
105
|
|
|
125
|
-
// Signing out is a navigation action, so it belongs in the rail nav's footer
|
|
126
|
-
//
|
|
127
|
-
// no session behind it.
|
|
106
|
+
// Signing out is a navigation action, so it belongs in the rail nav's footer slot.
|
|
107
|
+
// Opt-in: rendering it unasked puts a dead link on a page with no session behind it.
|
|
128
108
|
const signOut = (href) =>
|
|
129
109
|
`<a class="ui-nav__item is-danger" href="${esc(href)}" aria-label="Sign out">` +
|
|
130
110
|
`<span class="ui-nav__ic">${icon('logout')}</span>` +
|
|
131
111
|
`<span class="ui-nav__label">Sign out</span></a>`;
|
|
132
112
|
|
|
133
|
-
// Who is signed in. A sibling of the <nav>, not its footer: a
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// The initials carry the name, and the spelled-out half is aria-hidden. The
|
|
139
|
-
// narrow rail folds `.ui-app__who` out of view, so a name that lived only there
|
|
140
|
-
// left the initials on screen with nothing at all in the accessibility tree.
|
|
141
|
-
// Naming the mark instead makes the two agree at every width, and says it once.
|
|
113
|
+
// Who is signed in. A sibling of the <nav>, not its footer: a name and address are not
|
|
114
|
+
// navigation, and inside the landmark a screen reader announces the address as an entry.
|
|
115
|
+
// Empty when nobody is. The initials carry the name and the spelled-out half is
|
|
116
|
+
// aria-hidden, because the narrow rail folds `.ui-app__who` out of view and a name that
|
|
117
|
+
// lived only there left nothing in the accessibility tree.
|
|
142
118
|
function railUser({ name, email }) {
|
|
143
119
|
if (!name && !email) return '';
|
|
144
120
|
const who = [name, email].filter(Boolean).join(', ');
|
|
@@ -179,16 +155,14 @@ export function appShell(options = {}) {
|
|
|
179
155
|
ariaLabel: navLabel,
|
|
180
156
|
footer: signOutHref ? signOut(signOutHref) : '',
|
|
181
157
|
});
|
|
182
|
-
// The topbar already says the product word
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// so the name is written out — the mark itself is aria-hidden.
|
|
158
|
+
// The topbar already says the product word, so the rail head steps aside when there is
|
|
159
|
+
// one. The word is the link's only text and the narrow rail folds it out of view, so
|
|
160
|
+
// the name is written out — the mark itself is aria-hidden.
|
|
186
161
|
const brand = topbar ? '' : `<a class="ui-app__brand" href="${esc(brandHref)}" aria-label="${esc(word)}">`
|
|
187
162
|
+ `${prism(`appb-${++_shellUid}`, 24)}<span>${esc(word)}</span></a>`;
|
|
188
|
-
// A <div>, not an <aside>: <aside> is the `complementary` landmark
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
// already the landmark that names the menu.
|
|
163
|
+
// A <div>, not an <aside>: <aside> is the `complementary` landmark, and this holds the
|
|
164
|
+
// page's primary navigation and the signed-in reader. The <nav> inside it is already
|
|
165
|
+
// the landmark that names the menu.
|
|
192
166
|
const grid = `<div class="ui-app">
|
|
193
167
|
<div class="ui-app__rail">
|
|
194
168
|
${brand}
|
|
@@ -225,11 +199,9 @@ export function accountShell({
|
|
|
225
199
|
// topbar menu are handed one list rather than two readings of `nav`.
|
|
226
200
|
const items = toItems(nav);
|
|
227
201
|
const trail = [{ label: cap }, { label: crumb || title }];
|
|
228
|
-
// The preset hands the topbar the caller's text,
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
// as entities, and escaping nowhere is what the public `topbar` option used
|
|
232
|
-
// to do.
|
|
202
|
+
// The preset hands the topbar the caller's text, as it does the rail. toTopbar()
|
|
203
|
+
// escapes for the menu's raw sinks and runs once inside appShell(); escaping here as
|
|
204
|
+
// well would reach the menu as entities.
|
|
233
205
|
return appShell({
|
|
234
206
|
word,
|
|
235
207
|
nav: items,
|
|
@@ -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
|
@@ -34,14 +34,10 @@
|
|
|
34
34
|
.ui-table tbody tr { transition: background var(--dur-fast) var(--ease); }
|
|
35
35
|
|
|
36
36
|
/* Row hover: an inset, rounded fill so the highlight never sits flush against a
|
|
37
|
-
* container's border (
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Zebra tables keep the original full-bleed tint (finance ledger), so they're
|
|
42
|
-
* excluded here — their hover rule lives further down.
|
|
43
|
-
* The inset was 6px, which is a tie between --space-1 and --space-2; it rounds
|
|
44
|
-
* up, because what it buys is clearance and clearance rounds away from the edge. */
|
|
37
|
+
* container's border (#71). The separate border model with zero spacing keeps the row
|
|
38
|
+
* separators continuous while letting the row ends round, and table padding — which
|
|
39
|
+
* only applies in that model — insets the rows. Zebra keeps the original full-bleed
|
|
40
|
+
* tint (finance ledger) and is excluded here; its hover rule is below. */
|
|
45
41
|
.ui-table--hover:not(.ui-table--zebra) {
|
|
46
42
|
border-collapse: separate;
|
|
47
43
|
border-spacing: 0;
|
|
@@ -53,18 +49,10 @@
|
|
|
53
49
|
.ui-table--hover:not(.ui-table--zebra) tbody tr:hover td:last-child {
|
|
54
50
|
border-top-right-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
|
|
55
51
|
|
|
56
|
-
/*
|
|
57
|
-
*
|
|
58
|
-
* --
|
|
59
|
-
*
|
|
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
|
|
63
|
-
* more rows and rounding up spends the distinction it is for.
|
|
64
|
-
* --zebra row striping instead of per-row rules; 2px header rule; the
|
|
65
|
-
* (accent) hover tint out-ranks the stripe (defined after it)
|
|
66
|
-
* Cell helpers: .ui-table__num (right, tabular), .ui-table__code (plain
|
|
67
|
-
* monospace id — never a boxed code chip inside a table).
|
|
52
|
+
/* Data-table modifiers, composable: .ui-table--dense --zebra --hover.
|
|
53
|
+
* --dense --space-3 across, --space-2 down — one step in from the base table
|
|
54
|
+
* --zebra row striping; 2px header rule; the hover tint out-ranks the stripe
|
|
55
|
+
* Cell helpers: .ui-table__num (right, tabular), .ui-table__code.
|
|
68
56
|
* why: docs/specification.md#spacing-and-rhythm
|
|
69
57
|
* held by: stories/table-rhythm.test.js */
|
|
70
58
|
.ui-table--dense th { padding: 0 var(--space-3) var(--space-2) 0; }
|
|
@@ -77,15 +65,11 @@
|
|
|
77
65
|
.ui-table--zebra th { border-bottom-width: 2px; }
|
|
78
66
|
.ui-table--zebra td { border-bottom: 0; }
|
|
79
67
|
.ui-table--zebra tbody tr:nth-child(even) td { background: var(--surface-2); }
|
|
80
|
-
/* The stripe
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* x=0 of the stripe. `DataTable` is that composition — it writes `ui-table
|
|
86
|
-
* ui-table--hover ui-table--zebra` and no `--dense` — so its select-all and per-row
|
|
87
|
-
* checkboxes sat flush against the tint with a measured 0px gap.
|
|
88
|
-
* Header and body are inset together, or the columns stop lining up. */
|
|
68
|
+
/* The stripe stays full-bleed (the #71 inset above is hover only), but it only reads right
|
|
69
|
+
* if the end cells are inset from the tint's own edge, and `.ui-table td` sets
|
|
70
|
+
* `padding-left: 0`. Only `--dense` supplied that inset, so `--zebra` without `--dense` —
|
|
71
|
+
* `DataTable`'s composition — sat its checkboxes at x=0 of the stripe. Head and body are
|
|
72
|
+
* inset together, or the columns stop lining up. */
|
|
89
73
|
.ui-table--zebra th:first-child,
|
|
90
74
|
.ui-table--zebra td:first-child { padding-left: var(--space-3); }
|
|
91
75
|
.ui-table--zebra th:last-child,
|
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"] {
|
|
@@ -66,25 +62,15 @@
|
|
|
66
62
|
--purple: #1156b8;
|
|
67
63
|
--purple-light: #1568d6;
|
|
68
64
|
--purple-mid: #1560c8;
|
|
69
|
-
/* Deepened for WCAG AA on the raised grey panel and its wash
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
which is what this cell ships — an earlier version of this comment quoted
|
|
73
|
-
them at the 0.05 the wash briefly carried, which flattered both inks.
|
|
74
|
-
Light Ocean is the one light accent #96 never deepened, because it happened
|
|
75
|
-
to clear plain white. Same kind of move, smaller: hue and chroma unchanged
|
|
76
|
-
in OKLCH (H 258, C 0.185) and four points of lightness down, where #96's
|
|
77
|
-
two took about ten points and gave up 15.9% of the chroma on Phoenix and
|
|
78
|
-
17.6% on Emerald. See issues #96 and #157. */
|
|
65
|
+
/* Deepened for WCAG AA on the raised grey panel and its wash — hue and chroma held
|
|
66
|
+
in OKLCH (H 258, C 0.185), four points of lightness down. The washed figures are
|
|
67
|
+
read at the 0.10 alpha below, which is what this cell ships. See issues #96, #157. */
|
|
79
68
|
--accent: #005bc8;
|
|
80
69
|
--accent-strong: var(--accent);
|
|
81
70
|
--accent-contrast: #ffffff;
|
|
82
|
-
/* Back to 0.10
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
wash does not have to be the thing that gives. The rgb stays --purple-light
|
|
86
|
-
rather than the accent — in light a glow tints --purple-light, which
|
|
87
|
-
stories/signal-contrast.test.js holds. See issue #157. */
|
|
71
|
+
/* Back to the 0.10 this cell carried before #157: deepening the ink closed the pair,
|
|
72
|
+
so the wash does not have to give. The rgb stays --purple-light rather than the
|
|
73
|
+
accent — in light a glow tints --purple-light, which signal-contrast.test.js holds. */
|
|
88
74
|
--glow-purple: rgba(21, 104, 214, 0.10);
|
|
89
75
|
--grad-from: #1568d6;
|
|
90
76
|
--grad-to: #17a2c0;
|