@apliteni/apliteni-ui 0.61.2 → 0.63.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 +9 -0
- package/package.json +1 -1
- package/react/README.md +25 -1
- package/react/dist/index.css +254 -235
- package/react/dist/index.d.ts +68 -2
- package/react/dist/index.js +483 -334
- package/src/components/index.js +2 -2
- package/src/components/shell.js +10 -5
- package/src/styles/input.css +23 -1
- package/src/styles/layout.css +40 -10
- package/src/styles/nav.css +50 -11
package/src/components/index.js
CHANGED
|
@@ -210,8 +210,8 @@ export function select({ options = [], value, name, id, ariaLabel, disabled } =
|
|
|
210
210
|
}).join('');
|
|
211
211
|
return `<select class="ui-select"${id ? ` id="${esc(id)}"` : ''}${name ? ` name="${esc(name)}"` : ''}${ariaLabel ? ` aria-label="${esc(ariaLabel)}"` : ''}${disabled ? ' disabled' : ''}>${opts}</select>`;
|
|
212
212
|
}
|
|
213
|
-
export function checkbox({ label, checked, type = 'checkbox', name } = {}) {
|
|
214
|
-
return `<label class="ui-check"><input type="${esc(type)}"${name ? ` name="${esc(name)}"` : ''}${checked ? ' checked' : ''}><span>${label}</span></label>`;
|
|
213
|
+
export function checkbox({ label, checked, type = 'checkbox', name, disabled = false } = {}) {
|
|
214
|
+
return `<label class="ui-check"><input type="${esc(type)}"${name ? ` name="${esc(name)}"` : ''}${checked ? ' checked' : ''}${disabled ? ' disabled' : ''}><span>${label}</span></label>`;
|
|
215
215
|
}
|
|
216
216
|
// `label` becomes the input's accessible name (a bare switch has no visible text,
|
|
217
217
|
// so it needs one). Defaults to "Toggle" so a control is never left unlabelled.
|
package/src/components/shell.js
CHANGED
|
@@ -135,12 +135,17 @@ export function railCollapsed(cookies) {
|
|
|
135
135
|
// The name says what the press will do, and aria-expanded says what the rail is.
|
|
136
136
|
const railName = (collapsed) => (collapsed ? 'Expand sidebar' : 'Collapse sidebar');
|
|
137
137
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
138
|
+
// Lucide `panel-left-close`, unmodified: a frame, the seam at 9, and a chevron
|
|
139
|
+
// pointing the way the press moves the rail's edge. The folded rail mirrors the
|
|
140
|
+
// chevron about 15 in layout.css, which lands it exactly on Lucide
|
|
141
|
+
// `panel-left-open` — so both states are a shipped glyph and neither is drawn by
|
|
142
|
+
// hand. The seam does not move: it IS the rail, and the compartment it cuts off
|
|
143
|
+
// stays on the side the rail is on. Only the nodes are written here — a child a
|
|
144
|
+
// stylesheet can reach — and they are spliced into icon()'s own wrapper rather
|
|
145
|
+
// than a copy of it. why: docs/specification.md#the-page-shell
|
|
142
146
|
const MARK = '<rect x="3" y="3" width="18" height="18" rx="2"/>'
|
|
143
|
-
+ '<path class="ui-app__fold-seam" d="M9 3v18"/>'
|
|
147
|
+
+ '<path class="ui-app__fold-seam" d="M9 3v18"/>'
|
|
148
|
+
+ '<path class="ui-app__fold-arrow" d="m16 15-3-3 3-3"/>';
|
|
144
149
|
const railMark = () => icon('').replace('></svg>', `>${MARK}</svg>`);
|
|
145
150
|
|
|
146
151
|
// The rail's own skin, outside the <nav>: folding the rail is not a place to go.
|
package/src/styles/input.css
CHANGED
|
@@ -157,7 +157,9 @@
|
|
|
157
157
|
transform: translate(-50%, -50%);
|
|
158
158
|
}
|
|
159
159
|
.ui-check input[type="radio"] { border-radius: 50%; }
|
|
160
|
-
|
|
160
|
+
/* :enabled, or the accent lights the border of a box that cannot be clicked —
|
|
161
|
+
the same "press me" a disabled control has to stop making. #429 */
|
|
162
|
+
.ui-check input:enabled:hover { border-color: var(--accent); }
|
|
161
163
|
.ui-check input:checked { background: var(--accent); border-color: var(--accent); }
|
|
162
164
|
.ui-check input[type="checkbox"]:checked::after {
|
|
163
165
|
content: "";
|
|
@@ -177,6 +179,26 @@
|
|
|
177
179
|
}
|
|
178
180
|
.ui-check input:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
|
|
179
181
|
|
|
182
|
+
/* Off. `.ui-check` had no disabled rule at all until #429 put a disabled checkbox
|
|
183
|
+
on screen: the box kept --surface-2 and --border-strong, its words kept --text,
|
|
184
|
+
and it was the word "unavailable" in the label that carried the state. The same
|
|
185
|
+
flat pair the fields and the buttons take (#220, #273) — a paint on the box and
|
|
186
|
+
quiet ink on the words, never an `opacity`, which drags label and box toward the
|
|
187
|
+
ground together. A checked box drops the accent for the reason a disabled primary
|
|
188
|
+
button does: the accent IS the "press me". Its mark inverts to --disabled-surface,
|
|
189
|
+
so the tick and the dot keep the measured --disabled-ink pair in both themes. */
|
|
190
|
+
.ui-check input:disabled {
|
|
191
|
+
background: var(--disabled-surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
|
|
192
|
+
border-color: var(--disabled-border);
|
|
193
|
+
cursor: not-allowed;
|
|
194
|
+
}
|
|
195
|
+
.ui-check input:disabled:checked { background: var(--disabled-ink); border-color: var(--disabled-ink); }
|
|
196
|
+
.ui-check input[type="checkbox"]:disabled:checked::after { border-color: var(--disabled-surface); }
|
|
197
|
+
.ui-check input[type="radio"]:disabled:checked::after { background: var(--disabled-surface); /* ring-gap: inherit — the mark inside the box; the box carries the focus indicator. */ }
|
|
198
|
+
/* The label is the click target, so the cursor belongs on it, and the words it
|
|
199
|
+
holds go quiet with the box beside them. */
|
|
200
|
+
.ui-check:has(input:disabled) { /* muted-ink: state — unavailable control */ color: var(--disabled-ink); cursor: not-allowed; }
|
|
201
|
+
|
|
180
202
|
/* Toggle switch */
|
|
181
203
|
.ui-switch {
|
|
182
204
|
--sw-w: 44px;
|
package/src/styles/layout.css
CHANGED
|
@@ -106,13 +106,14 @@
|
|
|
106
106
|
/* Which row is current is structural here, not chromatic: the resting glyphs
|
|
107
107
|
step down so the current one is the brightest whatever --accent is doing, and
|
|
108
108
|
the bar beside it carries the hue. A 3x17px bar on a 45x44 tile was the
|
|
109
|
-
smallest of three weak signals doing the most work, so it grows with the row.
|
|
109
|
+
smallest of three weak signals doing the most work, so it grows with the row.
|
|
110
|
+
The current glyph's own ink is nav.css's rule now, not a copy here: since #429
|
|
111
|
+
EVERY rail reads it that way, so the rail's copy was the same sentence in a
|
|
112
|
+
second file. What is left here is the rail's own resting step. */
|
|
110
113
|
/* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
|
|
111
114
|
the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar —
|
|
112
|
-
stories/apps/shell-states.test.js:
|
|
115
|
+
stories/apps/shell-states.test.js:1491 `a resting glyph is dimmer but still legible`. */
|
|
113
116
|
.ui-app__rail .ui-nav__ic svg { opacity: 0.66; }
|
|
114
|
-
.ui-app__rail .ui-nav__item.is-active .ui-nav__ic svg,
|
|
115
|
-
.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 */
|
|
116
117
|
.ui-app__rail .ui-nav__item.is-active::before { width: 3px; height: 62%; }
|
|
117
118
|
|
|
118
119
|
/* A destructive row of the rail is a navigation row and rests like one; the danger
|
|
@@ -224,12 +225,27 @@
|
|
|
224
225
|
.ui-app .ui-app__fold { color: var(--text); }
|
|
225
226
|
.ui-app .ui-app__fold:hover,
|
|
226
227
|
.ui-app .ui-app__fold:focus-visible { color: var(--strong); }
|
|
227
|
-
/* The
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
the
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
/* The seam holds at 9 at both widths, because it is the rail: the compartment it
|
|
229
|
+
cuts off is the narrow one, on the side the rail is on. What travels is the
|
|
230
|
+
chevron, and it says what the press will do rather than repeating the state —
|
|
231
|
+
the same thing the control's own accessible name says. shell.js draws it at
|
|
232
|
+
13..16, Lucide `panel-left-close`; mirrored about 15, the centre of the
|
|
233
|
+
compartment it stands in, it lands at 14..17, which is Lucide
|
|
234
|
+
`panel-left-open`. So the two states are two Lucide glyphs and the CSS between
|
|
235
|
+
them is one reflection, not a hand-drawn position. `transform-origin` is
|
|
236
|
+
written out because a scale, unlike the translate this replaces, is read from
|
|
237
|
+
wherever the origin is — left to the initial 0 0 it would fold the mark
|
|
238
|
+
through the viewBox's left edge.
|
|
239
|
+
|
|
240
|
+
The width's own clock, not the words', so the mark and the closing edge arrive
|
|
241
|
+
together; no `!important`, so the reduced-motion net still takes it.
|
|
242
|
+
why: docs/specification.md#the-page-shell */
|
|
243
|
+
.ui-app__fold .ui-app__fold-arrow {
|
|
244
|
+
transform-box: view-box;
|
|
245
|
+
transform-origin: 15px 12px;
|
|
246
|
+
transition: transform var(--dur-med) var(--ease);
|
|
247
|
+
}
|
|
248
|
+
.ui-app.is-collapsed .ui-app__fold .ui-app__fold-arrow { transform: scaleX(-1); }
|
|
233
249
|
|
|
234
250
|
/* -- The collapsed rail --------------------------------------------------- */
|
|
235
251
|
/* Each rule here has its twin in the 720px block below; nothing lays a row out
|
|
@@ -257,6 +273,16 @@
|
|
|
257
273
|
:where(.ui-app:not(.ui-app--topbar).is-collapsed) .ui-app__brand { opacity: 0; visibility: hidden; pointer-events: none; }
|
|
258
274
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__sub { margin-inline-start: 0; padding-inline-start: 0; border-inline-start-width: 0; }
|
|
259
275
|
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
|
|
276
|
+
/* The current row is ONE plate on the glyph column. Same rules as nav.css writes
|
|
277
|
+
for a standalone folded rail, and for the same reasons: the bar stands in the
|
|
278
|
+
row's own left padding, which on the strip is the rail's edge; the hairline is
|
|
279
|
+
drawn on a row that keeps the open column, so its right edge is off the strip;
|
|
280
|
+
and --ui-nav-strip is twice the glyph's centre, so a box that wide at the row's
|
|
281
|
+
edge is centred on the glyph. why: docs/specification.md#the-page-shell */
|
|
282
|
+
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item.is-active { isolation: isolate; background: none; }
|
|
283
|
+
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item.is-active:not(:focus-visible) { box-shadow: none; }
|
|
284
|
+
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item.is-active::before { left: 0; width: var(--ui-nav-strip); height: 100%; border-radius: 10px; background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ z-index: -1; }
|
|
285
|
+
:where(.ui-app.is-collapsed) .ui-app__rail .ui-nav__item:hover:not(:focus-visible) { outline: none; }
|
|
260
286
|
/* The label IS the chip. `fixed` is the fallback placement, out of the rail's own
|
|
261
287
|
clip; the @supports branch below pins it to the row and is the only one that
|
|
262
288
|
survives a scroll. The toggle's selector is unscoped, because it is wordless at
|
|
@@ -452,6 +478,10 @@
|
|
|
452
478
|
.ui-app__brand span { opacity: 0; pointer-events: none; }
|
|
453
479
|
.ui-app__rail .ui-nav__sub { margin-inline-start: 0; padding-inline-start: 0; border-inline-start-width: 0; }
|
|
454
480
|
.ui-app__rail .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
|
|
481
|
+
.ui-app__rail .ui-nav__item.is-active { isolation: isolate; background: none; }
|
|
482
|
+
.ui-app__rail .ui-nav__item.is-active:not(:focus-visible) { box-shadow: none; }
|
|
483
|
+
.ui-app__rail .ui-nav__item.is-active::before { left: 0; width: var(--ui-nav-strip); height: 100%; border-radius: 10px; background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ z-index: -1; }
|
|
484
|
+
.ui-app__rail .ui-nav__item:hover:not(:focus-visible) { outline: none; }
|
|
455
485
|
/* The one line of this block that is deliberately NOT the reader's fold: a finger
|
|
456
486
|
is the only pointer this width has, so a row and the account block are held to
|
|
457
487
|
44px. Neither floor can spread to the press, where growing the box is the one
|
package/src/styles/nav.css
CHANGED
|
@@ -93,8 +93,12 @@
|
|
|
93
93
|
|
|
94
94
|
.ui-nav__ic { display: inline-flex; flex: none; }
|
|
95
95
|
.ui-nav__ic svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 2.2; opacity: 0.72; }
|
|
96
|
+
/* The glyph brightens with its row and takes the row's ink, not the accent. One
|
|
97
|
+
accent signal per row: that is the marker above, which is the only thing on a
|
|
98
|
+
rail that means "here". A glyph in --accent beside it is the same sentence
|
|
99
|
+
said twice, and beside an accent counter it was said three times. See #429. */
|
|
96
100
|
.ui-nav__item.is-active .ui-nav__ic svg,
|
|
97
|
-
.ui-nav__item.is-current .ui-nav__ic svg { opacity: 1; stroke:
|
|
101
|
+
.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 */
|
|
98
102
|
.ui-nav__label {
|
|
99
103
|
flex: 1 1 auto; min-width: 0;
|
|
100
104
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
@@ -146,16 +150,14 @@
|
|
|
146
150
|
.ui-nav__badge.is-live { background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--chip-success-ink); }
|
|
147
151
|
.ui-nav__badge.is-danger { background: var(--glow-pink); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ color: var(--pink); }
|
|
148
152
|
.ui-nav__item.is-active .ui-nav__badge.is-neutral { background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); }
|
|
149
|
-
/* Same shape, one row up: on
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
4.08 in dark Nebula,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
See #157 and docs/specification.md#colour-and-contrast. */
|
|
158
|
-
.ui-nav__item.is-active .ui-nav__badge.is-accent { background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--accent); }
|
|
153
|
+
/* Same shape, one row up: on the current row the accent counter drops both the
|
|
154
|
+
wash and the accent and reads as the neutral one beside it. The row already
|
|
155
|
+
carries the one accent signal it is allowed — the marker — and the count is a
|
|
156
|
+
number, not a second answer to "where am I". It also retires the pair #157
|
|
157
|
+
recorded: accent ink on --surface fell under WCAG AA in four of the eight
|
|
158
|
+
theme x accent cells (4.08 in dark Nebula), and no alpha cleared it. Nothing
|
|
159
|
+
now paints that pair. See #429 and docs/specification.md#colour-and-contrast. */
|
|
160
|
+
.ui-nav__item.is-active .ui-nav__badge.is-accent { background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ color: var(--strong); }
|
|
159
161
|
|
|
160
162
|
/* Collapsible group */
|
|
161
163
|
.ui-nav__group { display: flex; flex-direction: column; }
|
|
@@ -248,6 +250,43 @@
|
|
|
248
250
|
}
|
|
249
251
|
.ui-nav--side.is-collapsed .ui-nav__item:not(:has(.ui-nav__ic))::after { opacity: 0.62; }
|
|
250
252
|
|
|
253
|
+
/* The current row on a folded rail: ONE plate on the glyph column, and nothing
|
|
254
|
+
else. The open rail's marker is a 3px bar standing in the row's own left
|
|
255
|
+
padding; on the strip that padding is the rail's edge, so the bar reads as a
|
|
256
|
+
tick on the rail rather than a mark on a row — and a nested row puts it
|
|
257
|
+
--space-3 further out again, outside the rail altogether. The hairline cannot
|
|
258
|
+
close either: a row keeps the open column and the rail clips it, so its right
|
|
259
|
+
edge is drawn off the strip.
|
|
260
|
+
|
|
261
|
+
The plate is the strip. --ui-nav-strip is twice a row's own glyph centre, so a
|
|
262
|
+
box that wide standing at the row's edge is centred on the glyph, and the mark
|
|
263
|
+
lands where the reader is already looking. --surface-3 is the kit's quiet
|
|
264
|
+
non-text fill — a step off the rail in both themes, carrying a glyph and never
|
|
265
|
+
a word. The references mark current the same way and with no hue at all:
|
|
266
|
+
Cloudflare's docs rail paints a flat plate, and lessly-ui's rail is "a plate and
|
|
267
|
+
the weight step, no hue". Here the glyph's own step from 0.66 to full ink is the
|
|
268
|
+
second half of that pair. See #429.
|
|
269
|
+
|
|
270
|
+
`isolation` is what puts the plate behind the glyph: a positioned ::before paints
|
|
271
|
+
over its row's content, and a negative z-index with no stacking context of its
|
|
272
|
+
own would drop it behind the rail instead. */
|
|
273
|
+
.ui-nav--side.is-collapsed .ui-nav__item.is-active { isolation: isolate; background: none; }
|
|
274
|
+
/* At rest only — box-shadow carries the hairline AND the focus ring (WCAG 2.4.7). */
|
|
275
|
+
.ui-nav--side.is-collapsed .ui-nav__item.is-active:not(:focus-visible) { box-shadow: none; }
|
|
276
|
+
.ui-nav--side.is-collapsed .ui-nav__item.is-active::before {
|
|
277
|
+
left: 0;
|
|
278
|
+
width: var(--ui-nav-strip);
|
|
279
|
+
height: 100%;
|
|
280
|
+
border-radius: 10px;
|
|
281
|
+
background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
|
|
282
|
+
z-index: -1;
|
|
283
|
+
}
|
|
284
|
+
/* Hover's hairline is an outline on a row that keeps the open column, so on the
|
|
285
|
+
strip it too is drawn with no right edge. A folded rail answers a pointer with
|
|
286
|
+
the row's own ink and the name beside it, which is the whole readout at this
|
|
287
|
+
width. At rest only, so the focus ring is left standing. */
|
|
288
|
+
.ui-nav--side.is-collapsed .ui-nav__item:hover:not(:focus-visible) { outline: none; }
|
|
289
|
+
|
|
251
290
|
/* -- Horizontal tabs ----------------------------------------------------- */
|
|
252
291
|
.ui-nav--tabs {
|
|
253
292
|
display: flex;
|