@apliteni/apliteni-ui 0.30.0 → 0.32.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 +23 -28
- package/package.json +1 -1
- package/react/README.md +7 -9
- package/react/dist/index.css +10 -2
- package/react/dist/index.d.ts +25 -1
- package/react/dist/index.js +136 -92
- package/src/components/index.js +2 -2
- package/src/components/pagination.js +9 -31
- package/src/components/stat.js +75 -0
- package/src/index.css +2 -0
- package/src/index.js +1 -0
- package/src/inline.js +4 -0
- package/src/styles/button.css +6 -5
- package/src/styles/callout.css +7 -4
- package/src/styles/card.css +12 -19
- package/src/styles/command-palette.css +6 -5
- package/src/styles/confirm.css +10 -19
- package/src/styles/drawer.css +6 -6
- package/src/styles/dropdown.css +19 -12
- package/src/styles/feedback.css +11 -13
- package/src/styles/field-zoom.css +20 -0
- package/src/styles/input.css +17 -2
- package/src/styles/layout.css +8 -3
- package/src/styles/motion.css +3 -3
- package/src/styles/pagination.css +1 -0
- package/src/styles/segmented.css +1 -2
- package/src/styles/stat.css +121 -0
- package/src/styles/success.css +2 -2
- package/src/styles/tooltip.css +3 -4
- package/src/styles/topbar.css +8 -8
- package/src/tokens/accents.css +7 -6
- package/src/tokens/tokens.css +51 -38
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Stat band — a row of key figures, as an HTML string.
|
|
2
|
+
// why: docs/specification.md#stat-bands
|
|
3
|
+
//
|
|
4
|
+
// A band is a <dl>: each figure is a <div> holding its label as a <dt> and its
|
|
5
|
+
// value, change and trend as <dd>s, which is the grouping HTML allows inside a
|
|
6
|
+
// description list and the one a screen reader reads as "term, then its values".
|
|
7
|
+
import { esc } from './index.js';
|
|
8
|
+
import { icon } from '../assets/icons.js';
|
|
9
|
+
|
|
10
|
+
export const STAT_VARIANTS = ['band', 'tiles', 'open'];
|
|
11
|
+
export const STAT_TONES = ['good', 'bad', 'neutral'];
|
|
12
|
+
|
|
13
|
+
const cx = (...a) => a.filter(Boolean).join(' ');
|
|
14
|
+
let seq = 0;
|
|
15
|
+
|
|
16
|
+
// The arrow is read off the sign the caller printed, so the figure and the
|
|
17
|
+
// glyph beside it cannot disagree. U+2212, the en dash and an accounting
|
|
18
|
+
// bracket are negatives too: a formatter that typesets its figures uses them.
|
|
19
|
+
const directionOf = (text) => {
|
|
20
|
+
const t = text.trim();
|
|
21
|
+
return /^[-−–(]/.test(t) ? 'down' : /^\+/.test(t) ? 'up' : 'flat';
|
|
22
|
+
};
|
|
23
|
+
const GLYPH = { up: 'arrowUp', down: 'arrowDown', flat: 'minus' };
|
|
24
|
+
const hasChange = (delta) => delta && delta.value != null && delta.value !== '';
|
|
25
|
+
|
|
26
|
+
const deltaHtml = (delta, basisId) => {
|
|
27
|
+
if (!delta) return '';
|
|
28
|
+
if (!hasChange(delta)) {
|
|
29
|
+
return `<dd class="ui-stat__delta ui-stat__delta--none">${esc(delta.none || 'No earlier figure')}</dd>`;
|
|
30
|
+
}
|
|
31
|
+
const text = String(delta.value);
|
|
32
|
+
const dir = GLYPH[delta.direction] ? delta.direction : directionOf(text);
|
|
33
|
+
const own = delta.basis ? ` <span class="ui-stat__basis">${esc(delta.basis)}</span>` : '';
|
|
34
|
+
const describedby = !delta.basis && basisId ? ` aria-describedby="${basisId}"` : '';
|
|
35
|
+
return `<dd class="ui-stat__delta"${describedby}>${icon(GLYPH[dir])}<span class="ui-stat__change">${esc(text)}</span>${own}</dd>`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// `tone` says whether the change is good news, and nothing else. It is never
|
|
39
|
+
// inferred from the direction: costs going up and unclassified rows going down
|
|
40
|
+
// are both real, and a band that paints every rise green is editorialising.
|
|
41
|
+
// A figure with no change has no news to colour.
|
|
42
|
+
const figure = ({ label = '', value = '', delta, trend = '' }, tile, basisId) => {
|
|
43
|
+
const tone = hasChange(delta) && STAT_TONES.includes(delta.tone) && delta.tone !== 'neutral' ? delta.tone : '';
|
|
44
|
+
const cls = cx('ui-stat', tone && `ui-stat--${tone}`, tile && 'ui-card ui-card--pad-sm');
|
|
45
|
+
// `trend` is trusted markup — an <svg> the caller drew. The kit sizes and
|
|
46
|
+
// colours it and draws no chart of its own.
|
|
47
|
+
return `<div class="${cls}">`
|
|
48
|
+
+ `<dt class="ui-stat__label">${esc(label)}</dt>`
|
|
49
|
+
+ `<dd class="ui-stat__value">${esc(value)}</dd>`
|
|
50
|
+
+ deltaHtml(delta, basisId)
|
|
51
|
+
+ (trend ? `<dd class="ui-stat__trend">${trend}</dd>` : '')
|
|
52
|
+
+ '</div>';
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// `basis` is the band's caption, said once above the figures: what every change
|
|
56
|
+
// is measured against, or on a band with no changes, what the figures cover. A
|
|
57
|
+
// figure measured against something else carries its own `delta.basis`, printed
|
|
58
|
+
// beside the change.
|
|
59
|
+
//
|
|
60
|
+
// The caption comes before the list, the way a table's <caption> does, in every
|
|
61
|
+
// layout. It is one statement about all the figures, so it is read before them
|
|
62
|
+
// and it sits outside every one of them — under a row of tiles it would read as
|
|
63
|
+
// a note on the last card. why: docs/specification.md#stat-bands
|
|
64
|
+
export function statBand({ stats = [], variant = 'tiles', basis = '', label, id } = {}) {
|
|
65
|
+
const v = STAT_VARIANTS.includes(variant) ? variant : 'tiles';
|
|
66
|
+
const base = id ? esc(id) : `ui-stats-${++seq}`;
|
|
67
|
+
const basisId = basis ? `${base}-basis` : '';
|
|
68
|
+
const cls = cx('ui-stats', `ui-stats--${v}`, v === 'band' && 'ui-card');
|
|
69
|
+
const named = label ? ` role="group" aria-label="${esc(label)}"` : '';
|
|
70
|
+
const items = stats.map((s) => figure(s, v === 'tiles', basisId)).join('');
|
|
71
|
+
return `<div class="${cls}"${named}>`
|
|
72
|
+
+ (basis ? `<p class="ui-stats__basis" id="${basisId}">${esc(basis)}</p>` : '')
|
|
73
|
+
+ `<dl class="ui-stats__list">${items}</dl>`
|
|
74
|
+
+ '</div>';
|
|
75
|
+
}
|
package/src/index.css
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
@import "./tokens/accents.css";
|
|
17
17
|
@import "./styles/base.css";
|
|
18
18
|
@import "./styles/reduced-motion.css";
|
|
19
|
+
@import "./styles/field-zoom.css";
|
|
19
20
|
@import "./styles/motion.css";
|
|
20
21
|
@import "./styles/button.css";
|
|
21
22
|
@import "./styles/card.css";
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
@import "./styles/command-palette.css";
|
|
33
34
|
@import "./styles/table.css";
|
|
34
35
|
@import "./styles/pagination.css";
|
|
36
|
+
@import "./styles/stat.css";
|
|
35
37
|
@import "./styles/empty.css";
|
|
36
38
|
@import "./styles/callout.css";
|
|
37
39
|
@import "./styles/code.css";
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export * from './components/toasts.js';
|
|
|
17
17
|
export * from './components/success.js';
|
|
18
18
|
export * from './components/loading.js';
|
|
19
19
|
export * from './components/pagination.js';
|
|
20
|
+
export * from './components/stat.js';
|
|
20
21
|
export * from './assets/icons.js';
|
|
21
22
|
export * from './assets/brand.js';
|
|
22
23
|
export * from './motion.js';
|
package/src/inline.js
CHANGED
|
@@ -29,6 +29,7 @@ export const topbarCss = read('styles/topbar.css');
|
|
|
29
29
|
export const styles = {
|
|
30
30
|
base: baseCss,
|
|
31
31
|
reducedMotion: read('styles/reduced-motion.css'),
|
|
32
|
+
fieldZoom: read('styles/field-zoom.css'),
|
|
32
33
|
motion: read('styles/motion.css'),
|
|
33
34
|
button: read('styles/button.css'),
|
|
34
35
|
card: read('styles/card.css'),
|
|
@@ -45,6 +46,7 @@ export const styles = {
|
|
|
45
46
|
commandPalette: read('styles/command-palette.css'),
|
|
46
47
|
table: read('styles/table.css'),
|
|
47
48
|
pagination: read('styles/pagination.css'),
|
|
49
|
+
stat: read('styles/stat.css'),
|
|
48
50
|
empty: read('styles/empty.css'),
|
|
49
51
|
callout: read('styles/callout.css'),
|
|
50
52
|
code: read('styles/code.css'),
|
|
@@ -61,6 +63,7 @@ export const cssText = [
|
|
|
61
63
|
tokensCss,
|
|
62
64
|
styles.base,
|
|
63
65
|
styles.reducedMotion,
|
|
66
|
+
styles.fieldZoom,
|
|
64
67
|
styles.motion,
|
|
65
68
|
styles.button,
|
|
66
69
|
styles.card,
|
|
@@ -77,6 +80,7 @@ export const cssText = [
|
|
|
77
80
|
styles.commandPalette,
|
|
78
81
|
styles.table,
|
|
79
82
|
styles.pagination,
|
|
83
|
+
styles.stat,
|
|
80
84
|
styles.empty,
|
|
81
85
|
styles.callout,
|
|
82
86
|
styles.code,
|
package/src/styles/button.css
CHANGED
|
@@ -102,11 +102,12 @@
|
|
|
102
102
|
border-color: transparent;
|
|
103
103
|
color: var(--disabled-ink-bare);
|
|
104
104
|
}
|
|
105
|
-
/* With no box, the ink is read on whatever is behind it
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
/* With no box, the ink is read on whatever is behind it, so the ghost takes an ink
|
|
106
|
+
set for the dullest ground: in dark 7.49 / 6.31 / 7.00 / 5.44 on --bg / --surface /
|
|
107
|
+
--surface-2 / --surface-3, in light 5.70 / 6.17 / 5.20 / 5.39. The enabled ghost's
|
|
108
|
+
--dim reads about 1.5 times that in both themes. #273 cut this ink to rescue two
|
|
109
|
+
dark pairs from under #220's floor; #295 re-picked --muted against the elevation
|
|
110
|
+
ladder and lifted both clear, so it is the margin now rather than the rescue.
|
|
110
111
|
|
|
111
112
|
#273 got here the long way. It first gave the ghost the flat disabled box
|
|
112
113
|
instead, on the claim that nothing would get less readable. That was false:
|
package/src/styles/callout.css
CHANGED
|
@@ -110,8 +110,12 @@
|
|
|
110
110
|
.ui-toast__timer { position: absolute; left: 0; bottom: 0; height: 3px; width: 100%; background: var(--toast-accent); opacity: 0.85; transform-origin: left; }
|
|
111
111
|
.ui-toast__timer.is-running { animation: ui-toast-timer var(--toast-dur, 5s) linear forwards; /* motion: ambient — the bar spends the toast's dismiss timer, which the caller sets */ }
|
|
112
112
|
|
|
113
|
-
/* style: soft — tinted surface
|
|
114
|
-
|
|
113
|
+
/* style: soft — tinted surface. The shadow used to be its edge, so it takes the
|
|
114
|
+
status-tinted hairline the outline style carries, at half the weight. #295 */
|
|
115
|
+
.ui-toast--soft {
|
|
116
|
+
background: var(--toast-glow);
|
|
117
|
+
border: 1px solid color-mix(in srgb, var(--toast-accent) 22%, transparent);
|
|
118
|
+
}
|
|
115
119
|
|
|
116
120
|
/* style: solid — the status at fill strength, inked with the pole opposite it.
|
|
117
121
|
The fill and the ink are one pair, taken together from --signal-solid-*: dark
|
|
@@ -124,7 +128,6 @@
|
|
|
124
128
|
--toast-ink: var(--signal-solid-ink);
|
|
125
129
|
background: var(--toast-solid);
|
|
126
130
|
color: var(--toast-ink);
|
|
127
|
-
box-shadow: var(--shadow-lg);
|
|
128
131
|
}
|
|
129
132
|
.ui-toast--solid .ui-toast__title { color: var(--toast-ink); }
|
|
130
133
|
/* The body copy is the quieter of the two, but it stays quiet by dilution the
|
|
@@ -144,7 +147,7 @@
|
|
|
144
147
|
.ui-toast--solid .ui-toast__timer { background: color-mix(in srgb, var(--toast-ink) 55%, transparent); }
|
|
145
148
|
|
|
146
149
|
/* style: outline — elevated card with a status-tinted border */
|
|
147
|
-
.ui-toast--outline { background: var(--bg-elevated);
|
|
150
|
+
.ui-toast--outline { background: var(--bg-elevated); border: 1px solid color-mix(in srgb, var(--toast-accent) 40%, transparent); }
|
|
148
151
|
|
|
149
152
|
/* compact — single line, no body text */
|
|
150
153
|
.ui-toast--compact { align-items: center; padding-top: 10px; padding-bottom: 10px; }
|
package/src/styles/card.css
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
|
-
* Card — .ui-card (filled surface
|
|
3
|
-
* surface + radius, never outlines)
|
|
2
|
+
* Card — .ui-card (a filled surface one step off the page, edged by a hairline)
|
|
4
3
|
* Rows: .ui-card__row / headers: .ui-card__title / .ui-card__sub
|
|
5
4
|
* ========================================================================== */
|
|
6
5
|
|
|
6
|
+
/* Flat since #284 and edged in both themes since #295: the surface is the step, the
|
|
7
|
+
line is what makes it legible. why: docs/specification.md#elevation */
|
|
7
8
|
.ui-card {
|
|
8
9
|
background: var(--surface);
|
|
10
|
+
border: 1px solid var(--border);
|
|
9
11
|
border-radius: var(--radius-xl);
|
|
10
12
|
padding: var(--space-6) 26px;
|
|
11
13
|
position: relative;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
/* Light theme is an all-white app, so a filled card can't rely on a darker-
|
|
15
|
-
than-page surface the way the dark deck does — a hairline border delineates
|
|
16
|
-
it instead. Flat, no drop shadow (#284). `none` rather than no declaration:
|
|
17
|
-
at (0,3,0) it outranks .ui-card--accent's inset ring, which light never showed. */
|
|
18
|
-
:root[data-theme="light"] .ui-card {
|
|
19
|
-
border: 1px solid var(--border);
|
|
20
|
-
box-shadow: none;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
16
|
/* A table wider than its card scrolls INSIDE the card instead of bleeding past
|
|
24
17
|
the rounded corners / clipping its last column. Clipping to the card's
|
|
25
18
|
border-box also keeps zebra stripes from escaping the radius. */
|
|
@@ -32,10 +25,11 @@
|
|
|
32
25
|
.ui-card--pad-sm { padding: var(--space-5); }
|
|
33
26
|
.ui-card--pad-lg { padding: var(--space-8) var(--space-10); }
|
|
34
27
|
|
|
35
|
-
/* An accent-tinted card, e.g. a highlighted plan / success surface
|
|
28
|
+
/* An accent-tinted card, e.g. a highlighted plan / success surface. The accent edge is
|
|
29
|
+
the card's own hairline re-coloured: an inset ring over it would draw two. #295 */
|
|
36
30
|
.ui-card--accent {
|
|
37
31
|
background: color-mix(in srgb, var(--accent) 9%, var(--surface));
|
|
38
|
-
|
|
32
|
+
border-color: color-mix(in srgb, var(--accent) 22%, transparent);
|
|
39
33
|
}
|
|
40
34
|
.ui-card--live {
|
|
41
35
|
background: color-mix(in srgb, var(--green) 10%, var(--surface));
|
|
@@ -44,16 +38,15 @@
|
|
|
44
38
|
/* Interactive card (link / button semantics) */
|
|
45
39
|
.ui-card--interactive {
|
|
46
40
|
cursor: pointer;
|
|
47
|
-
/* Written as a <button>, the card would take the browser's frame, centred text
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
border: 0;
|
|
41
|
+
/* Written as a <button>, the card would take the browser's frame, centred text and
|
|
42
|
+
`font: 400 13.3333px Arial` — a shorthand, so `font: inherit` and not the family
|
|
43
|
+
longhand. .ui-card states the background and, since #295, the border, so a
|
|
44
|
+
`border: 0` here would only delete the hairline. #251 */
|
|
52
45
|
text-align: left;
|
|
53
46
|
font: inherit;
|
|
54
47
|
transition: transform var(--dur-fast) var(--ease);
|
|
55
48
|
}
|
|
56
|
-
/* The lift alone
|
|
49
|
+
/* The lift alone: the kit casts no shadow anywhere (#295), and cards are flat (#284). */
|
|
57
50
|
.ui-card--interactive:hover { transform: translateY(-2px); }
|
|
58
51
|
|
|
59
52
|
/* card() emits the title as an h2, so the text face is named here rather than
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* why: docs/specification.md#the-command-palette */
|
|
16
16
|
|
|
17
17
|
.ui-cmdk {
|
|
18
|
-
|
|
19
|
-
--cmdk-
|
|
18
|
+
/* The floating step; nothing casts. why: docs/specification.md#elevation */
|
|
19
|
+
--cmdk-surface: var(--bg-elevated);
|
|
20
20
|
--cmdk-scrim: var(--scrim);
|
|
21
21
|
--cmdk-w: 560px;
|
|
22
22
|
--cmdk-top: 12vh;
|
|
@@ -71,7 +71,6 @@
|
|
|
71
71
|
background: var(--cmdk-surface);
|
|
72
72
|
border: 1px solid var(--border);
|
|
73
73
|
border-radius: var(--radius-lg);
|
|
74
|
-
box-shadow: var(--cmdk-shadow);
|
|
75
74
|
overflow: hidden;
|
|
76
75
|
opacity: 0;
|
|
77
76
|
transform: translate(-50%, -8px);
|
|
@@ -80,7 +79,7 @@
|
|
|
80
79
|
transform var(--dur-med) var(--ease);
|
|
81
80
|
}
|
|
82
81
|
.ui-cmdk.is-open .ui-cmdk__panel { opacity: 1; transform: translate(-50%, 0); }
|
|
83
|
-
.ui-cmdk__panel:focus-visible { outline: none; box-shadow: var(--
|
|
82
|
+
.ui-cmdk__panel:focus-visible { outline: none; box-shadow: var(--ring); }
|
|
84
83
|
|
|
85
84
|
/* -- The text box --------------------------------------------------------- */
|
|
86
85
|
|
|
@@ -250,7 +249,9 @@
|
|
|
250
249
|
padding: 2px 5px;
|
|
251
250
|
border: 1px solid var(--border);
|
|
252
251
|
border-radius: var(--radius-xs);
|
|
253
|
-
|
|
252
|
+
/* A key cap reads as raised, so it takes the step above the panel rather than
|
|
253
|
+
the card step below it. why: docs/specification.md#elevation */
|
|
254
|
+
background: var(--surface-3);
|
|
254
255
|
font-family: var(--font-sans);
|
|
255
256
|
font-size: var(--text-xs);
|
|
256
257
|
line-height: 1.5;
|
package/src/styles/confirm.css
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
/* Confirm — the kit's confirmation dialog: a centred panel over a scrim, for the
|
|
2
|
-
* question a destructive action has to ask.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `visibility` is SWITCHED rather than transitioned: transitioned, it is still
|
|
6
|
-
* `hidden` in the frame the class lands, so openConfirm()'s focus() would have
|
|
7
|
-
* nothing to focus. The root owns the switch; panel and scrim inherit it.
|
|
8
|
-
*
|
|
9
|
-
* Local tokens so the panel and scrim re-theme cleanly:
|
|
10
|
-
* --confirm-surface panel background --confirm-shadow panel elevation
|
|
11
|
-
* --confirm-scrim backdrop colour
|
|
12
|
-
*
|
|
2
|
+
* question a destructive action has to ask. Shares its scrim and focus trap with
|
|
3
|
+
* the drawer (see overlay.js). Local tokens: --confirm-surface, --confirm-scrim.
|
|
13
4
|
* why: docs/specification.md#motion */
|
|
14
5
|
|
|
15
6
|
.ui-confirm {
|
|
16
|
-
|
|
17
|
-
--confirm-
|
|
7
|
+
/* The floating step; nothing casts. why: docs/specification.md#elevation */
|
|
8
|
+
--confirm-surface: var(--bg-elevated);
|
|
18
9
|
--confirm-scrim: var(--scrim);
|
|
19
10
|
--confirm-w: var(--panel-md);
|
|
20
11
|
|
|
@@ -26,10 +17,11 @@
|
|
|
26
17
|
z-index: calc(var(--z-overlay) + 2);
|
|
27
18
|
pointer-events: none; /* container never blocks; scrim/panel opt back in */
|
|
28
19
|
|
|
29
|
-
/*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
20
|
+
/* SWITCHED, not transitioned: transitioned, the panel is still `hidden` in the
|
|
21
|
+
frame the class lands and openConfirm()'s focus() has nothing to focus. Only
|
|
22
|
+
the hide is timed, so the panel is on screen while it fades out. `linear` and
|
|
23
|
+
not var(--ease): visibility is discrete, and a curve leaving [0, 1] flips it
|
|
24
|
+
mid-fade. The root owns the switch; panel and scrim inherit it. */
|
|
33
25
|
visibility: hidden;
|
|
34
26
|
transition: visibility var(--dur-med) linear;
|
|
35
27
|
}
|
|
@@ -72,7 +64,6 @@
|
|
|
72
64
|
background: var(--confirm-surface);
|
|
73
65
|
border: 1px solid var(--border);
|
|
74
66
|
border-radius: var(--radius-lg);
|
|
75
|
-
box-shadow: var(--confirm-shadow);
|
|
76
67
|
opacity: 0;
|
|
77
68
|
transform: translateY(8px);
|
|
78
69
|
transition:
|
|
@@ -80,7 +71,7 @@
|
|
|
80
71
|
transform var(--dur-med) var(--ease);
|
|
81
72
|
}
|
|
82
73
|
.ui-confirm.is-open .ui-confirm__panel { opacity: 1; transform: none; }
|
|
83
|
-
.ui-confirm__panel:focus-visible { outline: none; box-shadow: var(--
|
|
74
|
+
.ui-confirm__panel:focus-visible { outline: none; box-shadow: var(--ring); }
|
|
84
75
|
|
|
85
76
|
/* -- Slots ----------------------------------------------------------------- */
|
|
86
77
|
/* The question and the answers keep their size; the consequence between them is
|
package/src/styles/drawer.css
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
* (reduced-motion.css) makes the slide and the fade instant.
|
|
7
7
|
*
|
|
8
8
|
* Local tokens (surface/shadow/scrim) so the panel + scrim re-theme cleanly:
|
|
9
|
-
* --drawer-surface panel background
|
|
9
|
+
* --drawer-surface panel background
|
|
10
10
|
* --drawer-scrim backdrop colour (--scrim already re-themes per theme)
|
|
11
11
|
* ========================================================================== */
|
|
12
12
|
|
|
13
13
|
.ui-drawer {
|
|
14
|
-
--drawer
|
|
15
|
-
|
|
14
|
+
/* The floating step; the edge is the hairline each --drawer--<edge> rule below draws.
|
|
15
|
+
why: docs/specification.md#elevation */
|
|
16
|
+
--drawer-surface: var(--bg-elevated);
|
|
16
17
|
--drawer-scrim: var(--scrim);
|
|
17
18
|
--drawer-w: var(--panel-md); /* left|right panels: width */
|
|
18
19
|
--drawer-h: 45vh; /* top|bottom panels: height */
|
|
@@ -57,10 +58,9 @@
|
|
|
57
58
|
display: flex;
|
|
58
59
|
flex-direction: column;
|
|
59
60
|
background: var(--drawer-surface);
|
|
60
|
-
box-shadow: var(--drawer-shadow);
|
|
61
61
|
transition: transform var(--dur-med) var(--ease);
|
|
62
62
|
}
|
|
63
|
-
.ui-drawer__panel:focus-visible { outline: none; box-shadow: var(--
|
|
63
|
+
.ui-drawer__panel:focus-visible { outline: none; box-shadow: var(--ring); }
|
|
64
64
|
.ui-drawer.is-open .ui-drawer__panel { transform: none; }
|
|
65
65
|
|
|
66
66
|
/* -- Edge anchoring + slide-in direction ---------------------------------- */
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
background var(--dur-fast) var(--ease),
|
|
150
150
|
border-color var(--dur-fast) var(--ease);
|
|
151
151
|
}
|
|
152
|
-
.ui-drawer__close:hover { color: var(--strong); background: var(--surface); }
|
|
152
|
+
.ui-drawer__close:hover { color: var(--strong); background: var(--surface-3); }
|
|
153
153
|
.ui-drawer__close:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
|
|
154
154
|
.ui-drawer__close svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.1; }
|
|
155
155
|
|
package/src/styles/dropdown.css
CHANGED
|
@@ -57,11 +57,12 @@
|
|
|
57
57
|
top: calc(100% + var(--ui-dropdown-gap));
|
|
58
58
|
left: 0;
|
|
59
59
|
min-width: 240px;
|
|
60
|
-
|
|
60
|
+
/* The floating step, not the sunken one — a panel held open over a card cannot be
|
|
61
|
+
painted the colour of an inset. why: docs/specification.md#elevation */
|
|
62
|
+
background: var(--bg-elevated);
|
|
61
63
|
border: 1px solid var(--border);
|
|
62
64
|
border-radius: var(--radius-lg);
|
|
63
65
|
padding: 6px;
|
|
64
|
-
box-shadow: var(--shadow-lg);
|
|
65
66
|
opacity: 0;
|
|
66
67
|
visibility: hidden;
|
|
67
68
|
transform: translateY(-6px);
|
|
@@ -126,8 +127,9 @@
|
|
|
126
127
|
color: var(--text);
|
|
127
128
|
transition: background var(--dur-fast) var(--ease);
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
-
.ui-dropdown__item:
|
|
130
|
+
/* A row in a raised panel lifts: the step above it, never the card step below. why: docs/specification.md#elevation */
|
|
131
|
+
.ui-dropdown__item:hover { background: var(--surface-3); }
|
|
132
|
+
.ui-dropdown__item:focus-visible { outline: none; background: var(--surface-3); box-shadow: var(--ring); }
|
|
131
133
|
.ui-dropdown__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
132
134
|
.ui-dropdown__label { font-family: var(--font-sans); font-size: 12.5px; color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
|
|
133
135
|
.ui-dropdown__desc { font-size: 11px; color: var(--muted); line-height: 1.35; }
|
|
@@ -153,10 +155,15 @@
|
|
|
153
155
|
margin-left: auto;
|
|
154
156
|
flex: none;
|
|
155
157
|
color: var(--muted);
|
|
156
|
-
|
|
158
|
+
/* A chip is the top step. why: docs/specification.md#elevation */
|
|
159
|
+
background: var(--surface-3);
|
|
157
160
|
}
|
|
158
161
|
.ui-dropdown__badge.is-live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
|
|
159
|
-
|
|
162
|
+
/* The accent counter keeps the badge's flat surface and changes only the ink — the shape
|
|
163
|
+
src/styles/nav.css:128 `.ui-nav__item.is-active .ui-nav__badge.is-accent` already holds.
|
|
164
|
+
The accent wash goes on a base surface, never a raised one, and #295 made this panel a
|
|
165
|
+
raised one. why: docs/specification.md#elevation */
|
|
166
|
+
.ui-dropdown__badge.is-accent { color: var(--accent); background: var(--surface-3); }
|
|
160
167
|
|
|
161
168
|
/* Disabled + danger rows */
|
|
162
169
|
.ui-dropdown__item.is-disabled { color: var(--disabled-ink); cursor: not-allowed; pointer-events: none; }
|
|
@@ -209,12 +216,16 @@
|
|
|
209
216
|
}
|
|
210
217
|
/* 16px at 2.4 paints 1.6px, over the 1.5px floor stories/glyph-stroke.test.js holds. */
|
|
211
218
|
.ui-dropdown__search-ic svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2.4; }
|
|
219
|
+
/* 12.5px with a mouse only. On a touch screen the touch-zoom net in
|
|
220
|
+
src/styles/field-zoom.css takes this field, and every other field the kit
|
|
221
|
+
ships, to 16px. why: docs/specification.md#a-field-is-16px-on-a-touch-screen */
|
|
212
222
|
.ui-dropdown__search-input {
|
|
213
223
|
width: 100%;
|
|
214
224
|
font: inherit;
|
|
215
225
|
font-size: 12.5px;
|
|
216
226
|
color: var(--strong);
|
|
217
|
-
|
|
227
|
+
/* A field is the sunken step, the same one .ui-input takes. why: docs/specification.md#elevation */
|
|
228
|
+
background: var(--surface-2);
|
|
218
229
|
border: 1px solid var(--border);
|
|
219
230
|
border-radius: var(--radius-sm);
|
|
220
231
|
padding: 8px 12px 8px 34px;
|
|
@@ -223,10 +234,6 @@
|
|
|
223
234
|
min-height: 38px;
|
|
224
235
|
transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
|
|
225
236
|
}
|
|
226
|
-
/* iOS Safari zooms the page into a focused field under 16px, and opening the
|
|
227
|
-
panel focuses this one. A real 16px, not a scaled one: the zoom reads the
|
|
228
|
-
computed size, and a transform would shrink the ring and border with it. */
|
|
229
|
-
@media (pointer: coarse) { .ui-dropdown__search-input { font-size: 16px; } }
|
|
230
237
|
.ui-dropdown__search-input::placeholder { color: var(--muted); }
|
|
231
238
|
.ui-dropdown__search-input:hover { border-color: var(--border-strong); }
|
|
232
239
|
.ui-dropdown__search-input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
|
|
@@ -238,7 +245,7 @@
|
|
|
238
245
|
|
|
239
246
|
/* The row Enter would pick. Focus stays in the field, which carries the ring,
|
|
240
247
|
so this row takes the hover fill and a bar rather than a second ring. */
|
|
241
|
-
.ui-dropdown__item.is-active { background: var(--surface); box-shadow: inset 2px 0 0 var(--accent); }
|
|
248
|
+
.ui-dropdown__item.is-active { background: var(--surface-3); box-shadow: inset 2px 0 0 var(--accent); }
|
|
242
249
|
|
|
243
250
|
/* No match — a nudge and no action, per Guidelines / Microcopy. Empty while
|
|
244
251
|
anything matches, so it takes no room. */
|
package/src/styles/feedback.css
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
:root {
|
|
11
11
|
--ui-fb-pill-grad: linear-gradient(135deg, var(--accent), var(--accent-strong));
|
|
12
|
-
--ui-fb-pill-glow: color-mix(in srgb, var(--accent) 45%, transparent);
|
|
13
12
|
--ui-fb-sel: color-mix(in srgb, var(--accent) 22%, transparent);
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -34,23 +33,21 @@
|
|
|
34
33
|
border: 0; text-align: center; font: inherit;
|
|
35
34
|
font-family: var(--font-sans); font-size: 13px; font-weight: var(--weight-semibold); letter-spacing: .01em;
|
|
36
35
|
padding: 9px 17px 9px 15px; border-radius: 999px; cursor: pointer; white-space: nowrap; overflow: hidden;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
/* The pill cast two layers of ink and an accent glow until #295, and grew both on
|
|
37
|
+
hover. Nothing casts now, and it takes no hairline either: it is the accent at full
|
|
38
|
+
fill strength on the page, found the way the solid toast is found. What is left is an
|
|
39
|
+
inset sheen — gloss on the surface, not a shadow under it — so the hover is the travel
|
|
40
|
+
alone and the box-shadow it transitioned went with it.
|
|
41
|
+
why: docs/specification.md#elevation */
|
|
42
|
+
box-shadow: inset 0 1px 0 color-mix(in srgb, var(--sheen) 28%, transparent);
|
|
40
43
|
transition:
|
|
41
44
|
transform var(--dur-fast) var(--ease-spring),
|
|
42
|
-
opacity var(--dur-fast) var(--ease)
|
|
43
|
-
box-shadow var(--dur-fast) var(--ease);
|
|
45
|
+
opacity var(--dur-fast) var(--ease);
|
|
44
46
|
}
|
|
45
47
|
.ui-fbpill.show { opacity: 1; pointer-events: auto; transform: translate(-50%, -100%) translateY(-9px) scale(1); }
|
|
46
|
-
.ui-fbpill:hover {
|
|
47
|
-
box-shadow: 0 14px 34px var(--ui-fb-pill-glow),
|
|
48
|
-
0 3px 9px color-mix(in srgb, var(--shadow-ink) 30%, transparent),
|
|
49
|
-
inset 0 1px 0 color-mix(in srgb, var(--sheen) 34%, transparent);
|
|
50
|
-
}
|
|
51
48
|
.ui-fbpill.show:hover { transform: translate(-50%, -100%) translateY(-12px) scale(1.04); }
|
|
52
49
|
.ui-fbpill:active { transform: translate(-50%, -100%) translateY(-8px) scale(.98); }
|
|
53
|
-
.ui-fbpill svg { width: 16px; height: 16px; stroke-width: 2.4; color: currentColor;
|
|
50
|
+
.ui-fbpill svg { width: 16px; height: 16px; stroke-width: 2.4; color: currentColor; }
|
|
54
51
|
.ui-fbpill::before {
|
|
55
52
|
content: ""; position: absolute; top: 0; bottom: 0; left: -60%; width: 45%;
|
|
56
53
|
background: linear-gradient(100deg, transparent, color-mix(in srgb, var(--sheen) 55%, transparent), transparent); transform: skewX(-18deg);
|
|
@@ -70,7 +67,8 @@
|
|
|
70
67
|
.ui-fbcomposer {
|
|
71
68
|
position: fixed; z-index: 71; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(14px);
|
|
72
69
|
width: min(520px, calc(100vw - 32px)); background: var(--bg-elevated); border-radius: 18px;
|
|
73
|
-
|
|
70
|
+
/* The shadow was its only edge until #295; now it takes the hairline. */
|
|
71
|
+
border: 1px solid var(--border); opacity: 0; pointer-events: none;
|
|
74
72
|
transition: opacity var(--dur-med) var(--ease), transform var(--dur-med) var(--ease); overflow: hidden;
|
|
75
73
|
}
|
|
76
74
|
.ui-fbcomposer.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* apliteni-ui — touch-zoom net. iOS Safari zooms the page into a focused field
|
|
2
|
+
* whose text is under 16px and does not zoom back out, so under (pointer: coarse)
|
|
3
|
+
* every field the kit ships is 16px. Element selectors rather than kit classes,
|
|
4
|
+
* and !important for the reason reduced-motion.css takes it; left out are the
|
|
5
|
+
* controls with nothing to type into, which never zoom.
|
|
6
|
+
*
|
|
7
|
+
* The two costs, argued in the spec below: a 16px scaled back down with a
|
|
8
|
+
* transform still zooms, and this is a flat size rather than a floor, so a host
|
|
9
|
+
* field designed ABOVE 16px is shrunk here. The override recipe is there too.
|
|
10
|
+
*
|
|
11
|
+
* why: docs/specification.md#a-field-is-16px-on-a-touch-screen */
|
|
12
|
+
|
|
13
|
+
@media (pointer: coarse) {
|
|
14
|
+
input:not(:where(
|
|
15
|
+
[type="button"], [type="checkbox"], [type="color"], [type="file"], [type="hidden"],
|
|
16
|
+
[type="image"], [type="radio"], [type="range"], [type="reset"], [type="submit"]
|
|
17
|
+
)),
|
|
18
|
+
select,
|
|
19
|
+
textarea { font-size: 16px !important; }
|
|
20
|
+
}
|
package/src/styles/input.css
CHANGED
|
@@ -80,14 +80,24 @@
|
|
|
80
80
|
}
|
|
81
81
|
.ui-input-group__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 2.2; }
|
|
82
82
|
|
|
83
|
+
/* The chevron is a data URI, and a data URI cannot read a token: `var()` does not
|
|
84
|
+
substitute inside url() and `currentColor` does not cross into the image's own
|
|
85
|
+
document. So the ink is written twice, once per theme, each the theme's --muted —
|
|
86
|
+
which is the colour every other quiet glyph on a field takes. It was one frozen
|
|
87
|
+
hex until #295, dark --muted's old value, which the ladder's re-pick left behind
|
|
88
|
+
at 2.49:1 on the light field. Held by stories/colour-tokens.test.js.
|
|
89
|
+
why: docs/specification.md#colour-and-contrast */
|
|
83
90
|
.ui-select {
|
|
84
91
|
appearance: none;
|
|
85
|
-
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%
|
|
92
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a29db6' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
86
93
|
background-repeat: no-repeat;
|
|
87
94
|
background-position: right 15px center;
|
|
88
95
|
padding-right: 40px;
|
|
89
96
|
cursor: pointer;
|
|
90
97
|
}
|
|
98
|
+
:root[data-theme="light"] .ui-select {
|
|
99
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235c6270' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
100
|
+
}
|
|
91
101
|
|
|
92
102
|
/* Checkbox / radio — custom, token-driven */
|
|
93
103
|
.ui-check {
|
|
@@ -178,8 +188,13 @@
|
|
|
178
188
|
width: calc(var(--sw-h) - 6px);
|
|
179
189
|
height: calc(var(--sw-h) - 6px);
|
|
180
190
|
background: var(--control-knob);
|
|
191
|
+
/* --shadow-sm was the knob's only edge until #295, and a white knob on a light
|
|
192
|
+
off-track has 1.20:1 to spare. --border-strong and not --border, which measures
|
|
193
|
+
1.03:1 against that track and draws nothing — the checkbox above edges itself
|
|
194
|
+
the same way. why: docs/specification.md#elevation */
|
|
195
|
+
border: 1px solid var(--border-strong);
|
|
196
|
+
box-sizing: border-box;
|
|
181
197
|
border-radius: 50%;
|
|
182
|
-
box-shadow: var(--shadow-sm);
|
|
183
198
|
transition: transform var(--dur-med) var(--ease);
|
|
184
199
|
}
|
|
185
200
|
.ui-switch input:checked + .ui-switch__track { background: var(--accent); }
|
package/src/styles/layout.css
CHANGED
|
@@ -76,7 +76,10 @@
|
|
|
76
76
|
step down so the current one is the brightest whatever --accent is doing, and
|
|
77
77
|
the bar beside it carries the hue. A 3x17px bar on a 45x44 tile was the
|
|
78
78
|
smallest of three weak signals doing the most work, so it grows with the row. */
|
|
79
|
-
|
|
79
|
+
/* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
|
|
80
|
+
the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar —
|
|
81
|
+
stories/apps/shell-states.test.js:427 `a resting glyph is dimmer but still legible`. */
|
|
82
|
+
.ui-app__rail .ui-nav__ic svg { opacity: 0.66; }
|
|
80
83
|
.ui-app__rail .ui-nav__item.is-active .ui-nav__ic svg,
|
|
81
84
|
.ui-app__rail .ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke: currentColor; } /* motion: still — the glyph brightens with its row, whose own highlight already transitions */
|
|
82
85
|
.ui-app__rail .ui-nav__item.is-active::before { width: 3px; height: 62%; }
|
|
@@ -226,10 +229,12 @@
|
|
|
226
229
|
z-index: 1;
|
|
227
230
|
width: 100%;
|
|
228
231
|
max-width: var(--panel-md);
|
|
229
|
-
|
|
232
|
+
/* Floats over a spotlit page, so it takes the floating step and the hairline.
|
|
233
|
+
why: docs/specification.md#elevation */
|
|
234
|
+
background: var(--bg-elevated);
|
|
235
|
+
border: 1px solid var(--border);
|
|
230
236
|
border-radius: 22px;
|
|
231
237
|
padding: 38px 36px 32px;
|
|
232
|
-
box-shadow: var(--shadow-lg);
|
|
233
238
|
}
|
|
234
239
|
.ui-auth__brand { display: inline-flex; align-items: center; gap: 10px; margin-bottom: 26px; }
|
|
235
240
|
.ui-auth__brand span { font-size: 15px; font-weight: 600; color: var(--strong); }
|
package/src/styles/motion.css
CHANGED
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
to { opacity: 1; filter: blur(0); }
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/* -- Micro-interactions ----------------------------------------------------
|
|
67
|
+
/* -- Micro-interactions ----------------------------------------------------
|
|
68
|
+
* .m-lift is the travel only; it cast --shadow-md until #295. */
|
|
68
69
|
.m-lift {
|
|
69
|
-
transition: transform var(--dur-fast) var(--ease)
|
|
70
|
+
transition: transform var(--dur-fast) var(--ease);
|
|
70
71
|
}
|
|
71
72
|
.m-lift:hover {
|
|
72
73
|
transform: translateY(-3px);
|
|
73
|
-
box-shadow: var(--shadow-md);
|
|
74
74
|
}
|
|
75
75
|
.m-press {
|
|
76
76
|
transition: transform var(--dur-instant) var(--ease-sharp);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* --single one page of content: the size control alone, no steps
|
|
5
5
|
* The controls are .ui-btn ghost/sm and the size control is a .ui-select, so
|
|
6
6
|
* everything here is layout and the two states those sheets do not own.
|
|
7
|
+
* why: CONTRIBUTING.md#pagination-layout
|
|
7
8
|
* ========================================================================== */
|
|
8
9
|
|
|
9
10
|
.ui-pager {
|