@apliteni/apliteni-ui 0.12.0 β 0.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/react/dist/index.css +19 -0
- package/react/dist/index.d.ts +43 -2
- package/react/dist/index.js +98 -6
- package/src/assets/icons.js +5 -26
- package/src/components/account-nav.js +1 -1
- package/src/components/confirm.js +27 -28
- package/src/components/drawer.js +27 -28
- package/src/components/dropdown.js +26 -23
- package/src/components/index.js +9 -13
- package/src/components/loading.js +126 -0
- package/src/components/nav.js +7 -20
- package/src/components/overlay.js +7 -16
- package/src/components/shell.js +10 -9
- package/src/components/success.js +8 -17
- package/src/components/topbar.js +8 -12
- package/src/index.css +6 -1
- package/src/index.js +1 -0
- package/src/inline.js +4 -0
- package/src/styles/badge.css +1 -1
- package/src/styles/base.css +5 -18
- package/src/styles/button.css +18 -5
- package/src/styles/callout.css +37 -26
- package/src/styles/card.css +1 -1
- package/src/styles/code.css +7 -1
- package/src/styles/confirm.css +10 -13
- package/src/styles/drawer.css +9 -6
- package/src/styles/dropdown.css +14 -7
- package/src/styles/empty.css +1 -1
- package/src/styles/feedback.css +25 -16
- package/src/styles/footer.css +15 -6
- package/src/styles/input.css +28 -2
- package/src/styles/layout.css +14 -10
- package/src/styles/loading.css +129 -0
- package/src/styles/motion.css +21 -39
- package/src/styles/nav.css +14 -12
- package/src/styles/reduced-motion.css +21 -0
- package/src/styles/success.css +8 -8
- package/src/styles/table.css +20 -17
- package/src/styles/topbar.css +27 -12
- package/src/tokens/accents.css +10 -14
- package/src/tokens/tokens.css +95 -22
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/* ../src/styles/reduced-motion.css */
|
|
2
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3
|
+
html {
|
|
4
|
+
scroll-behavior: auto !important;
|
|
5
|
+
}
|
|
6
|
+
*,
|
|
7
|
+
::before,
|
|
8
|
+
::after {
|
|
9
|
+
animation-duration: 0.01ms !important;
|
|
10
|
+
animation-iteration-count: 1 !important;
|
|
11
|
+
transition-duration: 0.01ms !important;
|
|
12
|
+
scroll-behavior: auto !important;
|
|
13
|
+
}
|
|
14
|
+
[data-reveal] {
|
|
15
|
+
opacity: 1 !important;
|
|
16
|
+
transform: none !important;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
1
20
|
/* src/Modal.css */
|
|
2
21
|
.rx-scrim {
|
|
3
22
|
position: fixed;
|
package/react/dist/index.d.ts
CHANGED
|
@@ -12,9 +12,11 @@ type ButtonProps = {
|
|
|
12
12
|
iconRight?: string;
|
|
13
13
|
iconOnly?: boolean;
|
|
14
14
|
block?: boolean;
|
|
15
|
+
/** In flight: aria-busy, disabled, and the kit's indeterminate bars. */
|
|
16
|
+
busy?: boolean;
|
|
15
17
|
children?: ReactNode;
|
|
16
18
|
} & ButtonHTMLAttributes<HTMLButtonElement>;
|
|
17
|
-
declare function Button({ variant, size, icon, iconRight, iconOnly, block, children, type, ...rest }: ButtonProps): react.JSX.Element;
|
|
19
|
+
declare function Button({ variant, size, icon, iconRight, iconOnly, block, busy, children, type, disabled, ...rest }: ButtonProps): react.JSX.Element;
|
|
18
20
|
|
|
19
21
|
declare function Badge({ variant, children }: {
|
|
20
22
|
variant?: string;
|
|
@@ -55,4 +57,43 @@ declare function DataTable<T extends {
|
|
|
55
57
|
name: string;
|
|
56
58
|
}>({ columns, rows, pageSize, selected, onToggle, onTogglePage, }: DataTableProps<T>): react.JSX.Element;
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
type SkeletonProps = {
|
|
61
|
+
/** A count of bars, or explicit widths when a ragged prose edge matters. */
|
|
62
|
+
lines?: number | string[];
|
|
63
|
+
width?: string;
|
|
64
|
+
height?: string;
|
|
65
|
+
radius?: string;
|
|
66
|
+
className?: string;
|
|
67
|
+
};
|
|
68
|
+
declare function Skeleton({ lines, width, height, radius, className }: SkeletonProps): react.JSX.Element;
|
|
69
|
+
type SkeletonTableProps = {
|
|
70
|
+
rows?: number;
|
|
71
|
+
cols?: number;
|
|
72
|
+
head?: boolean;
|
|
73
|
+
};
|
|
74
|
+
declare function SkeletonTable({ rows, cols, head }: SkeletonTableProps): react.JSX.Element;
|
|
75
|
+
type BusyRegionProps = {
|
|
76
|
+
busy: boolean;
|
|
77
|
+
/** Spoken while it works. */
|
|
78
|
+
label?: string;
|
|
79
|
+
/** Spoken when it finishes β the specific line ("14 invoices") beats "Loaded". */
|
|
80
|
+
message?: string;
|
|
81
|
+
/** Placeholder while busy. Defaults to a three-bar skeleton. */
|
|
82
|
+
placeholder?: ReactNode;
|
|
83
|
+
className?: string;
|
|
84
|
+
children?: ReactNode;
|
|
85
|
+
};
|
|
86
|
+
declare function BusyRegion({ busy, label, message, placeholder, className, children, }: BusyRegionProps): react.JSX.Element;
|
|
87
|
+
type DeniedProps = {
|
|
88
|
+
title?: string;
|
|
89
|
+
sub?: string;
|
|
90
|
+
/** The scope or role the reader is missing, verbatim. */
|
|
91
|
+
need?: string;
|
|
92
|
+
icon?: string;
|
|
93
|
+
className?: string;
|
|
94
|
+
/** Buttons. Nothing is assumed about what a reader can do next. */
|
|
95
|
+
children?: ReactNode;
|
|
96
|
+
};
|
|
97
|
+
declare function Denied({ title, sub, need, icon, className, children, }: DeniedProps): react.JSX.Element;
|
|
98
|
+
|
|
99
|
+
export { Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Card, type Column, DataTable, type DataTableProps, Denied, type DeniedProps, Icon, Modal, type ModalProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps };
|
package/react/dist/index.js
CHANGED
|
@@ -24,8 +24,10 @@ function Button({
|
|
|
24
24
|
iconRight,
|
|
25
25
|
iconOnly,
|
|
26
26
|
block,
|
|
27
|
+
busy,
|
|
27
28
|
children,
|
|
28
29
|
type = "button",
|
|
30
|
+
disabled,
|
|
29
31
|
...rest
|
|
30
32
|
}) {
|
|
31
33
|
const cls = cx(
|
|
@@ -38,11 +40,27 @@ function Button({
|
|
|
38
40
|
const labelled = rest["aria-label"] != null || rest["aria-labelledby"] != null;
|
|
39
41
|
const fallback = typeof children === "string" && children.trim() ? children.trim() : icon2;
|
|
40
42
|
const named = iconOnly && !labelled && fallback ? { "aria-label": fallback, title: rest.title ?? fallback } : {};
|
|
41
|
-
return /* @__PURE__ */ jsxs(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
return /* @__PURE__ */ jsxs(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
type,
|
|
47
|
+
className: cls,
|
|
48
|
+
disabled: disabled || busy,
|
|
49
|
+
"aria-disabled": disabled || busy ? true : void 0,
|
|
50
|
+
"aria-busy": busy ? true : void 0,
|
|
51
|
+
...rest,
|
|
52
|
+
...named,
|
|
53
|
+
children: [
|
|
54
|
+
icon2 && /* @__PURE__ */ jsx2(Icon, { name: icon2 }),
|
|
55
|
+
!iconOnly && children != null && /* @__PURE__ */ jsx2("span", { children }),
|
|
56
|
+
iconRight && /* @__PURE__ */ jsx2(Icon, { name: iconRight }),
|
|
57
|
+
busy && /* @__PURE__ */ jsxs("span", { className: "ui-btn__bars", children: [
|
|
58
|
+
/* @__PURE__ */ jsx2("i", {}),
|
|
59
|
+
/* @__PURE__ */ jsx2("i", {})
|
|
60
|
+
] })
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
);
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
// src/primitives/Badge.tsx
|
|
@@ -244,11 +262,85 @@ function DataTable({
|
|
|
244
262
|
] })
|
|
245
263
|
] });
|
|
246
264
|
}
|
|
265
|
+
|
|
266
|
+
// src/Loading.tsx
|
|
267
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
268
|
+
var cx2 = (...a) => a.filter(Boolean).join(" ");
|
|
269
|
+
function Skeleton({ lines = 3, width, height, radius, className }) {
|
|
270
|
+
const widths = Array.isArray(lines) ? lines : null;
|
|
271
|
+
const n = widths ? widths.length : Math.max(1, lines);
|
|
272
|
+
const styleFor = (i) => {
|
|
273
|
+
const w = widths ? widths[i] : width;
|
|
274
|
+
if (!w && !height && !radius) return void 0;
|
|
275
|
+
return { ...w && { width: w }, ...height && { height }, ...radius && { borderRadius: radius } };
|
|
276
|
+
};
|
|
277
|
+
return /* @__PURE__ */ jsx7("div", { className: cx2("ui-skel", className), "aria-hidden": "true", children: Array.from({ length: n }, (_, i) => /* @__PURE__ */ jsx7("span", { className: "ui-skel__bar m-skeleton", style: styleFor(i) }, i)) });
|
|
278
|
+
}
|
|
279
|
+
function SkeletonTable({ rows = 5, cols = 4, head = true }) {
|
|
280
|
+
const cells = Array.from({ length: Math.max(1, cols) }, (_, i) => /* @__PURE__ */ jsx7("span", { className: "ui-skel__bar m-skeleton" }, i));
|
|
281
|
+
return /* @__PURE__ */ jsxs5(
|
|
282
|
+
"div",
|
|
283
|
+
{
|
|
284
|
+
className: "ui-skel ui-skel--table",
|
|
285
|
+
style: { "--skel-cols": Math.max(1, cols) },
|
|
286
|
+
"aria-hidden": "true",
|
|
287
|
+
children: [
|
|
288
|
+
head && /* @__PURE__ */ jsx7("div", { className: "ui-skel__row ui-skel__row--head", children: cells }),
|
|
289
|
+
Array.from({ length: Math.max(1, rows) }, (_, i) => /* @__PURE__ */ jsx7("div", { className: "ui-skel__row", children: cells }, i))
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
function BusyRegion({
|
|
295
|
+
busy,
|
|
296
|
+
label = "Loading\u2026",
|
|
297
|
+
message = "Loaded",
|
|
298
|
+
placeholder,
|
|
299
|
+
className,
|
|
300
|
+
children
|
|
301
|
+
}) {
|
|
302
|
+
return /* @__PURE__ */ jsxs5(
|
|
303
|
+
"div",
|
|
304
|
+
{
|
|
305
|
+
className: cx2("ui-busy", className),
|
|
306
|
+
role: "status",
|
|
307
|
+
"aria-live": "polite",
|
|
308
|
+
"aria-busy": busy,
|
|
309
|
+
children: [
|
|
310
|
+
/* @__PURE__ */ jsx7("span", { className: "ui-sr", children: busy ? label : message }),
|
|
311
|
+
/* @__PURE__ */ jsx7("div", { className: "ui-busy__body", children: busy ? placeholder ?? /* @__PURE__ */ jsx7(Skeleton, { lines: 3 }) : children })
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function Denied({
|
|
317
|
+
title = "You don\u2019t have access",
|
|
318
|
+
sub,
|
|
319
|
+
need,
|
|
320
|
+
icon: icon2 = "lock",
|
|
321
|
+
className,
|
|
322
|
+
children
|
|
323
|
+
}) {
|
|
324
|
+
return /* @__PURE__ */ jsxs5("div", { className: cx2("ui-denied", className), children: [
|
|
325
|
+
/* @__PURE__ */ jsx7("div", { className: "ui-denied__seal", children: /* @__PURE__ */ jsx7(Icon, { name: icon2 }) }),
|
|
326
|
+
/* @__PURE__ */ jsx7("div", { className: "ui-denied__title", children: title }),
|
|
327
|
+
sub && /* @__PURE__ */ jsx7("div", { className: "ui-denied__sub", children: sub }),
|
|
328
|
+
need && /* @__PURE__ */ jsxs5("div", { className: "ui-denied__need", children: [
|
|
329
|
+
"Needs ",
|
|
330
|
+
/* @__PURE__ */ jsx7("code", { className: "ui-code", children: need })
|
|
331
|
+
] }),
|
|
332
|
+
children && /* @__PURE__ */ jsx7("div", { className: "ui-denied__actions", children })
|
|
333
|
+
] });
|
|
334
|
+
}
|
|
247
335
|
export {
|
|
248
336
|
Badge,
|
|
337
|
+
BusyRegion,
|
|
249
338
|
Button,
|
|
250
339
|
Card,
|
|
251
340
|
DataTable,
|
|
341
|
+
Denied,
|
|
252
342
|
Icon,
|
|
253
|
-
Modal
|
|
343
|
+
Modal,
|
|
344
|
+
Skeleton,
|
|
345
|
+
SkeletonTable
|
|
254
346
|
};
|
package/src/assets/icons.js
CHANGED
|
@@ -1,34 +1,13 @@
|
|
|
1
|
-
// Line-icon set β 24Γ24, stroke=currentColor, 1.7 weight, round caps/joins
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// in any framework, and every glyph inherits `currentColor` + a consistent
|
|
5
|
-
// stroke so it sits right next to our type.
|
|
1
|
+
// Line-icon set β 24Γ24, stroke=currentColor, 1.7 weight, round caps/joins,
|
|
2
|
+
// Lucide house style. Delivery is inline SVG strings, so there is no runtime
|
|
3
|
+
// dependency and every glyph inherits `currentColor`.
|
|
6
4
|
//
|
|
7
5
|
// Each value is the INNER markup; icon() wraps it in the shared <svg>. Glyphs
|
|
8
6
|
// are grouped by domain β the flat ICONS map is what icon() looks up, and
|
|
9
7
|
// iconCategories drives the Storybook grid.
|
|
10
8
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// NAME it for what it depicts, not for the one place it is used: `trash`, not
|
|
14
|
-
// `deleteWorkspace`. camelCase, and a modifier follows its noun β `circleX`,
|
|
15
|
-
// `eyeOff`, `trendingUp` β so the family sorts together. A name is taken once;
|
|
16
|
-
// a second declaration of one is a gate failure, not a merge conflict.
|
|
17
|
-
//
|
|
18
|
-
// GROUP it by what it depicts, again rather than by caller. `chart` lives in
|
|
19
|
-
// DATA because it draws data, even when a comms panel is what renders it. If
|
|
20
|
-
// two groups both look right, the glyph belongs to the one whose other members
|
|
21
|
-
// it would sit beside in the catalogue. Groups are not tags: exactly one.
|
|
22
|
-
//
|
|
23
|
-
// PROVENANCE: the path comes from Lucide, unmodified, at the 24Γ24 / 1.7 house
|
|
24
|
-
// stroke β that is what keeps the set looking like one hand. Say which Lucide
|
|
25
|
-
// name it came from in the commit if the two differ. A hand-drawn path needs a
|
|
26
|
-
// reason in the commit message, because the next person cannot tell one from a
|
|
27
|
-
// traced one by looking.
|
|
28
|
-
//
|
|
29
|
-
// The gates: src/assets/icons.test.js holds one-group-per-glyph and the
|
|
30
|
-
// emitter's numbers, and stories/guidelines/iconography.test.js holds the
|
|
31
|
-
// icon-only list below. The written rules are on Guidelines / Iconography.
|
|
9
|
+
// Naming, grouping and provenance, and the gates that hold them:
|
|
10
|
+
// why: CONTRIBUTING.md#add-a-glyph
|
|
32
11
|
|
|
33
12
|
const NAV = {
|
|
34
13
|
chevronDown: '<path d="M6 9l6 6 6-6" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
@@ -3,7 +3,7 @@
|
|
|
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
5
|
// the published name docs/library.md documents.
|
|
6
|
-
// why: docs/
|
|
6
|
+
// why: docs/specification.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
|
|
@@ -3,18 +3,13 @@
|
|
|
3
3
|
// container.innerHTML = confirm({ title, body, confirmLabel, cancelLabel });
|
|
4
4
|
// wireConfirm(container); // scrim/Esc/answers + focus trap
|
|
5
5
|
//
|
|
6
|
-
// A page trigger opens it by id:
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// A page trigger opens it by id: <button data-confirm-open="ID">. Answering is
|
|
7
|
+
// the caller's job β a listener on [data-confirm-accept] is where the
|
|
8
|
+
// destructive work goes β and focus opens on the SAFE answer, so a reader who
|
|
9
|
+
// hits Enter out of habit keeps what they have.
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// the SAFE answer, never the destructive one, so a reader who hits Enter out of
|
|
13
|
-
// habit keeps what they have.
|
|
14
|
-
//
|
|
15
|
-
// Inertness, Escape and the focus trap come from ./overlay.js β one stack for
|
|
16
|
-
// every overlay on the page, so a confirm over a drawer never has to guess which
|
|
17
|
-
// of the two the keyboard belongs to.
|
|
11
|
+
// Inertness, Escape and the focus trap come from ./overlay.js: one stack for
|
|
12
|
+
// every overlay on the page.
|
|
18
13
|
import { button, esc } from './index.js';
|
|
19
14
|
import { OVERLAY_LAYER, adoptOverlay, focusablesIn, popOverlay, pushOverlay, returnFocus, syncOverlays } from './overlay.js';
|
|
20
15
|
|
|
@@ -24,23 +19,27 @@ const cx = (...a) => a.filter(Boolean).join(' ');
|
|
|
24
19
|
let _uid = 0;
|
|
25
20
|
const nextId = (p = 'confirm') => `${p}-${++_uid}`;
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The public factory. Returns an HTML string; wire it with wireConfirm().
|
|
24
|
+
*
|
|
25
|
+
* `specimen` renders open as a *picture* of the dialog β same markup, minus the
|
|
26
|
+
* data-confirm hook and aria-modal, so no wiring and no key handler can reach
|
|
27
|
+
* it. A documentation page shows several at once and none of them may own the
|
|
28
|
+
* page or answer Escape: an answered specimen would erase itself with nothing to
|
|
29
|
+
* bring it back, and three modal dialogs on one page trap the reader in the
|
|
30
|
+
* first. Use `open` when the dialog is real.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} [o]
|
|
33
|
+
* @param {string} [o.title] the question β also the accessible name
|
|
34
|
+
* @param {string} [o.body] the consequence β the accessible description
|
|
35
|
+
* @param {string} [o.confirmLabel] the destructive answer (default 'Confirm')
|
|
36
|
+
* @param {string} [o.cancelLabel] the safe answer (default 'Cancel')
|
|
37
|
+
* @param {string} [o.variant] 'danger' (default) | 'primary'
|
|
38
|
+
* @param {boolean} [o.open] render already-open, as a real dialog
|
|
39
|
+
* @param {boolean} [o.specimen] render open as a picture of the dialog
|
|
40
|
+
* @param {string} [o.id] root id a [data-confirm-open] trigger targets
|
|
41
|
+
* @returns {string} html
|
|
42
|
+
*/
|
|
44
43
|
export function confirm({
|
|
45
44
|
title = 'Are you sure?', body = '', confirmLabel = 'Confirm', cancelLabel = 'Cancel',
|
|
46
45
|
variant = 'danger', id, open = false, specimen = false,
|
package/src/components/drawer.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
// Drawer β the kit's edge-anchored overlay panel
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// left|right, full-width for top|bottom.
|
|
1
|
+
// Drawer β the kit's edge-anchored overlay panel: a panel that slides in from
|
|
2
|
+
// any screen edge over a scrim. Header / scrollable body / footer actions, sizes
|
|
3
|
+
// sm|md|lg along the slide axis.
|
|
5
4
|
//
|
|
6
5
|
// container.innerHTML = drawer({ side: 'right', title: 'Filters', body });
|
|
7
6
|
// wireDrawer(container); // scrim/Esc/close-button + focus trap
|
|
8
7
|
//
|
|
9
|
-
// A page trigger opens it by id:
|
|
10
|
-
//
|
|
11
|
-
// open, or `specimen: true` for a picture of one on a documentation page.
|
|
12
|
-
//
|
|
13
|
-
// Inertness, Escape and the focus trap are shared with confirm(): both are
|
|
8
|
+
// A page trigger opens it by id: <button data-drawer-open="ID">. Inertness,
|
|
9
|
+
// Escape and the focus trap are shared with confirm(): both are
|
|
14
10
|
// "content over a scrim, focus-trapped, Esc-dismissable", and they push onto one
|
|
15
|
-
// stack in ./overlay.js so the two can never disagree about which of them the
|
|
16
|
-
// keyboard
|
|
11
|
+
// stack in ./overlay.js, so the two can never disagree about which of them the
|
|
12
|
+
// keyboard belongs to.
|
|
17
13
|
import { esc, icon } from './index.js';
|
|
18
14
|
import { OVERLAY_LAYER, adoptOverlay, focusablesIn, popOverlay, pushOverlay, returnFocus, syncOverlays } from './overlay.js';
|
|
19
15
|
|
|
@@ -23,23 +19,26 @@ const cx = (...a) => a.filter(Boolean).join(' ');
|
|
|
23
19
|
let _uid = 0;
|
|
24
20
|
const nextId = (p = 'drawer') => `${p}-${++_uid}`;
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The public factory. Returns an HTML string; wire it with wireDrawer().
|
|
24
|
+
*
|
|
25
|
+
* `specimen` renders open as a *picture* of the panel β see confirm() for why a
|
|
26
|
+
* documentation page wants that. Use `open` when the drawer is real.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} [o]
|
|
29
|
+
* @param {string} [o.side] 'right' (default) | 'left' | 'top' | 'bottom'
|
|
30
|
+
* @param {string} [o.size] 'sm' | 'md' (default) | 'lg', along the slide axis
|
|
31
|
+
* @param {string} [o.title] header heading, also the accessible name
|
|
32
|
+
* @param {string} [o.body] scrollable body HTML (trusted markup)
|
|
33
|
+
* @param {string} [o.footer] pinned footer actions HTML (trusted markup)
|
|
34
|
+
* @param {boolean} [o.open] render already-open, as a real panel
|
|
35
|
+
* @param {boolean} [o.specimen] render open as a picture of the panel
|
|
36
|
+
* @param {string} [o.id] root id a [data-drawer-open] trigger targets
|
|
37
|
+
* @param {string} [o.ariaLabel] accessible name when there is no visible title
|
|
38
|
+
* @param {boolean} [o.dismissible] close button + scrim/Esc dismiss (default true)
|
|
39
|
+
* @param {string} [o.closeLabel] accessible name for the close button
|
|
40
|
+
* @returns {string} html
|
|
41
|
+
*/
|
|
43
42
|
export function drawer({
|
|
44
43
|
side = 'right', size = 'md', title, body = '', footer = '',
|
|
45
44
|
open = false, specimen = false, id, ariaLabel, dismissible = true, closeLabel = 'Close',
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
// Dropdown β the kit's one popover-list primitive. A trigger
|
|
2
|
-
//
|
|
3
|
-
// description, optional leading icon, optional trailing badge, selected +
|
|
4
|
-
// disabled state). Two flavours share the same panel + the same open/close JS:
|
|
1
|
+
// Dropdown β the kit's one popover-list primitive. A trigger opens a panel of
|
|
2
|
+
// item rows; two flavours share the same panel and the same open/close JS:
|
|
5
3
|
//
|
|
6
4
|
// variant: 'select' β role="listbox" / role="option", value shown in trigger
|
|
7
5
|
// variant: 'menu' β role="menu" / role="menuitem", action list
|
|
8
6
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// one open/close/click-outside/Esc/keyboard implementation in the kit.
|
|
7
|
+
// The name is deliberate: `menu` implies actions only and `select` implies a
|
|
8
|
+
// form control bound to a value, and this factory is the umbrella both are
|
|
9
|
+
// specialisations of. The topbar's version switcher and account menu are thin
|
|
10
|
+
// consumers of the SAME wiring, so there is one open/close/Esc/keyboard
|
|
11
|
+
// implementation in the kit.
|
|
15
12
|
//
|
|
16
13
|
// container.innerHTML = dropdown({ label: 'version:', value: 'β¦', items });
|
|
17
|
-
// wireDropdown(container); // or let wireTopbar() do it
|
|
14
|
+
// wireDropdown(container); // or let wireTopbar() do it
|
|
18
15
|
import { esc, icon } from './index.js';
|
|
19
16
|
|
|
20
17
|
const cx = (...a) => a.filter(Boolean).join(' ');
|
|
@@ -66,18 +63,24 @@ function ddBody({ items, sections }, listbox) {
|
|
|
66
63
|
return (items || []).map((it) => ddItem(it, listbox)).join('');
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
/**
|
|
67
|
+
* The public factory. Returns an HTML string; wire it with wireDropdown().
|
|
68
|
+
*
|
|
69
|
+
* @param {object} [o]
|
|
70
|
+
* @param {string} [o.label] muted prefix in the trigger (e.g. "version:")
|
|
71
|
+
* @param {string} [o.value] current value shown in the trigger
|
|
72
|
+
* @param {string} [o.placeholder] shown when there is no value
|
|
73
|
+
* @param {string} [o.variant] 'select' (listbox) | 'menu' (inferred from items)
|
|
74
|
+
* @param {Array} [o.items] [{ label, value?, description?, icon?, badge?, selected?, disabled?, href?, danger? }]
|
|
75
|
+
* @param {Array} [o.sections] [{ label, items }] β grouped alternative to items
|
|
76
|
+
* @param {string} [o.header] raw HTML pinned to the top of the panel
|
|
77
|
+
* @param {string} [o.footer] raw HTML pinned to the bottom of the panel
|
|
78
|
+
* @param {string} [o.align] 'start' (default) | 'end' β the edge the panel hugs
|
|
79
|
+
* @param {boolean|number} [o.scroll] true, or a maxHeight in px, to cap and scroll
|
|
80
|
+
* @param {boolean} [o.open] render already-open (handy for screenshots)
|
|
81
|
+
* @param {string} [o.ariaLabel] accessible name for the panel and trigger
|
|
82
|
+
* @returns {string} html
|
|
83
|
+
*/
|
|
81
84
|
export function dropdown({
|
|
82
85
|
label, value, placeholder = 'Selectβ¦', variant, items, sections,
|
|
83
86
|
header = '', footer = '', triggerContent, triggerClass = '', chevron = true,
|
package/src/components/index.js
CHANGED
|
@@ -66,21 +66,17 @@ export function card({ title, sub, body = '', variant, pad, icon: ic } = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// ---- Segmented control ---------------------------------------------------
|
|
69
|
-
// A strip of mutually exclusive toggle buttons
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// options, Home/End to jump to the ends, and `aria-pressed` to say which is on.
|
|
74
|
-
// wireTopbar() ships that keyboard behaviour (see topbar.js); the markup here
|
|
75
|
-
// carries the state it reads.
|
|
69
|
+
// A strip of mutually exclusive toggle buttons.
|
|
70
|
+
// It is NOT a tablist: it controls no panel, so it must not announce one.
|
|
71
|
+
// `role="toolbar"` with a roving tabindex is the honest shape, and wireTopbar()
|
|
72
|
+
// ships the keyboard behaviour.
|
|
76
73
|
//
|
|
77
|
-
// When the pill drives a real change of view
|
|
78
|
-
//
|
|
74
|
+
// When the pill drives a real change of view, reach for tabs() instead β
|
|
75
|
+
// that one owns panels and earns the tab announcement.
|
|
79
76
|
//
|
|
80
|
-
// `ariaLabel` names the strip. `name` seeds data-seg
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
// switchToggle() defaults its label.
|
|
77
|
+
// `ariaLabel` names the strip. `name` seeds data-seg and is deliberately
|
|
78
|
+
// not an accessible name β it is an identifier, not prose, defaulting to
|
|
79
|
+
// "Options" so a strip is never left unlabelled.
|
|
84
80
|
export function segmented({ options = [], active = 0, size, block, name = 'seg', ariaLabel = 'Options' } = {}) {
|
|
85
81
|
const cls = cx('ui-seg', size && `ui-seg--${size}`, block && 'ui-seg--block');
|
|
86
82
|
// Exactly one option holds the Tab stop. If `active` points nowhere (nothing
|