@apliteni/apliteni-ui 0.92.0 → 0.93.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 +5 -7
- package/package.json +1 -1
- package/react/README.md +2 -2
- package/react/dist/index.d.ts +3 -3
- package/src/assets/icons.js +1 -1
- package/src/components/account-nav.js +2 -2
- package/src/components/back.js +2 -2
- package/src/components/command-palette.js +2 -2
- package/src/components/drawer.js +1 -1
- package/src/components/dropdown.js +12 -12
- package/src/components/filter-bar.js +2 -2
- package/src/components/footer.js +1 -1
- package/src/components/index.js +1 -1
- package/src/components/loading.js +1 -1
- package/src/components/shell.js +19 -19
- package/src/components/stat.js +3 -3
- package/src/components/success.js +3 -3
- package/src/components/tooltip.js +1 -1
- package/src/components/topbar.js +1 -1
- package/src/html.js +1 -1
- package/src/index.css +1 -1
- package/src/index.d.ts +1 -1
- package/src/logic/accents.js +1 -1
- package/src/logic/filter-bar.js +4 -4
- package/src/styles/back.css +3 -3
- package/src/styles/base.css +3 -3
- package/src/styles/button.css +3 -3
- package/src/styles/callout.css +2 -2
- package/src/styles/card.css +8 -8
- package/src/styles/command-palette.css +14 -14
- package/src/styles/confirm.css +7 -7
- package/src/styles/drawer.css +8 -8
- package/src/styles/dropdown.css +21 -21
- package/src/styles/feedback.css +2 -2
- package/src/styles/field-zoom.css +1 -1
- package/src/styles/file-drop.css +1 -1
- package/src/styles/filter-bar.css +6 -6
- package/src/styles/footer.css +1 -1
- package/src/styles/input.css +6 -6
- package/src/styles/layout.css +32 -32
- package/src/styles/loading.css +1 -1
- package/src/styles/motion.css +1 -1
- package/src/styles/nav.css +4 -4
- package/src/styles/pagination.css +2 -2
- package/src/styles/reduced-motion.css +2 -2
- package/src/styles/segmented.css +3 -3
- package/src/styles/stat.css +3 -3
- package/src/styles/success.css +1 -1
- package/src/styles/table.css +6 -6
- package/src/styles/tap-zone.css +1 -1
- package/src/styles/tooltip.css +3 -3
- package/src/styles/topbar.css +8 -8
- package/src/tokens/accents.css +1 -1
- package/src/tokens/tokens.css +25 -25
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ import { DataTable, Modal } from '@apliteni/apliteni-ui/react';
|
|
|
61
61
|
|
|
62
62
|
The `appShell()` factory provides the complete product layout: the sticky rail, the page body
|
|
63
63
|
and a topbar over it when you ask for one, so no product rebuilds it. The
|
|
64
|
-
[component catalog](docs/
|
|
64
|
+
[component catalog](docs/components.md#component-catalog) lists this factory and every other one.
|
|
65
65
|
Account and personal settings belong in a modal over the product rather than on a page of
|
|
66
66
|
their own — see [Guidelines / Account and settings](guidelines/account-and-settings.md).
|
|
67
67
|
|
|
@@ -100,7 +100,7 @@ only `data-accent` is present, the kit uses that accent with the dark theme. An
|
|
|
100
100
|
`data-theme` does *not* mean “follow the system”: the kit has no `prefers-color-scheme`
|
|
101
101
|
rule. If the host wants to follow the operating system preference, its JavaScript must
|
|
102
102
|
choose the theme and add the attribute. See
|
|
103
|
-
[an absent attribute means dark](docs/
|
|
103
|
+
[an absent attribute means dark](docs/components.md#an-absent-attribute-means-dark).
|
|
104
104
|
|
|
105
105
|
You can change the settings at runtime with `applyTheme('light')` and
|
|
106
106
|
`applyAccent('phoenix')`. These functions save the choices to `localStorage`. You can also
|
|
@@ -121,12 +121,10 @@ section.
|
|
|
121
121
|
|
|
122
122
|
| Page | What it answers |
|
|
123
123
|
| --- | --- |
|
|
124
|
-
| [
|
|
125
|
-
| [
|
|
124
|
+
| [foundations.md](docs/foundations.md) | The tokens and the floors: widths, breakpoints, spacing, type, colour, elevation, the focus ring, the tap floor. |
|
|
125
|
+
| [components.md](docs/components.md) | The package: what to import, theming, the component catalog, and what each component guarantees. |
|
|
126
126
|
| [react/README.md](react/README.md) | The React components, their props, and the Storybook on port 6007. |
|
|
127
|
-
| [
|
|
128
|
-
| [landing-page.md](docs/landing-page.md) | ui.apli.tech: the chrome, the static build, the hosting. |
|
|
129
|
-
| [changelog.md](docs/changelog.md) | The changelog's data model and its Storybook deeplinks. |
|
|
127
|
+
| [contributing.md](docs/contributing.md) | Working on the kit: the repository's layout, Storybook, the guidelines collection, the site, the changelog and releases. |
|
|
130
128
|
|
|
131
129
|
## Contribute
|
|
132
130
|
|
package/package.json
CHANGED
package/react/README.md
CHANGED
|
@@ -246,7 +246,7 @@ this component and compares what is left.
|
|
|
246
246
|
Two things the factory has that this does not, both deliberate. It emits no `data-dropdown`
|
|
247
247
|
on the container, so a page that calls `wireDropdown(document)` cannot adopt a dropdown React
|
|
248
248
|
owns — the same decision `Drawer` makes about `data-drawer`; the row and panel hooks stay,
|
|
249
|
-
because they are the row contract `docs/
|
|
249
|
+
because they are the row contract `docs/components.md` publishes. And it has no `portal: true`: the
|
|
250
250
|
panel is a child of the trigger's container, so a dropdown inside `.ui-app__rail`
|
|
251
251
|
(`position: sticky` with `overflow-y: auto`) still wants the vanilla factory.
|
|
252
252
|
|
|
@@ -541,7 +541,7 @@ An optional `id` lets the caller choose the control ID; it must be unique.
|
|
|
541
541
|
`type="search"` input, and no visible label, so the row keeps the height of the
|
|
542
542
|
unlabelled controls beside it. `ariaLabel` is required and is the control's only
|
|
543
543
|
name. It forwards its ref and the native input props, and `className` joins
|
|
544
|
-
`.ui-input`. There is no clear button — see
|
|
544
|
+
`.ui-input`. There is no clear button — see docs/components.md#react-search-field for why.
|
|
545
545
|
|
|
546
546
|
```tsx
|
|
547
547
|
<div className="ui-toolbar">
|
package/react/dist/index.d.ts
CHANGED
|
@@ -730,7 +730,7 @@ type SearchFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'ar
|
|
|
730
730
|
* No clear button. The browser paints its own near-black on the light field and
|
|
731
731
|
* white on the dark one, beside the kit's `--muted` magnifier — two glyphs, and
|
|
732
732
|
* only one answers to a token — so `input.css` suppresses it.
|
|
733
|
-
* why: docs/
|
|
733
|
+
* why: docs/components.md#react-search-field
|
|
734
734
|
*/
|
|
735
735
|
declare const SearchField: react.ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "aria-label"> & {
|
|
736
736
|
/** The control's name. A toolbar search box shows no label, so this is the
|
|
@@ -850,7 +850,7 @@ type SuccessProps = Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'children'> &
|
|
|
850
850
|
check?: SuccessMark;
|
|
851
851
|
title?: string;
|
|
852
852
|
/** One short line under the title, or nothing. There is no eyebrow tier.
|
|
853
|
-
* why: docs/
|
|
853
|
+
* why: docs/components.md#success-confirmations */
|
|
854
854
|
body?: string;
|
|
855
855
|
/** Removed: a confirmation carries one title and at most one line. Typed as
|
|
856
856
|
* `never` so a caller still passing it is told where it went rather than
|
|
@@ -869,7 +869,7 @@ declare const Success: react.ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLD
|
|
|
869
869
|
check?: SuccessMark;
|
|
870
870
|
title?: string;
|
|
871
871
|
/** One short line under the title, or nothing. There is no eyebrow tier.
|
|
872
|
-
* why: docs/
|
|
872
|
+
* why: docs/components.md#success-confirmations */
|
|
873
873
|
body?: string;
|
|
874
874
|
/** Removed: a confirmation carries one title and at most one line. Typed as
|
|
875
875
|
* `never` so a caller still passing it is told where it went rather than
|
package/src/assets/icons.js
CHANGED
|
@@ -166,7 +166,7 @@ export const iconNames = Object.keys(ICONS);
|
|
|
166
166
|
// `filter` — Lucide's funnel — fails the first test. Depicting a thing is not the
|
|
167
167
|
// fault; `copy` is on this list and depicts one too. The kit reads the funnel alone
|
|
168
168
|
// as not saying whether the control filters, sorts or exports — its own inference
|
|
169
|
-
// from the sources in docs/
|
|
169
|
+
// from the sources in docs/foundations.md, not a measured recognition finding.
|
|
170
170
|
// See #565.
|
|
171
171
|
export const iconOnlyAllowed = {
|
|
172
172
|
x: 'close or dismiss',
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// that interpolates raw. It lives here rather than in shell.js because both
|
|
3
3
|
// shell.js and topbar.js need it and shell.js already imports topbar.js — the
|
|
4
4
|
// other direction would be a cycle. shell.js re-exports ACCOUNT_NAV, which is
|
|
5
|
-
// the published name docs/
|
|
6
|
-
// why: docs/
|
|
5
|
+
// the published name docs/components.md documents.
|
|
6
|
+
// why: docs/components.md#the-page-shell
|
|
7
7
|
import { esc } from './index.js';
|
|
8
8
|
|
|
9
9
|
// nav.js item objects. Labels are raw text — every nav primitive escapes, so a
|
package/src/components/back.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// docs/reviews/270-back-control.html and the owner chose this one, so the other
|
|
12
12
|
// three — a bordered button above the title, an icon-only arrow beside it, and
|
|
13
13
|
// the breadcrumb trail alone — are not built here.
|
|
14
|
-
// why: docs/
|
|
14
|
+
// why: docs/components.md#the-back-link
|
|
15
15
|
import { esc, icon } from './index.js';
|
|
16
16
|
|
|
17
17
|
// "Back" names a direction rather than a place. It is what a caller who names no
|
|
@@ -29,7 +29,7 @@ const TAB_OR_NEWLINE = /[\t\n\r]/g;
|
|
|
29
29
|
|
|
30
30
|
// A label that already says "Back to Invoices" names the place after those words, or the
|
|
31
31
|
// link would be read as "Back to Back to Invoices". The whole phrase, since "Backups" and
|
|
32
|
-
// "Back office" are places too. why: docs/
|
|
32
|
+
// "Back office" are places too. why: docs/components.md#the-back-link
|
|
33
33
|
const SAID = /^back\s+to(?:\s+|$)/i;
|
|
34
34
|
|
|
35
35
|
const text = (v) => (typeof v === 'string' || typeof v === 'number' ? String(v).trim() : '');
|
|
@@ -9,7 +9,7 @@ export { SCORE, scoreCommand, rankCommands, rankGroups, paletteHotkey } from '..
|
|
|
9
9
|
// The kit ships the shell and the ranking and names no result kinds; a row goes
|
|
10
10
|
// somewhere, runs something, or asks a confirm first. Inertness, Escape and the
|
|
11
11
|
// focus trap come from ./overlay.js, the stack the drawer and the confirm share.
|
|
12
|
-
// why: docs/
|
|
12
|
+
// why: docs/components.md#the-command-palette
|
|
13
13
|
import { esc, icon } from './index.js';
|
|
14
14
|
import { OVERLAY_LAYER, adoptOverlay, popOverlay, pushOverlay, returnFocus, syncOverlays } from './overlay.js';
|
|
15
15
|
import { safeUrl } from '../html.js';
|
|
@@ -61,7 +61,7 @@ function keysFor(shortcut) {
|
|
|
61
61
|
* the wiring, which is also what makes Cmd+Enter open it in a tab.
|
|
62
62
|
*
|
|
63
63
|
* A destructive item that names no confirm is rendered DISABLED. The kit's rule
|
|
64
|
-
* is that a delete asks first (docs/
|
|
64
|
+
* is that a delete asks first (docs/components.md#the-command-palette), and a
|
|
65
65
|
* palette is the one surface where a reader is typing fast and choosing from a
|
|
66
66
|
* list that reorders under them.
|
|
67
67
|
*/
|
package/src/components/drawer.js
CHANGED
|
@@ -75,7 +75,7 @@ export function drawer({
|
|
|
75
75
|
*
|
|
76
76
|
* drawer({ title, body: drawerSection({ title: 'Source', rows: [['Statement', '#4102']] }) })
|
|
77
77
|
*
|
|
78
|
-
* why: docs/
|
|
78
|
+
* why: docs/components.md#the-drawer
|
|
79
79
|
*
|
|
80
80
|
* @param {object} [o]
|
|
81
81
|
* @param {string} [o.title] group heading (escaped)
|
|
@@ -80,7 +80,7 @@ function ddBody({ items, sections }, listbox, sx) {
|
|
|
80
80
|
// The match is a substring of the label, anywhere in it, ignoring case and
|
|
81
81
|
// accents; rows keep their order. The factory and the wiring both ask
|
|
82
82
|
// dropdownMatch(), so a preset query and a typed one hide the same rows.
|
|
83
|
-
// why: docs/
|
|
83
|
+
// why: docs/components.md#a-dropdown-with-a-search-field
|
|
84
84
|
// NFD takes the mark off é or ö; ł, ø, đ and the rest are letters of their own
|
|
85
85
|
// with nothing to take off, so they are mapped by hand.
|
|
86
86
|
const ddIsRow = (it) => it && it !== '---' && !it.separator;
|
|
@@ -207,7 +207,7 @@ export function dropdown({
|
|
|
207
207
|
|
|
208
208
|
// The block the sheet bleeds to the panel's bottom edge. It sits OUTSIDE the
|
|
209
209
|
// unwrapped `footer`, because the block that bleeds is the one that has to
|
|
210
|
-
// touch the edge. why: docs/
|
|
210
|
+
// touch the edge. why: docs/components.md#the-dropdown-panel
|
|
211
211
|
const footBlock = foot ? `<div class="ui-dropdown__foot">${foot}</div>` : '';
|
|
212
212
|
|
|
213
213
|
const ddAttrs = 'data-dropdown'
|
|
@@ -236,7 +236,7 @@ export function dropdown({
|
|
|
236
236
|
// Every wired container, so the close handlers can reach a dropdown wherever it
|
|
237
237
|
// was drawn: `document.querySelectorAll` enters no shadow root and sees no other
|
|
238
238
|
// document. A Set and not a WeakSet, because this has to be walked.
|
|
239
|
-
// why: docs/
|
|
239
|
+
// why: docs/components.md#the-dropdown-panel
|
|
240
240
|
const _ddAll = new Set();
|
|
241
241
|
// One pair of close handlers per document that holds a dropdown, the same way
|
|
242
242
|
// wireShell() listens once per document it is handed.
|
|
@@ -276,7 +276,7 @@ function ddItemsOf(dd) {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
// ---- Search wiring ---------------------------------------------------------
|
|
279
|
-
// why: docs/
|
|
279
|
+
// why: docs/components.md#a-dropdown-with-a-search-field
|
|
280
280
|
const ddSearchOf = (dd) => ddPanelOf(dd)?.querySelector('[data-dd-search]') || null;
|
|
281
281
|
const ddActiveOf = (dd) => ddPanelOf(dd)?.querySelector('[data-dd-item].is-active') || null;
|
|
282
282
|
const ddComposing = (e) => e.isComposing || e.keyCode === 229;
|
|
@@ -342,7 +342,7 @@ export const DD_MENU_FLOOR = 240;
|
|
|
342
342
|
* A panel is anchored at one edge of its dropdown — its inline end when it
|
|
343
343
|
* carries `is-end` — so room is measured from that edge and the slide goes the
|
|
344
344
|
* other way. Measuring an end-anchored panel forwards put one off the page.
|
|
345
|
-
* why: docs/
|
|
345
|
+
* why: docs/components.md#a-filter-row-holds-its-panels
|
|
346
346
|
*
|
|
347
347
|
* @param {Element} dd a `.ui-dropdown` that may be inside a filter row
|
|
348
348
|
* @param {number} [floor] the width to reach for
|
|
@@ -405,7 +405,7 @@ function ddFitFilterPanel(dd, panel) {
|
|
|
405
405
|
* the old width too, and inline beats the sheet, so it is read again off the
|
|
406
406
|
* newly bounded box; only when the fit moved, because re-reading it is itself a
|
|
407
407
|
* write.
|
|
408
|
-
* why: docs/
|
|
408
|
+
* why: docs/components.md#a-filter-row-holds-its-panels */
|
|
409
409
|
function ddRefitFilterPanel(dd, panel) {
|
|
410
410
|
const fit = panel?.style ? filterPanelFit(dd) : null;
|
|
411
411
|
if (!fit || !ddWriteFit(panel, fit)) return;
|
|
@@ -424,7 +424,7 @@ function ddRowOf(dd) {
|
|
|
424
424
|
* `position: absolute`, so nothing in it can move a chip along its row. Any
|
|
425
425
|
* panel, not only the one being fitted: two open menus in one row would
|
|
426
426
|
* otherwise answer each other a mutation at a time.
|
|
427
|
-
* why: docs/
|
|
427
|
+
* why: docs/components.md#a-filter-row-holds-its-panels */
|
|
428
428
|
function ddOurs(node) {
|
|
429
429
|
const el = node?.nodeType === 1 ? node : node?.parentElement;
|
|
430
430
|
return !!el?.closest?.('[data-dropdown-panel]');
|
|
@@ -443,7 +443,7 @@ function ddOurs(node) {
|
|
|
443
443
|
* is laid out.
|
|
444
444
|
*
|
|
445
445
|
* Why those two cover the arithmetic, and the one case they do not:
|
|
446
|
-
* why: docs/
|
|
446
|
+
* why: docs/components.md#a-filter-row-holds-its-panels
|
|
447
447
|
*/
|
|
448
448
|
function ddWatchRow(dd, panel) {
|
|
449
449
|
ddUnwatchRow(dd);
|
|
@@ -664,7 +664,7 @@ function selectOption(dd, item) {
|
|
|
664
664
|
// Click-outside, Escape and the repositioning sweep, registered once per document
|
|
665
665
|
// that holds a dropdown — the same shape wireShell()'s listen() has, and for the
|
|
666
666
|
// same reason: a frame is its own document and a listener on the page's never
|
|
667
|
-
// fires there. why: docs/
|
|
667
|
+
// fires there. why: docs/components.md#the-dropdown-panel
|
|
668
668
|
function ddListen(doc) {
|
|
669
669
|
if (!doc || _ddWiredDocs.has(doc)) return;
|
|
670
670
|
_ddWiredDocs.add(doc);
|
|
@@ -683,7 +683,7 @@ function ddListen(doc) {
|
|
|
683
683
|
};
|
|
684
684
|
/* The fallback re-fit, for a view with no ResizeObserver. Not taken as well where
|
|
685
685
|
* ddWatchRow() is watching: a `resize` event is the worse measurement of the two.
|
|
686
|
-
* why: docs/
|
|
686
|
+
* why: docs/components.md#a-filter-row-holds-its-panels */
|
|
687
687
|
const refit = () => {
|
|
688
688
|
for (const dd of ddLive()) {
|
|
689
689
|
if (dd.classList.contains('open') && !dd.__ddPanel && !dd.__ddRowFit) {
|
|
@@ -712,11 +712,11 @@ export function wireDropdown(root = document) {
|
|
|
712
712
|
// Portal: lift the panel onto <body>. An ancestor whose overflow is not
|
|
713
713
|
// `visible` clips it on both axes, and one that is `position: sticky` opens
|
|
714
714
|
// a stacking context whatever z-index the panel carries — the app rail is
|
|
715
|
-
// both at once. why: docs/
|
|
715
|
+
// both at once. why: docs/components.md#the-dropdown-panel
|
|
716
716
|
if (panel && dd.hasAttribute('data-dropdown-portal')) {
|
|
717
717
|
// The tree the trigger is in, not the page's: a panel lifted out of a
|
|
718
718
|
// frame or a shadow root into the top document leaves its stylesheet and
|
|
719
|
-
// its close handler behind. why: docs/
|
|
719
|
+
// its close handler behind. why: docs/components.md#the-dropdown-panel
|
|
720
720
|
const host = ddHostOf(dd);
|
|
721
721
|
sweepOrphanPanels(host);
|
|
722
722
|
panel.setAttribute('data-dropdown-portal', '');
|
|
@@ -9,7 +9,7 @@ export function filterBar({ filters = [], label = 'Filters', clearLabel = 'Clear
|
|
|
9
9
|
+ `<legend class="ui-filter-bar__legend">${esc(filter.label)}</legend>`
|
|
10
10
|
+ dropdown({ items: filterChipItems(filter), variant: 'select',
|
|
11
11
|
// The chip prints one line; the field's name reaches a reader through the
|
|
12
|
-
// trigger's name and the chip's own legend. why: docs/
|
|
12
|
+
// trigger's name and the chip's own legend. why: docs/components.md#dense-financial-tables
|
|
13
13
|
triggerContent: `<span class="${filterChipUnset(filter) ? 'ui-dropdown__value is-placeholder' : 'ui-dropdown__value'}">${esc(filterChipText(filter))}</span>`,
|
|
14
14
|
ariaLabel: filterChipName(filter), open: !!filter.open && !disabled && !busy && !filter.disabled })
|
|
15
15
|
+ `<button type="button" class="ui-filter-bar__remove" data-filter-remove aria-label="${esc(`Remove ${filter.label} filter`)}">×</button></fieldset>`).join('')
|
|
@@ -19,7 +19,7 @@ export function filterBar({ filters = [], label = 'Filters', clearLabel = 'Clear
|
|
|
19
19
|
// rule — a control with no action to offer is not shown. It returns with
|
|
20
20
|
// the first chip, in the bordered skin rather than the ghost one, because a
|
|
21
21
|
// live action beside two chips has to read as live.
|
|
22
|
-
// why: docs/
|
|
22
|
+
// why: docs/components.md#a-filter-row-holds-its-panels
|
|
23
23
|
+ (filters.length
|
|
24
24
|
? `<span data-filter-clear>${button({ label: clearLabel, size: 'sm' })}</span>`
|
|
25
25
|
: '')
|
package/src/components/footer.js
CHANGED
|
@@ -36,7 +36,7 @@ function linkList(links = []) {
|
|
|
36
36
|
// matter, and h2 is the one rank that cannot skip whatever heading came before
|
|
37
37
|
// it. It was an h4 — the only h4 the kit drew — which read h2 → h4 on the
|
|
38
38
|
// landing page and left a rank a reader hears missing. The look is the class's,
|
|
39
|
-
// not the tag's. why:
|
|
39
|
+
// not the tag's. why: guidelines/the-page.md#the-page
|
|
40
40
|
function column({ title, links = [] } = {}) {
|
|
41
41
|
return `<div class="ui-footer__col">` +
|
|
42
42
|
(title ? `<h2 class="ui-footer__col-title">${esc(title)}</h2>` : '') +
|
package/src/components/index.js
CHANGED
|
@@ -61,7 +61,7 @@ export function statusDot(live = false) {
|
|
|
61
61
|
|
|
62
62
|
// ---- Card ----------------------------------------------------------------
|
|
63
63
|
// The title is a heading one level under the page's h1 unless `level` says
|
|
64
|
-
// otherwise. why: docs/
|
|
64
|
+
// otherwise. why: docs/foundations.md#labels-and-titles
|
|
65
65
|
export function card({ title, sub, body = '', variant, pad, icon: ic, level = 2 } = {}) {
|
|
66
66
|
const cls = cx('ui-card', variant && `ui-card--${variant}`, pad && `ui-card--pad-${pad}`);
|
|
67
67
|
const h = [2, 3, 4, 5, 6].includes(Number(level)) ? `h${Number(level)}` : 'h2';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Three things land in that body: skeleton() while it fetches, your markup once
|
|
6
6
|
// the rows arrive, deniedState() when the answer came back 403.
|
|
7
7
|
//
|
|
8
|
-
// why: docs/
|
|
8
|
+
// why: docs/components.md#pending-and-denied-states
|
|
9
9
|
import { icon } from '../assets/icons.js';
|
|
10
10
|
import { button, esc } from './index.js';
|
|
11
11
|
import { playEntrance } from '../motion.js';
|
package/src/components/shell.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The kit's one page shell: a full-height rail beside one <main>, in two layouts.
|
|
2
2
|
// `topbar` switches the product band on; it is off unless the caller hands one over.
|
|
3
3
|
// wireShell() once after mounting wires the fold, the nav's groups and the reader's menu.
|
|
4
|
-
// why: docs/
|
|
4
|
+
// why: docs/components.md#the-page-shell
|
|
5
5
|
import { topbar as productTopbar } from './topbar.js';
|
|
6
6
|
import { esc, icon } from './index.js';
|
|
7
7
|
import { sidebarNav, breadcrumbs, wireNav } from './nav.js';
|
|
@@ -12,7 +12,7 @@ import { ACCOUNT_NAV, toMenuTuple, initials } from './account-nav.js';
|
|
|
12
12
|
import { paletteHotkey } from './command-palette.js';
|
|
13
13
|
import { safeUrl } from '../html.js';
|
|
14
14
|
// The one account navigation definition lives in account-nav.js because topbar.js needs
|
|
15
|
-
// it too; re-exported here so the name docs/
|
|
15
|
+
// it too; re-exported here so the name docs/components.md publishes keeps working.
|
|
16
16
|
export { ACCOUNT_NAV };
|
|
17
17
|
|
|
18
18
|
const str = (v) => (v == null ? '' : String(v));
|
|
@@ -44,7 +44,7 @@ const toCrumbs = (crumbs) => (Array.isArray(crumbs) ? crumbs : [])
|
|
|
44
44
|
|
|
45
45
|
// A back link replaces the trail rather than joining it: the two would name the same parent
|
|
46
46
|
// twice. Anything but a record is no back link; its fields go through as given, so a back
|
|
47
|
-
// backLink() refuses leaves the trail standing. why: docs/
|
|
47
|
+
// backLink() refuses leaves the trail standing. why: docs/components.md#the-back-link
|
|
48
48
|
const toBack = (b) => (isRecord(b) && !Array.isArray(b) ? { href: b.href, label: b.label } : null);
|
|
49
49
|
|
|
50
50
|
// The reader, as two strings. railUser() and initials() both read them, and an
|
|
@@ -83,15 +83,15 @@ const toTopbar = (t) => {
|
|
|
83
83
|
//
|
|
84
84
|
// Each is a name the kit knows or the default — read strictly, and not through
|
|
85
85
|
// String(), which turned `['topbar']` into a layout and drew half a page.
|
|
86
|
-
// why: docs/
|
|
86
|
+
// why: docs/components.md#the-page-shell
|
|
87
87
|
const toLayout = (v) => (v === 'topbar' ? 'topbar' : 'rail');
|
|
88
88
|
|
|
89
|
-
// why: docs/
|
|
89
|
+
// why: docs/foundations.md#widths
|
|
90
90
|
const toWidth = (v) => (v === 'wide' ? 'wide' : 'centered');
|
|
91
91
|
|
|
92
92
|
// `search: 'palette-id'`, or the same id in `{ palette }` with a placeholder beside
|
|
93
93
|
// it. No palette to open, no field — the argument `signOutHref` takes.
|
|
94
|
-
// why: docs/
|
|
94
|
+
// why: docs/components.md#the-page-shell
|
|
95
95
|
const toSearch = (v) => {
|
|
96
96
|
const given = typeof v === 'string' ? { palette: v } : (isRecord(v) && !Array.isArray(v) ? v : null);
|
|
97
97
|
const palette = given && typeof given.palette === 'string' ? given.palette.trim() : '';
|
|
@@ -104,7 +104,7 @@ const toSearch = (v) => {
|
|
|
104
104
|
// attribute, falling through to --measure. It must REMOVE the property rather than pass
|
|
105
105
|
// a bad value on: a custom property accepts any token stream, so garbage is a valid
|
|
106
106
|
// declaration that drops the column to `none`, the full track.
|
|
107
|
-
// why: docs/
|
|
107
|
+
// why: docs/foundations.md#widths
|
|
108
108
|
const LENGTH = /^(?:\d+|\d*\.\d+)(?:px|rem|em|ch|%|vw)$/;
|
|
109
109
|
const mainMax = (v) => {
|
|
110
110
|
const s = str(v).trim();
|
|
@@ -114,7 +114,7 @@ const mainMax = (v) => {
|
|
|
114
114
|
// ---- the fold, and where the reader's choice is kept ---------------------
|
|
115
115
|
//
|
|
116
116
|
// A cookie rather than localStorage, because a server can read one and paint the
|
|
117
|
-
// rail at the width the reader left it. why: docs/
|
|
117
|
+
// rail at the width the reader left it. why: docs/components.md#the-page-shell
|
|
118
118
|
export const RAIL_COOKIE = 'apliteni-ui-rail';
|
|
119
119
|
const RAIL_MAX_AGE = 60 * 60 * 24 * 365;
|
|
120
120
|
const RAIL_VALUE = new RegExp(`(?:^|;\\s*)${RAIL_COOKIE}=(collapsed|expanded)(?:;|$)`);
|
|
@@ -142,7 +142,7 @@ const railName = (collapsed) => (collapsed ? 'Expand sidebar' : 'Collapse sideba
|
|
|
142
142
|
// hand. The seam does not move: it IS the rail, and the compartment it cuts off
|
|
143
143
|
// stays on the side the rail is on. Only the nodes are written here — a child a
|
|
144
144
|
// stylesheet can reach — and they are spliced into icon()'s own wrapper rather
|
|
145
|
-
// than a copy of it. why: docs/
|
|
145
|
+
// than a copy of it. why: docs/components.md#the-page-shell
|
|
146
146
|
const MARK = '<rect x="3" y="3" width="18" height="18" rx="2"/>'
|
|
147
147
|
+ '<path class="ui-app__fold-seam" d="M9 3v18"/>'
|
|
148
148
|
+ '<path class="ui-app__fold-arrow" d="m16 15-3-3 3-3"/>';
|
|
@@ -151,7 +151,7 @@ const railMark = () => icon('').replace('></svg>', `>${MARK}</svg>`);
|
|
|
151
151
|
// The rail's own skin, outside the <nav>: folding the rail is not a place to go.
|
|
152
152
|
// It stands at the far end of the head band's brand row, and its name is written
|
|
153
153
|
// out rather than put in a tooltip, because the name IS the chip layout.css lands
|
|
154
|
-
// beside the glyph — at both widths. why: docs/
|
|
154
|
+
// beside the glyph — at both widths. why: docs/components.md#the-page-shell
|
|
155
155
|
const railToggle = (collapsed) =>
|
|
156
156
|
`<div class="ui-app__fold-row">`
|
|
157
157
|
+ `<button type="button" class="ui-nav__item ui-app__fold" data-rail-toggle`
|
|
@@ -165,7 +165,7 @@ const SHAPES = {
|
|
|
165
165
|
nav: toItems, crumbs: toCrumbs, back: toBack, account: toReader, maxWidth: mainMax, topbar: toTopbar,
|
|
166
166
|
layout: toLayout, width: toWidth, search: toSearch,
|
|
167
167
|
// Drawn by default; `collapsible: false` is the way out, for a page that will
|
|
168
|
-
// never call wireShell(). why: docs/
|
|
168
|
+
// never call wireShell(). why: docs/components.md#the-page-shell
|
|
169
169
|
collapsible: (v) => v !== false,
|
|
170
170
|
// A boolean is the caller's answer. Anything else leaves it to the reader.
|
|
171
171
|
collapsed: (v) => (typeof v === 'boolean' ? v : null),
|
|
@@ -186,7 +186,7 @@ function settle(options) {
|
|
|
186
186
|
// The face of the reader block: the initials, and the two lines beside them that
|
|
187
187
|
// the fold takes away. `named` is the accessible name when nothing else carries
|
|
188
188
|
// one; under the menu trigger it is null, because the button is named by the words
|
|
189
|
-
// inside it. why: docs/
|
|
189
|
+
// inside it. why: docs/components.md#the-page-shell
|
|
190
190
|
const readerFace = (name, email, named, markOnly = false) =>
|
|
191
191
|
`<span class="ui-app__av"${named ? ` role="img" aria-label="Signed in as ${esc(named)}"` : ' aria-hidden="true"'}>`
|
|
192
192
|
+ `${esc(initials(name, email))}</span>`
|
|
@@ -235,7 +235,7 @@ function readerBlock({ name, email }, signOutHref, { band = false } = {}) {
|
|
|
235
235
|
|
|
236
236
|
// A field to look at, a button to press. `data-cmdk-open` and the cap's class are the
|
|
237
237
|
// palette's own, and the cap is NOT aria-hidden: it is half the button's name.
|
|
238
|
-
// why: docs/
|
|
238
|
+
// why: docs/components.md#the-page-shell
|
|
239
239
|
const searchField = ({ palette, placeholder }) =>
|
|
240
240
|
`<button type="button" class="ui-app__search" data-cmdk-open="${esc(palette)}" aria-haspopup="dialog">`
|
|
241
241
|
+ `<span class="ui-app__search-ic" aria-hidden="true">${icon('search')}</span>`
|
|
@@ -244,7 +244,7 @@ const searchField = ({ palette, placeholder }) =>
|
|
|
244
244
|
+ `</button>`;
|
|
245
245
|
|
|
246
246
|
// The band: search at its start, the reader at its end, beside the rail rather than
|
|
247
|
-
// across the top of both. why: docs/
|
|
247
|
+
// across the top of both. why: docs/components.md#the-page-shell
|
|
248
248
|
const shellBar = (search, account, signOutHref) =>
|
|
249
249
|
`<header class="ui-app__bar">`
|
|
250
250
|
+ (search ? searchField(search) : '')
|
|
@@ -292,13 +292,13 @@ export function appShell(options = {}) {
|
|
|
292
292
|
ariaLabel: navLabel,
|
|
293
293
|
});
|
|
294
294
|
// One band over a page, never two: the banded layout draws its own, so the
|
|
295
|
-
// compatibility bag is not drawn beside it. why: docs/
|
|
295
|
+
// compatibility bag is not drawn beside it. why: docs/components.md#the-page-shell
|
|
296
296
|
const compat = banded ? null : topbar;
|
|
297
297
|
// The topbar already says the product word, so the rail head steps aside when there is
|
|
298
298
|
// one. The word is the link's only text and the narrow rail folds it out of view, so
|
|
299
299
|
// the name is written out — the mark itself is aria-hidden. The banded layout's own
|
|
300
300
|
// bar says no word, so there the rail keeps the lockup: that layout's head band IS
|
|
301
|
-
// the product's mark. why: docs/
|
|
301
|
+
// the product's mark. why: docs/components.md#the-page-shell
|
|
302
302
|
const brand = compat ? '' : `<a class="ui-app__brand" href="${esc(safeUrl(brandHref))}" aria-label="${esc(word)}">`
|
|
303
303
|
+ `${prism(`appb-${++_shellUid}`, 24)}<span>${esc(word)}</span></a>`;
|
|
304
304
|
// A <div>, not an <aside>: <aside> is the `complementary` landmark, and this holds the
|
|
@@ -319,7 +319,7 @@ export function appShell(options = {}) {
|
|
|
319
319
|
? `<div class="ui-app__head">${brand}${inHead}</div>`
|
|
320
320
|
: '';
|
|
321
321
|
// The rail's foot: the reader, or the control that took their place. One block
|
|
322
|
-
// either way. why: docs/
|
|
322
|
+
// either way. why: docs/components.md#the-page-shell
|
|
323
323
|
const foot = banded
|
|
324
324
|
? (fold ? `<div class="ui-app__foot">${fold}</div>` : '')
|
|
325
325
|
: readerBlock(account, signOutHref);
|
|
@@ -357,7 +357,7 @@ const _unpersisted = new WeakSet();
|
|
|
357
357
|
// path is written once — the listener, the reflector and wireShell() all have to mean
|
|
358
358
|
// the same control. A descendant and not a child, because the two layouts stand the
|
|
359
359
|
// row in two places: the head band, or the rail's foot.
|
|
360
|
-
// why: docs/
|
|
360
|
+
// why: docs/components.md#the-page-shell
|
|
361
361
|
const FOLD_PATH = '.ui-app__fold-row > [data-rail-toggle]';
|
|
362
362
|
const RAIL_FOLD = `.ui-app__rail ${FOLD_PATH}`;
|
|
363
363
|
|
|
@@ -413,7 +413,7 @@ export function wireShell(root = document, { persist } = {}) {
|
|
|
413
413
|
// other. Idempotent, and a shell with no account draws none to find.
|
|
414
414
|
wireDropdown(root);
|
|
415
415
|
// A server cannot know which key the reader holds, so the browser corrects it here —
|
|
416
|
-
// off the ROOT's window, not the global one. why: docs/
|
|
416
|
+
// off the ROOT's window, not the global one. why: docs/components.md#the-page-shell
|
|
417
417
|
const here = root.nodeType === 9 ? root : root.ownerDocument;
|
|
418
418
|
const view = here && here.defaultView;
|
|
419
419
|
const key = paletteHotkey(view && view.navigator ? view.navigator.platform : '');
|
package/src/components/stat.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Stat band — a row of key figures, as an HTML string.
|
|
2
|
-
// why: docs/
|
|
2
|
+
// why: docs/components.md#stat-bands
|
|
3
3
|
//
|
|
4
4
|
// A band is a <dl>: each figure is a <div> holding its label as a <dt> and its
|
|
5
5
|
// value, change and trend as <dd>s, which is the grouping HTML allows inside a
|
|
@@ -24,7 +24,7 @@ const GLYPH = { up: 'arrowUp', down: 'arrowDown', flat: 'minus' };
|
|
|
24
24
|
const hasChange = (delta) => delta && delta.value != null && delta.value !== '';
|
|
25
25
|
|
|
26
26
|
// One row and one line: the caption, the change, then what it is measured
|
|
27
|
-
// against. Nothing to compare draws no row. why: docs/
|
|
27
|
+
// against. Nothing to compare draws no row. why: docs/components.md#stat-bands
|
|
28
28
|
const contextRow = (caption, delta, basisId) => {
|
|
29
29
|
if (!hasChange(delta)) return caption ? `<dd class="ui-stat__caption">${esc(caption)}</dd>` : '';
|
|
30
30
|
// The space is read where the CSS gap is only drawn: "of income+1.2 pts" else.
|
|
@@ -58,7 +58,7 @@ const figure = ({ label = '', value = '', caption = '', delta, trend = '' }, til
|
|
|
58
58
|
// `basis` is the band's caption: what every change is measured against, said
|
|
59
59
|
// once before the list the way a table's <caption> is, in every layout — under
|
|
60
60
|
// a row of tiles it would read as a note on the last card.
|
|
61
|
-
// why: docs/
|
|
61
|
+
// why: docs/components.md#stat-bands
|
|
62
62
|
export function statBand({ stats = [], variant = 'tiles', basis = '', label, id } = {}) {
|
|
63
63
|
const v = STAT_VARIANTS.includes(variant) ? variant : 'tiles';
|
|
64
64
|
const base = id ? esc(id) : `ui-stats-${++seq}`;
|
|
@@ -10,7 +10,7 @@ import { esc, button } from './index.js';
|
|
|
10
10
|
|
|
11
11
|
// 'line' is the bare Lucide `check`, 'circled' is Lucide circle-check-big. Which
|
|
12
12
|
// is the default, and why that departs from Guidelines / Iconography:
|
|
13
|
-
// why: docs/
|
|
13
|
+
// why: docs/components.md#success-confirmations
|
|
14
14
|
// Each is written whole because the icon-sizing and glyph-stroke gates find their
|
|
15
15
|
// subjects by scanning source for a class on an `<svg …>`: a tag split across
|
|
16
16
|
// string pieces leaves both silent rather than failing.
|
|
@@ -66,7 +66,7 @@ function countdownEl({ seconds = 5, label = 'Redirecting' } = {}) {
|
|
|
66
66
|
// Both carry one title and at most one short line under it. There is no eyebrow
|
|
67
67
|
// tier: a confirmation stacking a label, a headline and a paragraph reads as
|
|
68
68
|
// three competing voices for one outcome.
|
|
69
|
-
// why: docs/
|
|
69
|
+
// why: docs/components.md#success-confirmations
|
|
70
70
|
export function success({
|
|
71
71
|
layout = 'hero', // 'hero' | 'split' | 'compact'
|
|
72
72
|
level, // heading level of the title; see the note below
|
|
@@ -88,7 +88,7 @@ export function success({
|
|
|
88
88
|
// takes h2. It was an h3 either way, which left a page whose whole content is
|
|
89
89
|
// a success() with no h1 at all — the fault Guidelines / The page names.
|
|
90
90
|
// A caller who knows better passes `level`. The look is the class's.
|
|
91
|
-
// why:
|
|
91
|
+
// why: guidelines/the-page.md#the-page
|
|
92
92
|
const rank = [1, 2, 3, 4, 5, 6].includes(Number(level)) ? Number(level) : (layout === 'compact' ? 2 : 1);
|
|
93
93
|
const h = `h${rank}`;
|
|
94
94
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// A mark carries `data-tip-value`, with `data-tip-label` and `data-tip-detail`;
|
|
12
12
|
// a `[data-tip-anchor]` inside it is where the readout opens. Under a finger
|
|
13
13
|
// there is no hover, so a tap is the switch: see Pointer kinds below.
|
|
14
|
-
// why: docs/
|
|
14
|
+
// why: docs/components.md#the-hover-readout
|
|
15
15
|
import { esc } from './index.js';
|
|
16
16
|
|
|
17
17
|
const cx = (...a) => a.filter(Boolean).join(' ');
|
package/src/components/topbar.js
CHANGED
|
@@ -30,7 +30,7 @@ export function deckTextSwitch(active = 'deck') {
|
|
|
30
30
|
// bespoke .vsw/.vopt classes for a pixel-identical look, but emits the generic
|
|
31
31
|
// [data-dropdown] hooks so there's ONE open/close/keyboard implementation.
|
|
32
32
|
// `badge` is a tone key and the kit writes the word for it; the stylesheet used
|
|
33
|
-
// to uppercase the key itself. why: docs/
|
|
33
|
+
// to uppercase the key itself. why: docs/foundations.md#labels-and-titles
|
|
34
34
|
const VBADGE = { live: 'Live', archive: 'Archive' };
|
|
35
35
|
export function versionSwitcher(versions = [], activeIdx = 0) {
|
|
36
36
|
const cur = versions[activeIdx]?.label || '';
|
package/src/html.js
CHANGED
|
@@ -5,7 +5,7 @@ export const esc = value => String(value == null ? '' : value).replace(/[&<>"]/g
|
|
|
5
5
|
// Legacy topbar/brand slots accept already escaped HTML. Preserve their entities.
|
|
6
6
|
export const trustedAttr = value => String(value ?? '').replace(/[<>"]/g, c => ENTITIES[c]);
|
|
7
7
|
|
|
8
|
-
// why: docs/
|
|
8
|
+
// why: docs/components.md#escaping-and-url-slots
|
|
9
9
|
export function safeUrl(value, fallback = '#') {
|
|
10
10
|
const raw = String(value ?? '');
|
|
11
11
|
const scheme = raw.replace(/[\t\n\r]/g, '').replace(/^[\u0000-\u0020]+/, '');
|
package/src/index.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (--font-sans, everything else). Weights 300-700 in both. Neither is
|
|
10
10
|
* bundled; a family that never loads falls back to the system stack behind
|
|
11
11
|
* it, in silence.
|
|
12
|
-
* why: docs/
|
|
12
|
+
* why: docs/foundations.md#typefaces
|
|
13
13
|
* ========================================================================== */
|
|
14
14
|
@import "./tokens/brand.generated.css";
|
|
15
15
|
@import "./tokens/tokens.css";
|
package/src/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare const DD_MENU_FLOOR: number;
|
|
|
41
41
|
* the width it reached for, and whether it is anchored at its inline end.
|
|
42
42
|
* `null` unless the dropdown is inside a `.ui-filter-bar__chip`: the slide is a
|
|
43
43
|
* chip's offset along its row, so a panel anchored to the row itself is not this
|
|
44
|
-
* function's subject and sizes itself. why: docs/
|
|
44
|
+
* function's subject and sizes itself. why: docs/components.md */
|
|
45
45
|
export declare function filterPanelFit(
|
|
46
46
|
dd: Element | null | undefined,
|
|
47
47
|
floor?: number,
|
package/src/logic/accents.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// react/src/AccentPicker.tsx call accentSwatchStyle(); site/chrome.mjs and
|
|
6
6
|
// site/index.html still hand-keep the same four gradients. Held by
|
|
7
7
|
// stories/accent-swatch.test.js, which derives both sides out of src/tokens/.
|
|
8
|
-
// why: docs/
|
|
8
|
+
// why: docs/components.md#page-furniture. See issues #190 and #429.
|
|
9
9
|
export const ACCENTS = ['default', 'phoenix', 'ocean', 'emerald'];
|
|
10
10
|
|
|
11
11
|
// Per accent, the gradient's two stops — each the value src/tokens/ declares for
|
package/src/logic/filter-bar.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* `value` is display text: it is the chip's only visible line, so a consumer
|
|
8
8
|
* answers a change with text a reader can read, not a raw code.
|
|
9
|
-
* why: docs/
|
|
9
|
+
* why: docs/components.md#dense-financial-tables
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const text = (v) => (v == null ? '' : String(v));
|
|
@@ -71,7 +71,7 @@ function stopsAround(host) {
|
|
|
71
71
|
* order, else the nearest one behind it. A filter bar that has just dropped its
|
|
72
72
|
* last control keeps no box the focus ring can sit on, so the focus has to
|
|
73
73
|
* leave the bar rather than land on a line with no height.
|
|
74
|
-
* why: docs/
|
|
74
|
+
* why: docs/components.md#a-filter-row-holds-its-panels */
|
|
75
75
|
export function nextFocusStop(host) {
|
|
76
76
|
return stopsAround(host)[0] || null;
|
|
77
77
|
}
|
|
@@ -88,7 +88,7 @@ export function nextFocusStop(host) {
|
|
|
88
88
|
* Being reachable is not the same as taking the focus: a control can be visible
|
|
89
89
|
* and enabled and still refuse, so each candidate is asked and then checked, and
|
|
90
90
|
* the next one tried.
|
|
91
|
-
* why: docs/
|
|
91
|
+
* why: docs/components.md#a-filter-row-holds-its-panels */
|
|
92
92
|
export function focusNextStop(host) {
|
|
93
93
|
const doc = host && host.ownerDocument;
|
|
94
94
|
if (!doc) return null;
|
|
@@ -114,7 +114,7 @@ export function focusNextStop(host) {
|
|
|
114
114
|
* consumer wrote them left whatever they had marked standing — a menu checking
|
|
115
115
|
* `All`, `aria-selected="true"` on it, under a chip printing `Asia`. The
|
|
116
116
|
* consumer's own array and objects are not written to. #550
|
|
117
|
-
* why: docs/
|
|
117
|
+
* why: docs/components.md#a-filter-row-holds-its-panels */
|
|
118
118
|
export function filterChipItems(filter = {}) {
|
|
119
119
|
const items = filter.items || [];
|
|
120
120
|
const chosen = text(filter.value);
|