@atelic-action/ui 2.0.0 → 2.1.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
CHANGED
|
@@ -20,6 +20,7 @@ Every export, grouped by the entry it is imported from. The gallery column links
|
|
|
20
20
|
| `Brand`, `BrandLogo`, `CallToAction`, `HoursRow`, `MenuContact`, `NavLink`, `Phone`, `PostalAddress`, `SiteLink`, `SocialLink`, `PageStop` | `@atelic-action/ui/chrome` | The shapes the chrome renders from (types) | none |
|
|
21
21
|
| `NotFound` | `@atelic-action/ui/components` | The body of the page an unknown path renders | [#not-found](https://ui.atelic.me/#not-found) |
|
|
22
22
|
| `Checklist` | `@atelic-action/ui/components` | A short list of checks, each passed or failed | [#checklist](https://ui.atelic.me/#checklist) |
|
|
23
|
+
| `IconList` | `@atelic-action/ui/components` | A list whose bullets are icons, one for the list or one per item, each in a tone | [#icon-list](https://ui.atelic.me/#icon-list) |
|
|
23
24
|
| `DataTable` | `@atelic-action/ui/components` | A table whose first column names each row, sortable, stacking on a phone | [#data-table](https://ui.atelic.me/#data-table) |
|
|
24
25
|
| `StatRow` | `@atelic-action/ui/components` | A row of large figures with periods, deltas, and trends | [#stat-row](https://ui.atelic.me/#stat-row) |
|
|
25
26
|
| `Callout` | `@atelic-action/ui/components` | One finding: an icon in its tone, the title, the explainer | [#callout](https://ui.atelic.me/#callout) |
|
|
@@ -694,7 +695,8 @@ The blocks a page composes, each styled in `styles/components.css` inside the `a
|
|
|
694
695
|
| Component | What it is | Notes |
|
|
695
696
|
|---|---|---|
|
|
696
697
|
| `Checklist` | A short list of checks, each passed or failed | Two columns when there is room, one on a phone. `passLabel` and `failLabel` name the icons for a screen reader |
|
|
697
|
-
| `
|
|
698
|
+
| `IconList` | A list whose bullets are icons | `icon` and `tone` on the list set every bullet; an item's own override them. The bullet is decoration, so a screen reader hears only the items. A dot when no icon is set; the text's color when no tone is |
|
|
699
|
+
| `DataTable` | A table whose first column names each row, sortable by any column marked `sortable` | A click on a heading sorts on a wide screen. On a phone each row stacks, every fact wearing its column's label, and a dropdown sorts. A cell is a node, or `{ display, sort }` when what it shows is not what it sorts by; a formatted number in a string (`"$12,047"`, `"0.24%"`) sorts as a number. It renders in `defaultSort` on the server. Every other row sits on a tinted ground; `striped={false}` turns that off. A column marked `fit` hugs its content, for a swatch or a mark beside a figure |
|
|
698
700
|
| `StatRow` | A row of large figures, each over what it counts and its `period`. `variant="quiet"` is the glance row for a working page: a light figure in the text color over a small capitalized label, hairlines between; `unit` sets a figure's unit small beside it | `trend` draws an arrow before a figure that is itself a change. `deltas` list comparisons beneath: an arrow, how far, against what (`basis`), and the earlier figure (`was`), each in its own tone, since a spend falling and a cost falling read differently |
|
|
699
701
|
| `Callout` | One finding: an icon in its tone beside the title, the explainer under it | `variant="box"` sets it apart on a tinted ground, for something missing or owed |
|
|
700
702
|
| `Disclosure` | A section the reader opens and closes, in two forms chosen by whether `open` is passed | Controlled (`open` and `onToggle`), it is an app's card: the caller remembers what was folded, a closed body is not rendered, the label is the toggle, `meta` (a count, a flag) sits beside it outside its name, and `headingLevel` makes the label a real heading. Uncontrolled, it is a document's fold: a native `details` with a `summary` line that carries the point and `defaultOpen`, so it works before any script loads, find in page opens it, and it prints open. `Collapsible`, the fold's old name, was folded in at 0.18.0 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atelic-action/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Shared UI for the Atelic templates: site chrome, base styles, the component library, the access gate, and the app shell they install",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -13,6 +13,8 @@ export interface DataTableColumn {
|
|
|
13
13
|
sortable?: boolean;
|
|
14
14
|
/** The direction a first click sorts in; numbers usually read best largest first. */
|
|
15
15
|
firstSort?: SortDirection;
|
|
16
|
+
/** The column hugs its content (a swatch, a mark) instead of sharing the width. */
|
|
17
|
+
fit?: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/** A cell whose display is not what it sorts by, e.g. an icon over a number. */
|
|
@@ -37,6 +39,8 @@ export interface DataTableProps {
|
|
|
37
39
|
defaultSort?: { column: number; direction: SortDirection };
|
|
38
40
|
/** The label on the phone's sort control. */
|
|
39
41
|
sortLabel?: string;
|
|
42
|
+
/** Every other row on a tinted ground, so a wide table reads across. On by default. */
|
|
43
|
+
striped?: boolean;
|
|
40
44
|
className?: string;
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -71,6 +75,7 @@ export function DataTable({
|
|
|
71
75
|
rows,
|
|
72
76
|
defaultSort,
|
|
73
77
|
sortLabel = "Sort by",
|
|
78
|
+
striped = true,
|
|
74
79
|
className,
|
|
75
80
|
}: DataTableProps) {
|
|
76
81
|
const [sort, setSort] = useState(defaultSort ?? null);
|
|
@@ -102,7 +107,7 @@ export function DataTable({
|
|
|
102
107
|
};
|
|
103
108
|
|
|
104
109
|
return (
|
|
105
|
-
<div className={["data-table", className].filter(Boolean).join(" ")}>
|
|
110
|
+
<div className={["data-table", striped && "is-striped", className].filter(Boolean).join(" ")}>
|
|
106
111
|
{anySortable && (
|
|
107
112
|
<div className="data-table-sort">
|
|
108
113
|
<label htmlFor={selectId}>{sortLabel}</label>
|
|
@@ -135,21 +140,20 @@ export function DataTable({
|
|
|
135
140
|
{columns.map((column, index) => {
|
|
136
141
|
const active = sort?.column === index;
|
|
137
142
|
const Icon = !active ? ArrowUpDown : sort?.direction === "asc" ? ArrowUp : ArrowDown;
|
|
143
|
+
const head = {
|
|
144
|
+
"data-align": column.align ?? "left",
|
|
145
|
+
"data-fit": column.fit || undefined,
|
|
146
|
+
"aria-sort": column.sortable
|
|
147
|
+
? active
|
|
148
|
+
? sort?.direction === "asc"
|
|
149
|
+
? ("ascending" as const)
|
|
150
|
+
: ("descending" as const)
|
|
151
|
+
: ("none" as const)
|
|
152
|
+
: undefined,
|
|
153
|
+
};
|
|
138
154
|
return (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
key={column.label}
|
|
142
|
-
data-align={column.align ?? "left"}
|
|
143
|
-
aria-sort={
|
|
144
|
-
column.sortable
|
|
145
|
-
? active
|
|
146
|
-
? sort?.direction === "asc"
|
|
147
|
-
? "ascending"
|
|
148
|
-
: "descending"
|
|
149
|
-
: "none"
|
|
150
|
-
: undefined
|
|
151
|
-
}
|
|
152
|
-
>
|
|
155
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: columns sit in order, and two may share a blank label
|
|
156
|
+
<th scope="col" key={index} {...head}>
|
|
153
157
|
{column.sortable ? (
|
|
154
158
|
<button
|
|
155
159
|
type="button"
|
|
@@ -171,16 +175,19 @@ export function DataTable({
|
|
|
171
175
|
{sorted.map(({ row, index }) => (
|
|
172
176
|
<tr key={index} data-tone={row.tone}>
|
|
173
177
|
{row.cells.map((cell, c) => {
|
|
174
|
-
const align = columns[c]?.align ?? "left";
|
|
175
178
|
const body = isValueCell(cell) ? cell.display : cell;
|
|
179
|
+
const shape = {
|
|
180
|
+
"data-align": columns[c]?.align ?? "left",
|
|
181
|
+
"data-fit": columns[c]?.fit || undefined,
|
|
182
|
+
};
|
|
176
183
|
return c === 0 ? (
|
|
177
184
|
// biome-ignore lint/suspicious/noArrayIndexKey: cells sit in column order
|
|
178
|
-
<th scope="row" key={c}
|
|
185
|
+
<th scope="row" key={c} {...shape}>
|
|
179
186
|
{body}
|
|
180
187
|
</th>
|
|
181
188
|
) : (
|
|
182
189
|
// biome-ignore lint/suspicious/noArrayIndexKey: cells sit in column order
|
|
183
|
-
<td key={c}
|
|
190
|
+
<td key={c} {...shape} data-label={columns[c]?.label}>
|
|
184
191
|
{body}
|
|
185
192
|
</td>
|
|
186
193
|
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Dot } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { Tone } from "./tone";
|
|
4
|
+
|
|
5
|
+
export interface IconListItem {
|
|
6
|
+
/** The item itself. */
|
|
7
|
+
body: ReactNode;
|
|
8
|
+
/** The icon it leads with, e.g. a lucide icon at size 16; the list's when left out. */
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
/** The tone its bullet reads in; the list's when left out. */
|
|
11
|
+
tone?: Tone;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IconListProps {
|
|
15
|
+
items: IconListItem[];
|
|
16
|
+
/** The bullet every item without its own leads with; a dot when left out. */
|
|
17
|
+
icon?: ReactNode;
|
|
18
|
+
/** The tone every bullet without its own reads in; the text's color when left out. */
|
|
19
|
+
tone?: Tone;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A list whose bullets are icons: one for the whole list, or one per item,
|
|
25
|
+
* each in a tone set the same two ways. The bullets are decoration; the
|
|
26
|
+
* items carry the meaning, so a screen reader hears the list and not the
|
|
27
|
+
* icons.
|
|
28
|
+
*/
|
|
29
|
+
export function IconList({ items, icon, tone, className }: IconListProps) {
|
|
30
|
+
return (
|
|
31
|
+
<ul className={["icon-list", className].filter(Boolean).join(" ")} data-tone={tone}>
|
|
32
|
+
{items.map((item, i) => (
|
|
33
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: items are static content in a fixed order
|
|
34
|
+
<li key={i} data-tone={item.tone}>
|
|
35
|
+
<span className="icon-list-bullet" aria-hidden="true">
|
|
36
|
+
{item.icon ?? icon ?? <Dot size={16} strokeWidth={4} />}
|
|
37
|
+
</span>
|
|
38
|
+
<span className="icon-list-body">{item.body}</span>
|
|
39
|
+
</li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -48,8 +48,8 @@ export function StackedBar({ segments, showKey = true, className }: StackedBarPr
|
|
|
48
48
|
{segments.map((segment) => (
|
|
49
49
|
<li key={segment.label} data-tone={segment.tone} style={fill(segment)}>
|
|
50
50
|
<span className="chart-swatch" aria-hidden="true" />
|
|
51
|
-
<b>{figure(segment)}</b>
|
|
52
51
|
<span>{segment.label}</span>
|
|
52
|
+
<b>{figure(segment)}</b>
|
|
53
53
|
</li>
|
|
54
54
|
))}
|
|
55
55
|
</ul>
|
package/src/components/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export {
|
|
|
23
23
|
type DisclosureProps,
|
|
24
24
|
} from "./Disclosure";
|
|
25
25
|
export { GateLogin, type GateLoginProps } from "./GateLogin";
|
|
26
|
+
export { IconList, type IconListItem, type IconListProps } from "./IconList";
|
|
26
27
|
export { NotFound, type NotFoundProps } from "./NotFound";
|
|
27
28
|
export { StackedBar, type StackedBarProps, type StackedBarSegment } from "./StackedBar";
|
|
28
29
|
export { describeStarRating, StarRating, type StarRatingProps } from "./StarRating";
|
|
@@ -390,7 +390,34 @@
|
|
|
390
390
|
color: var(--ink-soft);
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/* ----
|
|
393
|
+
/* ---- IconList: a list whose bullets are icons, each in a tone ---- */
|
|
394
|
+
.atelic-ui .icon-list {
|
|
395
|
+
list-style: none;
|
|
396
|
+
margin: 0;
|
|
397
|
+
padding: 0;
|
|
398
|
+
display: grid;
|
|
399
|
+
gap: 8px;
|
|
400
|
+
}
|
|
401
|
+
.atelic-ui .icon-list li {
|
|
402
|
+
display: grid;
|
|
403
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
404
|
+
column-gap: 10px;
|
|
405
|
+
align-items: start;
|
|
406
|
+
line-height: 1.45;
|
|
407
|
+
text-wrap: pretty;
|
|
408
|
+
}
|
|
409
|
+
/* A list with no tone of its own reads in the text color, whatever tone its ancestor carries; an item's own tone still wins on its row. */
|
|
410
|
+
.atelic-ui .icon-list:not([data-tone]) {
|
|
411
|
+
--tone: currentColor;
|
|
412
|
+
}
|
|
413
|
+
.atelic-ui .icon-list-bullet {
|
|
414
|
+
display: inline-flex;
|
|
415
|
+
margin-top: 0.2em;
|
|
416
|
+
line-height: 0;
|
|
417
|
+
color: var(--tone, currentColor);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* ---- The charts' shared key: a swatch, a label, a figure ---- */
|
|
394
421
|
.atelic-ui .chart-key {
|
|
395
422
|
list-style: none;
|
|
396
423
|
margin: 10px 0 0;
|
|
@@ -619,6 +646,9 @@
|
|
|
619
646
|
border-bottom: 0;
|
|
620
647
|
padding-bottom: 0;
|
|
621
648
|
}
|
|
649
|
+
.atelic-ui .data-table.is-striped tbody tr:nth-child(even) {
|
|
650
|
+
background: color-mix(in srgb, var(--ink) 3%, transparent);
|
|
651
|
+
}
|
|
622
652
|
.atelic-ui .data-table tbody th {
|
|
623
653
|
width: 100%;
|
|
624
654
|
text-align: left;
|
|
@@ -686,6 +716,10 @@
|
|
|
686
716
|
.atelic-ui .data-table [data-align="center"] {
|
|
687
717
|
text-align: center;
|
|
688
718
|
}
|
|
719
|
+
.atelic-ui .data-table [data-fit] {
|
|
720
|
+
width: 1%;
|
|
721
|
+
white-space: nowrap;
|
|
722
|
+
}
|
|
689
723
|
.atelic-ui .data-table [data-align="right"] {
|
|
690
724
|
text-align: right;
|
|
691
725
|
}
|
|
@@ -719,11 +753,22 @@
|
|
|
719
753
|
color: var(--ui-primary, var(--primary));
|
|
720
754
|
}
|
|
721
755
|
.atelic-ui .data-table thead th button svg {
|
|
756
|
+
flex: none;
|
|
722
757
|
opacity: 0.55;
|
|
723
758
|
}
|
|
724
759
|
.atelic-ui .data-table thead th button[data-active] svg {
|
|
725
760
|
opacity: 1;
|
|
726
761
|
}
|
|
762
|
+
/* Striped, the cells take side padding so the band reads, the last column included. */
|
|
763
|
+
.atelic-ui .data-table.is-striped th,
|
|
764
|
+
.atelic-ui .data-table.is-striped td {
|
|
765
|
+
padding-left: 10px;
|
|
766
|
+
padding-right: 10px;
|
|
767
|
+
}
|
|
768
|
+
.atelic-ui .data-table.is-striped th:last-child,
|
|
769
|
+
.atelic-ui .data-table.is-striped td:last-child {
|
|
770
|
+
padding-right: 10px;
|
|
771
|
+
}
|
|
727
772
|
.atelic-ui .data-table tbody tr:last-child th,
|
|
728
773
|
.atelic-ui .data-table tbody tr:last-child td {
|
|
729
774
|
border-bottom: 0;
|