@hackcove/ui-react 0.0.0-stage → 0.8.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/LICENSE +21 -0
- package/MIGRATING.md +116 -0
- package/README.md +400 -2
- package/dist/AppShell.d.ts +97 -0
- package/dist/AppShell.js +106 -0
- package/dist/Avatar.d.ts +49 -0
- package/dist/Avatar.js +65 -0
- package/dist/AvatarStack.d.ts +29 -0
- package/dist/AvatarStack.js +26 -0
- package/dist/Badge.d.ts +14 -0
- package/dist/Badge.js +11 -0
- package/dist/Breadcrumbs.d.ts +28 -0
- package/dist/Breadcrumbs.js +30 -0
- package/dist/BulletList.d.ts +4 -0
- package/dist/BulletList.js +5 -0
- package/dist/Button.d.ts +78 -0
- package/dist/Button.js +133 -0
- package/dist/ButtonGroup.d.ts +15 -0
- package/dist/ButtonGroup.js +10 -0
- package/dist/Callout.d.ts +24 -0
- package/dist/Callout.js +28 -0
- package/dist/Card.d.ts +95 -0
- package/dist/Card.js +134 -0
- package/dist/CardBadge.d.ts +7 -0
- package/dist/CardBadge.js +12 -0
- package/dist/CardParts.d.ts +25 -0
- package/dist/CardParts.js +25 -0
- package/dist/Checkbox.d.ts +29 -0
- package/dist/Checkbox.js +36 -0
- package/dist/Chevron.d.ts +5 -0
- package/dist/Chevron.js +6 -0
- package/dist/Cluster.d.ts +51 -0
- package/dist/Cluster.js +38 -0
- package/dist/Code.d.ts +4 -0
- package/dist/Code.js +5 -0
- package/dist/CodeBlock.d.ts +27 -0
- package/dist/CodeBlock.js +12 -0
- package/dist/CodeExample.d.ts +17 -0
- package/dist/CodeExample.js +13 -0
- package/dist/CodeText.d.ts +7 -0
- package/dist/CodeText.js +21 -0
- package/dist/ColourSchemeToggle.d.ts +34 -0
- package/dist/ColourSchemeToggle.js +50 -0
- package/dist/Container.d.ts +22 -0
- package/dist/Container.js +13 -0
- package/dist/CopyButton.d.ts +17 -0
- package/dist/CopyButton.js +13 -0
- package/dist/CopyLine.d.ts +9 -0
- package/dist/CopyLine.js +7 -0
- package/dist/DateCalendar.d.ts +10 -0
- package/dist/DateCalendar.js +45 -0
- package/dist/DateField.d.ts +87 -0
- package/dist/DateField.js +100 -0
- package/dist/DateFieldCalendar.d.ts +6 -0
- package/dist/DateFieldCalendar.js +35 -0
- package/dist/Dialog.d.ts +26 -0
- package/dist/Dialog.js +18 -0
- package/dist/Disclosure.d.ts +15 -0
- package/dist/Disclosure.js +12 -0
- package/dist/Divider.d.ts +4 -0
- package/dist/Divider.js +5 -0
- package/dist/Drawer.d.ts +29 -0
- package/dist/Drawer.js +22 -0
- package/dist/EmptyState.d.ts +18 -0
- package/dist/EmptyState.js +14 -0
- package/dist/Field.d.ts +21 -0
- package/dist/Field.js +19 -0
- package/dist/FieldDialog.d.ts +28 -0
- package/dist/FieldDialog.js +34 -0
- package/dist/FileDrop.d.ts +17 -0
- package/dist/FileDrop.js +12 -0
- package/dist/FilterGroup.d.ts +7 -0
- package/dist/FilterGroup.js +6 -0
- package/dist/FormRow.d.ts +42 -0
- package/dist/FormRow.js +33 -0
- package/dist/Icon.d.ts +20 -0
- package/dist/Icon.js +13 -0
- package/dist/IconButton.d.ts +57 -0
- package/dist/IconButton.js +51 -0
- package/dist/Input.d.ts +22 -0
- package/dist/Input.js +22 -0
- package/dist/InputShell.d.ts +16 -0
- package/dist/InputShell.js +26 -0
- package/dist/Kbd.d.ts +6 -0
- package/dist/Kbd.js +5 -0
- package/dist/KeyValueList.d.ts +23 -0
- package/dist/KeyValueList.js +19 -0
- package/dist/Lightbox.d.ts +28 -0
- package/dist/Lightbox.js +44 -0
- package/dist/ListRow.d.ts +109 -0
- package/dist/ListRow.js +92 -0
- package/dist/Markdown.d.ts +9 -0
- package/dist/Markdown.js +43 -0
- package/dist/MetaList.d.ts +23 -0
- package/dist/MetaList.js +14 -0
- package/dist/OutlineRegion.d.ts +43 -0
- package/dist/OutlineRegion.js +27 -0
- package/dist/PageFade.d.ts +26 -0
- package/dist/PageFade.js +39 -0
- package/dist/PageHeader.d.ts +24 -0
- package/dist/PageHeader.js +14 -0
- package/dist/PageLayouts.d.ts +130 -0
- package/dist/PageLayouts.js +86 -0
- package/dist/Pill.d.ts +53 -0
- package/dist/Pill.js +34 -0
- package/dist/ProgressBar.d.ts +32 -0
- package/dist/ProgressBar.js +32 -0
- package/dist/RadioGroup.d.ts +48 -0
- package/dist/RadioGroup.js +39 -0
- package/dist/SearchInput.d.ts +8 -0
- package/dist/SearchInput.js +40 -0
- package/dist/SegmentedControl.d.ts +20 -0
- package/dist/SegmentedControl.js +31 -0
- package/dist/Select.d.ts +5 -0
- package/dist/Select.js +9 -0
- package/dist/SplitBar.d.ts +27 -0
- package/dist/SplitBar.js +15 -0
- package/dist/Stack.d.ts +70 -0
- package/dist/Stack.js +47 -0
- package/dist/StackedBar.d.ts +56 -0
- package/dist/StackedBar.js +35 -0
- package/dist/StatTile.d.ts +21 -0
- package/dist/StatTile.js +22 -0
- package/dist/StatusPages.d.ts +38 -0
- package/dist/StatusPages.js +57 -0
- package/dist/Steps.d.ts +17 -0
- package/dist/Steps.js +14 -0
- package/dist/StickyBar.d.ts +12 -0
- package/dist/StickyBar.js +10 -0
- package/dist/Table.d.ts +142 -0
- package/dist/Table.js +138 -0
- package/dist/Tabs.d.ts +40 -0
- package/dist/Tabs.js +49 -0
- package/dist/TextLink.d.ts +51 -0
- package/dist/TextLink.js +37 -0
- package/dist/Textarea.d.ts +5 -0
- package/dist/Textarea.js +7 -0
- package/dist/Timeline.d.ts +27 -0
- package/dist/Timeline.js +29 -0
- package/dist/Video.d.ts +13 -0
- package/dist/Video.js +9 -0
- package/dist/colourScheme.d.ts +21 -0
- package/dist/colourScheme.js +53 -0
- package/dist/control.d.ts +31 -0
- package/dist/control.js +38 -0
- package/dist/cx.d.ts +10 -0
- package/dist/cx.js +12 -0
- package/dist/highlight.d.ts +16 -0
- package/dist/highlight.js +292 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +66 -0
- package/dist/isoDate.d.ts +5 -0
- package/dist/isoDate.js +16 -0
- package/dist/lift.harness.d.ts +2 -0
- package/dist/lift.harness.js +3 -0
- package/dist/link.d.ts +50 -0
- package/dist/link.js +25 -0
- package/dist/modal.d.ts +11 -0
- package/dist/modal.js +41 -0
- package/dist/props.d.ts +14 -0
- package/dist/props.js +1 -0
- package/dist/shownDate.d.ts +45 -0
- package/dist/shownDate.js +88 -0
- package/dist/size.d.ts +6 -0
- package/dist/size.js +1 -0
- package/dist/switcher.d.ts +88 -0
- package/dist/switcher.js +71 -0
- package/dist/typography.d.ts +123 -0
- package/dist/typography.js +120 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.js +27 -0
- package/package.json +91 -4
package/dist/Table.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { CalloutTone } from "./Callout.js";
|
|
3
|
+
import { type TextTone } from "./typography.js";
|
|
4
|
+
type Align = "start" | "end";
|
|
5
|
+
/**
|
|
6
|
+
* The text of a cell: its weight and colour. `default`, `strong` and `muted` are the plain ones; the
|
|
7
|
+
* rest are the Callout's tones, for an amount that carries a meaning (a gain, an overspend). Colour
|
|
8
|
+
* alone does not say it, so a signed figure or a word says it too.
|
|
9
|
+
*/
|
|
10
|
+
export type CellTone = "default" | "strong" | "muted" | CalloutTone;
|
|
11
|
+
type TableVariant = "card" | "plain";
|
|
12
|
+
/**
|
|
13
|
+
* The status of a row: the Callout's tones, plus `muted`. A toned row gets a stripe on its leading
|
|
14
|
+
* edge and the tone's soft fill; `muted` fades the row instead, for one that matters less. The
|
|
15
|
+
* stripe takes the tone's text colour, which every theme already remaps, so no new palette entry.
|
|
16
|
+
*/
|
|
17
|
+
export type RowTone = "neutral" | "info" | "positive" | "warn" | "danger" | "muted";
|
|
18
|
+
/** The row's status mark: a `tone` (stripe and fill, or faded) or a `stripe` alone, never both. */
|
|
19
|
+
type RowMark = {
|
|
20
|
+
/** The row's status: a stripe on the leading edge plus the tone's soft fill, or `muted` to fade the row. Give `tone` or `stripe`, not both. */
|
|
21
|
+
tone?: RowTone | undefined;
|
|
22
|
+
/** A stripe on the leading edge in this tone, with no fill. Give `stripe` or `tone`, not both. */
|
|
23
|
+
stripe?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
/** A stripe on the leading edge in this tone, with no fill. Give `stripe` or `tone`, not both. */
|
|
26
|
+
stripe?: TextTone | undefined;
|
|
27
|
+
/** The row's status: a stripe on the leading edge plus the tone's soft fill, or `muted` to fade the row. Give `tone` or `stripe`, not both. */
|
|
28
|
+
tone?: undefined;
|
|
29
|
+
};
|
|
30
|
+
/** What an element passes through (`data-*`, `aria-*`, `id`, …): the layout and the children stay the component's. */
|
|
31
|
+
type Rest<Tag extends "table" | "thead" | "tbody" | "tfoot" | "tr" | "th" | "td", Own extends string = never> = Omit<ComponentPropsWithoutRef<Tag>, "className" | "style" | "children" | Own>;
|
|
32
|
+
/**
|
|
33
|
+
* A list of records in a rounded, bordered card that scrolls sideways on a narrow screen. Compose it
|
|
34
|
+
* from `TableHead`, `TableBody`, `TableFoot`, `TableRow`, `TableHeaderCell` and `TableCell`. `caption` names the
|
|
35
|
+
* table for assistive tech and is not drawn. `variant="plain"` drops the card (its border, fill and
|
|
36
|
+
* shadow), the head's tinted strip and the row hover, and keeps the row dividers; it is for a table
|
|
37
|
+
* that sits inside a card of its own. Other props
|
|
38
|
+
* (`id`, `data-*`, `aria-*`) go on the `<table>`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function Table({ caption, variant, children, ...rest }: {
|
|
41
|
+
/** Names the table for assistive tech; it is not drawn. */
|
|
42
|
+
caption?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* `card` sits the table in its own bordered card; `plain` drops the card, the head's tint and the row hover, for a table inside a card of its own.
|
|
45
|
+
* @default card
|
|
46
|
+
*/
|
|
47
|
+
variant?: TableVariant | undefined;
|
|
48
|
+
children: ReactNode;
|
|
49
|
+
} & Rest<"table">): ReactElement;
|
|
50
|
+
/** The `<thead>`: small uppercase column titles, on a tinted strip unless the table is `plain`. Holds one `TableRow` of `TableHeaderCell`s. */
|
|
51
|
+
export declare function TableHead({ children, ...rest }: {
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
} & Rest<"thead">): ReactElement;
|
|
54
|
+
/** The `<tbody>`: the rows, each with a line above it and, unless the table is `plain`, a fill on hover. */
|
|
55
|
+
export declare function TableBody({ children, ...rest }: {
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
} & Rest<"tbody">): ReactElement;
|
|
58
|
+
/**
|
|
59
|
+
* The `<tfoot>`: totals and subtotals. Its cells are strong by default and each row has a firmer
|
|
60
|
+
* line above it, in a card and in a `plain` table alike. Put it after `TableBody`; a row of it takes
|
|
61
|
+
* no `tone`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function TableFoot({ children, ...rest }: {
|
|
64
|
+
children: ReactNode;
|
|
65
|
+
} & Rest<"tfoot">): ReactElement;
|
|
66
|
+
/**
|
|
67
|
+
* A `<tr>`, `relative`: the anchor a `TextLink stretch` (with `href`, or `render` for a router `<Link>`) stretches to. Any other link, button or
|
|
68
|
+
* control in the row (a `TextLink`, a `Button`, a `Checkbox`) sits above that link and is clickable
|
|
69
|
+
* on its own, with no class from the caller. Use it in the head and the
|
|
70
|
+
* body. In the body it also draws the line above the row and, unless the table is `plain`, the hover
|
|
71
|
+
* fill. `tone` marks the row's status (see `RowTone`): a stripe on the leading edge and a soft fill,
|
|
72
|
+
* or `muted` to fade it. A toned row keeps its fill on hover (the status stays readable), so a
|
|
73
|
+
* linked row's link, not a hover fill, says it is clickable. `stripe` is the stripe alone, in the
|
|
74
|
+
* `TextTone` colours (the first five names of a `StackedBar` segment's `VizTone`), with no fill: the row keeps its hover
|
|
75
|
+
* and its own look. Give `tone` or `stripe`, not both. Body rows only; colour alone does not say it,
|
|
76
|
+
* so name the status in a cell too (a `Pill`). A bold subtotal is not a tone: it is a `TableFoot` row.
|
|
77
|
+
*/
|
|
78
|
+
export declare function TableRow({ tone, stripe, children, ...rest }: RowMark & {
|
|
79
|
+
children: ReactNode;
|
|
80
|
+
} & Rest<"tr">): ReactElement;
|
|
81
|
+
/**
|
|
82
|
+
* A full-width group header in the body: a tinted row with a `label` that spans the table's
|
|
83
|
+
* `colSpan` columns (its real column count), and optional `trailing` content at the far end (a
|
|
84
|
+
* count, a subtotal, a small button). The group's rows follow it. For a panel of other content across the whole row, use a `TableCell` with
|
|
85
|
+
* `colSpan` instead.
|
|
86
|
+
*/
|
|
87
|
+
export declare function TableGroupRow({ label, trailing, colSpan, ...rest }: {
|
|
88
|
+
/** The group's name, at the start of the row. */
|
|
89
|
+
label: ReactNode;
|
|
90
|
+
/** Content at the far end of the row: a count, a subtotal, a small button. */
|
|
91
|
+
trailing?: ReactNode;
|
|
92
|
+
/** The table's real column count, so the header spans the whole row. */
|
|
93
|
+
colSpan: number;
|
|
94
|
+
} & Rest<"tr">): ReactElement;
|
|
95
|
+
/**
|
|
96
|
+
* A column title, `<th scope="col">`. A column with nothing to show in its head (a row's actions)
|
|
97
|
+
* takes `srLabel` instead of `children`: the name a screen reader hears ("Actions"), visually
|
|
98
|
+
* hidden so the head keeps its column.
|
|
99
|
+
*/
|
|
100
|
+
export declare function TableHeaderCell({ align, children, srLabel, ...rest }: {
|
|
101
|
+
/**
|
|
102
|
+
* The cell's horizontal alignment; `end` for figures.
|
|
103
|
+
* @default start
|
|
104
|
+
*/
|
|
105
|
+
align?: Align | undefined;
|
|
106
|
+
} & ({
|
|
107
|
+
children?: ReactNode;
|
|
108
|
+
srLabel?: undefined;
|
|
109
|
+
} | {
|
|
110
|
+
children?: undefined;
|
|
111
|
+
/** The column's name for a screen reader, visually hidden, for a column with no visible title (a row's actions). */
|
|
112
|
+
srLabel: string;
|
|
113
|
+
}) & Rest<"th", "align" | "scope">): ReactElement;
|
|
114
|
+
/**
|
|
115
|
+
* A `<td>`. `tone` is the text weight and colour (see `CellTone`; in a `TableFoot` it defaults to
|
|
116
|
+
* `strong`); `numeric` sets tabular figures, `mono` a small mono face. `secondary` is a second,
|
|
117
|
+
* muted line under the content. One rule for wrapping: prose wraps onto more lines (never narrower
|
|
118
|
+
* than `min-w-48`, so it scrolls the table before it becomes a sliver), and a `numeric` or `mono`
|
|
119
|
+
* cell (a figure, a code, a type) never breaks. `truncate` caps a long value's width, cuts it with
|
|
120
|
+
* an ellipsis on one line and puts the full text in a `title`; the content is then plain text (strings and
|
|
121
|
+
* numbers), since that is what the title shows. `colSpan` passes through: a cell that
|
|
122
|
+
* spans the row is a full-width panel (an empty state, a note under a group).
|
|
123
|
+
*/
|
|
124
|
+
export declare function TableCell({ align, tone, numeric, mono, truncate, secondary, children, ...rest }: {
|
|
125
|
+
/**
|
|
126
|
+
* The cell's horizontal alignment; `end` for figures.
|
|
127
|
+
* @default start
|
|
128
|
+
*/
|
|
129
|
+
align?: Align | undefined;
|
|
130
|
+
/** The text weight and colour (see `CellTone`). In a `TableFoot` it defaults to `strong`. */
|
|
131
|
+
tone?: CellTone | undefined;
|
|
132
|
+
/** Tabular figures, and the cell never wraps. */
|
|
133
|
+
numeric?: boolean | undefined;
|
|
134
|
+
/** A small monospace face for a code or a type, and the cell never wraps. */
|
|
135
|
+
mono?: boolean | undefined;
|
|
136
|
+
/** Caps the width and cuts a long value with an ellipsis on one line, the full text in a `title`. Plain text content only. */
|
|
137
|
+
truncate?: boolean | undefined;
|
|
138
|
+
/** A second, muted line under the content. */
|
|
139
|
+
secondary?: ReactNode;
|
|
140
|
+
children?: ReactNode;
|
|
141
|
+
} & Rest<"td", "align">): ReactElement;
|
|
142
|
+
export {};
|
package/dist/Table.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, createContext, useContext } from "react";
|
|
3
|
+
import { cx, liftInteractive } from "./cx.js";
|
|
4
|
+
import { TEXT_TONE } from "./typography.js";
|
|
5
|
+
const ALIGN = { start: "text-left", end: "text-right" };
|
|
6
|
+
const TONE = {
|
|
7
|
+
default: "text-slate-700",
|
|
8
|
+
strong: "font-medium text-slate-900",
|
|
9
|
+
neutral: "text-slate-600",
|
|
10
|
+
...TEXT_TONE,
|
|
11
|
+
};
|
|
12
|
+
const ROW_TONE = {
|
|
13
|
+
neutral: "bg-slate-50 text-slate-500",
|
|
14
|
+
info: "bg-blue-50 text-blue-700",
|
|
15
|
+
positive: "bg-emerald-50 text-emerald-700",
|
|
16
|
+
warn: "bg-amber-50 text-amber-700",
|
|
17
|
+
danger: "bg-red-50 text-red-700",
|
|
18
|
+
muted: "opacity-60",
|
|
19
|
+
};
|
|
20
|
+
const STRIPE = "shadow-[inset_4px_0_0_0_currentColor]";
|
|
21
|
+
const VariantContext = createContext("card");
|
|
22
|
+
/**
|
|
23
|
+
* Whether a `TableRow` sits in the body (a line above, a hover fill), the foot (a firmer line above,
|
|
24
|
+
* strong cells) or the head or nowhere (neither).
|
|
25
|
+
*/
|
|
26
|
+
const SectionContext = createContext(undefined);
|
|
27
|
+
/**
|
|
28
|
+
* A list of records in a rounded, bordered card that scrolls sideways on a narrow screen. Compose it
|
|
29
|
+
* from `TableHead`, `TableBody`, `TableFoot`, `TableRow`, `TableHeaderCell` and `TableCell`. `caption` names the
|
|
30
|
+
* table for assistive tech and is not drawn. `variant="plain"` drops the card (its border, fill and
|
|
31
|
+
* shadow), the head's tinted strip and the row hover, and keeps the row dividers; it is for a table
|
|
32
|
+
* that sits inside a card of its own. Other props
|
|
33
|
+
* (`id`, `data-*`, `aria-*`) go on the `<table>`.
|
|
34
|
+
*/
|
|
35
|
+
export function Table({ caption, variant = "card", children, ...rest }) {
|
|
36
|
+
return (_jsx(VariantContext.Provider, { value: variant, children: _jsx("div", { className: cx("overflow-x-auto", variant === "card" && "rounded-xl border border-slate-200/70 bg-white shadow-sm"), children: _jsxs("table", { ...rest, className: "w-full text-left text-sm", children: [caption && _jsx("caption", { className: "sr-only", children: caption }), children] }) }) }));
|
|
37
|
+
}
|
|
38
|
+
/** The `<thead>`: small uppercase column titles, on a tinted strip unless the table is `plain`. Holds one `TableRow` of `TableHeaderCell`s. */
|
|
39
|
+
export function TableHead({ children, ...rest }) {
|
|
40
|
+
const plain = useContext(VariantContext) === "plain";
|
|
41
|
+
return (_jsx("thead", { ...rest, className: cx("text-xs uppercase tracking-wide text-slate-500", !plain && "bg-slate-50"), children: _jsx(SectionContext.Provider, { value: "head", children: children }) }));
|
|
42
|
+
}
|
|
43
|
+
/** The `<tbody>`: the rows, each with a line above it and, unless the table is `plain`, a fill on hover. */
|
|
44
|
+
export function TableBody({ children, ...rest }) {
|
|
45
|
+
return (_jsx("tbody", { ...rest, children: _jsx(SectionContext.Provider, { value: "body", children: children }) }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The `<tfoot>`: totals and subtotals. Its cells are strong by default and each row has a firmer
|
|
49
|
+
* line above it, in a card and in a `plain` table alike. Put it after `TableBody`; a row of it takes
|
|
50
|
+
* no `tone`.
|
|
51
|
+
*/
|
|
52
|
+
export function TableFoot({ children, ...rest }) {
|
|
53
|
+
return (_jsx("tfoot", { ...rest, children: _jsx(SectionContext.Provider, { value: "foot", children: children }) }));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A `<tr>`, `relative`: the anchor a `TextLink stretch` (with `href`, or `render` for a router `<Link>`) stretches to. Any other link, button or
|
|
57
|
+
* control in the row (a `TextLink`, a `Button`, a `Checkbox`) sits above that link and is clickable
|
|
58
|
+
* on its own, with no class from the caller. Use it in the head and the
|
|
59
|
+
* body. In the body it also draws the line above the row and, unless the table is `plain`, the hover
|
|
60
|
+
* fill. `tone` marks the row's status (see `RowTone`): a stripe on the leading edge and a soft fill,
|
|
61
|
+
* or `muted` to fade it. A toned row keeps its fill on hover (the status stays readable), so a
|
|
62
|
+
* linked row's link, not a hover fill, says it is clickable. `stripe` is the stripe alone, in the
|
|
63
|
+
* `TextTone` colours (the first five names of a `StackedBar` segment's `VizTone`), with no fill: the row keeps its hover
|
|
64
|
+
* and its own look. Give `tone` or `stripe`, not both. Body rows only; colour alone does not say it,
|
|
65
|
+
* so name the status in a cell too (a `Pill`). A bold subtotal is not a tone: it is a `TableFoot` row.
|
|
66
|
+
*/
|
|
67
|
+
export function TableRow({ tone, stripe, children, ...rest }) {
|
|
68
|
+
const section = useContext(SectionContext);
|
|
69
|
+
const plain = useContext(VariantContext) === "plain";
|
|
70
|
+
return (_jsx("tr", { ...rest, className: rowClass(section, plain, tone, stripe), children: children }));
|
|
71
|
+
}
|
|
72
|
+
/** The classes of a row: a line above in the body and the foot, the hover fill and the tone's or stripe's look in the body only. */
|
|
73
|
+
function rowClass(section, plain, tone, stripe) {
|
|
74
|
+
if (section === "foot")
|
|
75
|
+
return cx("relative border-t border-slate-300", liftInteractive);
|
|
76
|
+
if (section !== "body")
|
|
77
|
+
return cx("relative", liftInteractive);
|
|
78
|
+
const line = cx("relative border-t border-slate-200/70", liftInteractive);
|
|
79
|
+
const hover = !plain && "hover:bg-slate-50";
|
|
80
|
+
if (stripe !== undefined)
|
|
81
|
+
return cx(line, hover, TEXT_TONE[stripe], STRIPE);
|
|
82
|
+
if (tone === undefined)
|
|
83
|
+
return cx(line, hover);
|
|
84
|
+
if (tone === "muted")
|
|
85
|
+
return cx(line, hover, ROW_TONE.muted);
|
|
86
|
+
return cx(line, ROW_TONE[tone], STRIPE);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A full-width group header in the body: a tinted row with a `label` that spans the table's
|
|
90
|
+
* `colSpan` columns (its real column count), and optional `trailing` content at the far end (a
|
|
91
|
+
* count, a subtotal, a small button). The group's rows follow it. For a panel of other content across the whole row, use a `TableCell` with
|
|
92
|
+
* `colSpan` instead.
|
|
93
|
+
*/
|
|
94
|
+
export function TableGroupRow({ label, trailing, colSpan, ...rest }) {
|
|
95
|
+
return (_jsx("tr", { ...rest, className: "relative border-t border-slate-200/70 bg-slate-50", children: _jsx("th", { scope: "rowgroup", colSpan: colSpan, className: "px-4 py-2 text-left text-sm", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { className: "font-medium text-slate-900", children: label }), trailing != null && trailing !== false && (_jsx("span", { className: "shrink-0 font-normal text-slate-500", children: trailing }))] }) }) }));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A column title, `<th scope="col">`. A column with nothing to show in its head (a row's actions)
|
|
99
|
+
* takes `srLabel` instead of `children`: the name a screen reader hears ("Actions"), visually
|
|
100
|
+
* hidden so the head keeps its column.
|
|
101
|
+
*/
|
|
102
|
+
export function TableHeaderCell({ align = "start", children, srLabel, ...rest }) {
|
|
103
|
+
return (_jsx("th", { ...rest, scope: "col", className: cx("whitespace-nowrap px-4 py-2.5 font-medium", ALIGN[align]), children: srLabel === undefined ? children : _jsx("span", { className: "sr-only", children: srLabel }) }));
|
|
104
|
+
}
|
|
105
|
+
/** The wrapping rule: a figure or code never breaks; prose wraps, down to `min-w-48`. */
|
|
106
|
+
function figureClass(numeric, mono) {
|
|
107
|
+
if (!numeric && !mono)
|
|
108
|
+
return "min-w-48";
|
|
109
|
+
return cx("whitespace-nowrap", numeric && "tabular-nums", mono && "font-mono text-xs");
|
|
110
|
+
}
|
|
111
|
+
/** The widest a `truncate` cell grows. */
|
|
112
|
+
const TRUNCATE_WIDTH = "max-w-xs";
|
|
113
|
+
/**
|
|
114
|
+
* A `<td>`. `tone` is the text weight and colour (see `CellTone`; in a `TableFoot` it defaults to
|
|
115
|
+
* `strong`); `numeric` sets tabular figures, `mono` a small mono face. `secondary` is a second,
|
|
116
|
+
* muted line under the content. One rule for wrapping: prose wraps onto more lines (never narrower
|
|
117
|
+
* than `min-w-48`, so it scrolls the table before it becomes a sliver), and a `numeric` or `mono`
|
|
118
|
+
* cell (a figure, a code, a type) never breaks. `truncate` caps a long value's width, cuts it with
|
|
119
|
+
* an ellipsis on one line and puts the full text in a `title`; the content is then plain text (strings and
|
|
120
|
+
* numbers), since that is what the title shows. `colSpan` passes through: a cell that
|
|
121
|
+
* spans the row is a full-width panel (an empty state, a note under a group).
|
|
122
|
+
*/
|
|
123
|
+
export function TableCell({ align = "start", tone, numeric = false, mono = false, truncate = false, secondary, children, ...rest }) {
|
|
124
|
+
const foot = useContext(SectionContext) === "foot";
|
|
125
|
+
return (_jsx("td", { ...rest, className: cellClass({ align, tone, foot, numeric, mono }), children: _jsx(CellBody, { align: align, truncate: truncate, secondary: secondary, children: children }) }));
|
|
126
|
+
}
|
|
127
|
+
/** The classes of a cell: the tone (`strong` by default in a foot, and a foot's cells are medium whatever the tone), then the wrapping rule. */
|
|
128
|
+
function cellClass({ align, tone, foot, numeric, mono, }) {
|
|
129
|
+
const resolved = tone ?? (foot ? "strong" : "default");
|
|
130
|
+
return cx("px-4 py-3", ALIGN[align], TONE[resolved], foot && resolved !== "strong" && "font-medium", figureClass(numeric, mono));
|
|
131
|
+
}
|
|
132
|
+
const isText = (node) => typeof node === "string" || typeof node === "number";
|
|
133
|
+
/** What a cell holds: the content, cut to one line with its full text in a `title` when `truncate`, then the `secondary` line. */
|
|
134
|
+
function CellBody({ align, truncate, secondary, children, }) {
|
|
135
|
+
const parts = Children.toArray(children);
|
|
136
|
+
const text = parts.every(isText) ? parts.join("") : undefined;
|
|
137
|
+
return (_jsxs(_Fragment, { children: [truncate ? (_jsx("div", { className: cx(TRUNCATE_WIDTH, "truncate", align === "end" && "ml-auto"), title: text, children: children })) : (children), secondary != null && secondary !== false && (_jsx("div", { className: "text-xs font-normal text-slate-500", children: secondary }))] }));
|
|
138
|
+
}
|
package/dist/Tabs.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import type { Size } from "./size.js";
|
|
3
|
+
import { type SwitchItem, type SwitchProps } from "./switcher.js";
|
|
4
|
+
/** One tab of a Tabs row. See `SwitchItem`. */
|
|
5
|
+
export type Tab = SwitchItem;
|
|
6
|
+
/** The classes of one pill tab. A `disabled` one drops the hover fill. */
|
|
7
|
+
export declare function pillTabClass(active: boolean, disabled?: boolean, size?: Size): string;
|
|
8
|
+
export type TabsProps = SwitchProps & ({
|
|
9
|
+
/** `underline` (default) is a row over a bottom rule; `pill` fills the current tab. */
|
|
10
|
+
variant?: "underline";
|
|
11
|
+
/** Only a `pill` row takes a `size`. */
|
|
12
|
+
size?: undefined;
|
|
13
|
+
/** Only a `pill` row takes an `orientation`. */
|
|
14
|
+
orientation?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
variant: "pill";
|
|
17
|
+
/**
|
|
18
|
+
* The pills' height and text, on the scale of a `Button`.
|
|
19
|
+
* @default md
|
|
20
|
+
*/
|
|
21
|
+
size?: Size;
|
|
22
|
+
/**
|
|
23
|
+
* `vertical` stacks the pills in one column at the rail's width, for a `SidebarLayout` rail;
|
|
24
|
+
* the up and down arrows then move between button tabs.
|
|
25
|
+
* @default horizontal
|
|
26
|
+
*/
|
|
27
|
+
orientation?: "horizontal" | "vertical";
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Underline tabs for switching a view in place: a row over a bottom rule, the current tab
|
|
31
|
+
* underlined in the accent. `items` are `{ value, label }` buttons (`value` and `onChange` are the
|
|
32
|
+
* caller's state; the arrow keys, Home and End move between tabs) or links (`href`, or `render` to
|
|
33
|
+
* draw the app's own router `<Link>`; `active` on an item, or `value`, marks the current one, which
|
|
34
|
+
* gets `aria-current`). `variant="pill"` fills the current tab instead, with no rule, for under a
|
|
35
|
+
* `PageHeader` or as a site's section nav, and takes a `size`; `orientation="vertical"` stacks the
|
|
36
|
+
* pills for a `SidebarLayout` rail. An item may carry a `count`, and a button one `disabled` (with a
|
|
37
|
+
* `title` for the reason); `disabled` on the row disables every tab. `label` names the tab list.
|
|
38
|
+
* The panel is the caller's: give it `role="tabpanel"`, and a tab `controls` to point at it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function Tabs({ variant, size, orientation, ...props }: TabsProps): ReactElement;
|
package/dist/Tabs.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx, focusRing } from "./cx.js";
|
|
3
|
+
import { Switcher } from "./switcher.js";
|
|
4
|
+
const BASE = `-mb-px border-b-2 px-0.5 py-2 transition ${focusRing}`;
|
|
5
|
+
const ON = "border-violet-600 font-medium text-violet-700";
|
|
6
|
+
const OFF = "border-transparent text-slate-600";
|
|
7
|
+
const OFF_LIVE = "hover:text-violet-700";
|
|
8
|
+
// A transparent border, so a pill is as tall as a `Button` of the same size.
|
|
9
|
+
const PILL = `rounded-lg border border-transparent transition ${focusRing}`;
|
|
10
|
+
/** A pill's padding and text, by `size`. */
|
|
11
|
+
const PILL_SIZE = {
|
|
12
|
+
sm: "px-2.5 py-1 text-xs",
|
|
13
|
+
md: "px-3 py-2 text-sm",
|
|
14
|
+
lg: "px-4 py-2.5 text-base",
|
|
15
|
+
};
|
|
16
|
+
/** The classes of one pill tab. A `disabled` one drops the hover fill. */
|
|
17
|
+
export function pillTabClass(active, disabled = false, size = "md") {
|
|
18
|
+
return cx(PILL, PILL_SIZE[size], active ? "bg-slate-100 font-medium text-slate-900" : "text-slate-600", !active && !disabled && "hover:bg-slate-100");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Underline tabs for switching a view in place: a row over a bottom rule, the current tab
|
|
22
|
+
* underlined in the accent. `items` are `{ value, label }` buttons (`value` and `onChange` are the
|
|
23
|
+
* caller's state; the arrow keys, Home and End move between tabs) or links (`href`, or `render` to
|
|
24
|
+
* draw the app's own router `<Link>`; `active` on an item, or `value`, marks the current one, which
|
|
25
|
+
* gets `aria-current`). `variant="pill"` fills the current tab instead, with no rule, for under a
|
|
26
|
+
* `PageHeader` or as a site's section nav, and takes a `size`; `orientation="vertical"` stacks the
|
|
27
|
+
* pills for a `SidebarLayout` rail. An item may carry a `count`, and a button one `disabled` (with a
|
|
28
|
+
* `title` for the reason); `disabled` on the row disables every tab. `label` names the tab list.
|
|
29
|
+
* The panel is the caller's: give it `role="tabpanel"`, and a tab `controls` to point at it.
|
|
30
|
+
*/
|
|
31
|
+
export function Tabs({ variant = "underline", size = "md", orientation = "horizontal", ...props }) {
|
|
32
|
+
const pill = variant === "pill";
|
|
33
|
+
const stacked = pill && orientation === "vertical";
|
|
34
|
+
return (_jsx(Switcher, { ...props, parts: {
|
|
35
|
+
group: "tablist",
|
|
36
|
+
item: "tab",
|
|
37
|
+
vertical: stacked,
|
|
38
|
+
stacked,
|
|
39
|
+
root: rootClass(pill, stacked),
|
|
40
|
+
option: (active, disabled) => pill
|
|
41
|
+
? pillTabClass(active, disabled, size)
|
|
42
|
+
: cx(BASE, active ? ON : OFF, !active && !disabled && OFF_LIVE),
|
|
43
|
+
} }));
|
|
44
|
+
}
|
|
45
|
+
function rootClass(pill, stacked) {
|
|
46
|
+
if (stacked)
|
|
47
|
+
return "flex flex-col gap-1";
|
|
48
|
+
return pill ? "flex flex-wrap gap-1" : "flex flex-wrap gap-x-5 border-b border-slate-200 text-sm";
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement } from "react";
|
|
2
|
+
import { type LinkRender } from "./link.js";
|
|
3
|
+
import type { Unstyled } from "./props.js";
|
|
4
|
+
import type { Size } from "./size.js";
|
|
5
|
+
import { type TextTone } from "./typography.js";
|
|
6
|
+
/** The classes of an inline text link, for a router's own <Link> that should look like one. */
|
|
7
|
+
export declare const linkClass: string;
|
|
8
|
+
/**
|
|
9
|
+
* Stretches a link's hit area over its nearest `relative` ancestor (a table row, a card), so the
|
|
10
|
+
* whole of it opens the link. Add it to the one link in the row; the ancestor needs `relative`.
|
|
11
|
+
* `link-stretch` marks the link, so a `TableRow` or `Card` that lifts its other links and buttons
|
|
12
|
+
* above it leaves this one alone.
|
|
13
|
+
*/
|
|
14
|
+
export declare const linkStretchClass = "link-stretch after:absolute after:inset-0";
|
|
15
|
+
export type TextLinkSize = Size;
|
|
16
|
+
type TextLinkProps = {
|
|
17
|
+
/**
|
|
18
|
+
* The link's own type size: `sm` is `text-xs`, `md` `text-sm` and `lg` `text-base`. Left out, it
|
|
19
|
+
* takes the size of the text around it: a link inside prose has no default size.
|
|
20
|
+
*/
|
|
21
|
+
size?: TextLinkSize | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* A meaning's colour, as on the inline roles: `warn` for a warning link, `danger`, `positive`,
|
|
24
|
+
* `info` or `muted`. Left out, the link is the accent colour.
|
|
25
|
+
*/
|
|
26
|
+
tone?: TextTone | undefined;
|
|
27
|
+
/** The monospace face, for a path, an id or a command. */
|
|
28
|
+
mono?: boolean | undefined;
|
|
29
|
+
/** Opens in a new tab: adds `target="_blank"` and `rel="noreferrer"`. */
|
|
30
|
+
external?: boolean | undefined;
|
|
31
|
+
/** The link covers its nearest `relative` ancestor, so the whole row or card is the click target. */
|
|
32
|
+
stretch?: boolean | undefined;
|
|
33
|
+
} & Unstyled<Omit<ComponentPropsWithoutRef<"a">, "href">> & ({
|
|
34
|
+
/** Where the link goes. */
|
|
35
|
+
href: string;
|
|
36
|
+
/** Draws the link with the app's own router `<Link>`: `(p) => <Link to="/terms" {...p} />`. */
|
|
37
|
+
render?: LinkRender | undefined;
|
|
38
|
+
} | {
|
|
39
|
+
/** Where the link goes. */
|
|
40
|
+
href?: string | undefined;
|
|
41
|
+
/** Draws the link with the app's own router `<Link>`: `(p) => <Link to="/terms" {...p} />`. */
|
|
42
|
+
render: LinkRender;
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* An inline text link in the accent colour. Every anchor prop goes on the `<a>`. With `render` it
|
|
46
|
+
* draws the app's own router `<Link>` instead (`render={(p) => <Link to="/terms" {...p} />}`).
|
|
47
|
+
* `size` pins the type size (`sm`, `md` or `lg`), `tone` gives it a meaning's colour and `mono` sets the
|
|
48
|
+
* monospace face.
|
|
49
|
+
*/
|
|
50
|
+
export declare function TextLink({ size, tone, mono, external, stretch, href, render, children, ...rest }: TextLinkProps): ReactElement;
|
|
51
|
+
export {};
|
package/dist/TextLink.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { cx, focusRing } from "./cx.js";
|
|
2
|
+
import { renderLink } from "./link.js";
|
|
3
|
+
import { TEXT_TONE } from "./typography.js";
|
|
4
|
+
const LINK = "rounded font-medium underline-offset-2 transition-colors";
|
|
5
|
+
/** The classes of an inline text link, for a router's own <Link> that should look like one. */
|
|
6
|
+
export const linkClass = cx(LINK, "text-violet-600 hover:text-violet-700", focusRing);
|
|
7
|
+
// A toned link takes its tone's text colour, shared with Table's cells and the inline roles, and
|
|
8
|
+
// underlines on hover in place of a hover shade.
|
|
9
|
+
const toneClass = (tone) => cx(LINK, TEXT_TONE[tone], "hover:underline", focusRing);
|
|
10
|
+
/**
|
|
11
|
+
* Stretches a link's hit area over its nearest `relative` ancestor (a table row, a card), so the
|
|
12
|
+
* whole of it opens the link. Add it to the one link in the row; the ancestor needs `relative`.
|
|
13
|
+
* `link-stretch` marks the link, so a `TableRow` or `Card` that lifts its other links and buttons
|
|
14
|
+
* above it leaves this one alone.
|
|
15
|
+
*/
|
|
16
|
+
export const linkStretchClass = "link-stretch after:absolute after:inset-0";
|
|
17
|
+
const SIZE = {
|
|
18
|
+
sm: "text-xs/4",
|
|
19
|
+
md: "text-sm/5",
|
|
20
|
+
lg: "text-base/6",
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* An inline text link in the accent colour. Every anchor prop goes on the `<a>`. With `render` it
|
|
24
|
+
* draws the app's own router `<Link>` instead (`render={(p) => <Link to="/terms" {...p} />}`).
|
|
25
|
+
* `size` pins the type size (`sm`, `md` or `lg`), `tone` gives it a meaning's colour and `mono` sets the
|
|
26
|
+
* monospace face.
|
|
27
|
+
*/
|
|
28
|
+
export function TextLink({ size, tone, mono = false, external = false, stretch = false, href, render, children, ...rest }) {
|
|
29
|
+
return renderLink({
|
|
30
|
+
...rest,
|
|
31
|
+
href,
|
|
32
|
+
render,
|
|
33
|
+
external,
|
|
34
|
+
className: cx(tone ? toneClass(tone) : linkClass, size && SIZE[size], mono && "font-mono", stretch && linkStretchClass),
|
|
35
|
+
children,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactElement } from "react";
|
|
2
|
+
import { type ControlProps } from "./control.js";
|
|
3
|
+
import type { Unstyled } from "./props.js";
|
|
4
|
+
/** A multi-line text input, styled like `Input`. `rows` defaults to 3. Takes `size` and `width` too. */
|
|
5
|
+
export declare function Textarea({ size, width, ...rest }: ControlProps & Unstyled<Omit<ComponentPropsWithoutRef<"textarea">, "width">>): ReactElement;
|
package/dist/Textarea.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { CONTROL_PAD, controlClass } from "./control.js";
|
|
3
|
+
import { cx } from "./cx.js";
|
|
4
|
+
/** A multi-line text input, styled like `Input`. `rows` defaults to 3. Takes `size` and `width` too. */
|
|
5
|
+
export function Textarea({ size = "md", width = "full", ...rest }) {
|
|
6
|
+
return (_jsx("textarea", { rows: 3, ...rest, className: cx(controlClass(size, width), CONTROL_PAD[size].x) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
+
import type { CalloutTone } from "./Callout.js";
|
|
3
|
+
import type { Passthrough } from "./props.js";
|
|
4
|
+
/**
|
|
5
|
+
* A vertical list of events, newest or oldest first as the caller orders them. A named `<ol>` of
|
|
6
|
+
* `TimelineItem`s on a rail; with no children it draws `empty` instead of an empty list.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Timeline({ label, empty, children, ...rest }: {
|
|
9
|
+
/** Names the list (`aria-label`), e.g. "Status history". */
|
|
10
|
+
label: string;
|
|
11
|
+
/** Drawn in place of the list when there are no items. */
|
|
12
|
+
empty?: ReactNode;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
} & Passthrough): ReactElement | null;
|
|
15
|
+
/**
|
|
16
|
+
* One event: a `title` (a `Pill`, a heading), a `time` the app has already formatted, and the
|
|
17
|
+
* `children` as a note under them. `tone` colours the marker dot with the Callout's set.
|
|
18
|
+
*/
|
|
19
|
+
export declare function TimelineItem({ title, time, tone, children, ...rest }: {
|
|
20
|
+
/** What happened: text, a `Pill` or a heading. */
|
|
21
|
+
title: ReactNode;
|
|
22
|
+
/** When, already formatted by the app, small and muted beside the title. */
|
|
23
|
+
time?: ReactNode;
|
|
24
|
+
/** The marker dot's colour. Defaults to `neutral`, a plain ring. */
|
|
25
|
+
tone?: CalloutTone;
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
} & Passthrough): ReactElement;
|
package/dist/Timeline.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children } from "react";
|
|
3
|
+
import { cx } from "./cx.js";
|
|
4
|
+
// The marker's colour is the tone's text colour, which every theme already remaps, drawn as a
|
|
5
|
+
// solid dot; `neutral` is the ring.
|
|
6
|
+
const DOT = {
|
|
7
|
+
neutral: "border-slate-300 bg-white",
|
|
8
|
+
info: "border-current bg-current text-blue-700",
|
|
9
|
+
positive: "border-current bg-current text-emerald-700",
|
|
10
|
+
warn: "border-current bg-current text-amber-700",
|
|
11
|
+
danger: "border-current bg-current text-red-700",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A vertical list of events, newest or oldest first as the caller orders them. A named `<ol>` of
|
|
15
|
+
* `TimelineItem`s on a rail; with no children it draws `empty` instead of an empty list.
|
|
16
|
+
*/
|
|
17
|
+
export function Timeline({ label, empty, children, ...rest }) {
|
|
18
|
+
if (Children.toArray(children).length === 0) {
|
|
19
|
+
return empty == null ? null : _jsx(_Fragment, { children: empty });
|
|
20
|
+
}
|
|
21
|
+
return (_jsx("ol", { ...rest, "aria-label": label, className: "flex flex-col", children: children }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One event: a `title` (a `Pill`, a heading), a `time` the app has already formatted, and the
|
|
25
|
+
* `children` as a note under them. `tone` colours the marker dot with the Callout's set.
|
|
26
|
+
*/
|
|
27
|
+
export function TimelineItem({ title, time, tone = "neutral", children, ...rest }) {
|
|
28
|
+
return (_jsxs("li", { ...rest, className: "relative border-l border-slate-300 pb-5 pl-5 last:border-transparent last:pb-0", children: [_jsx("span", { "aria-hidden": "true", className: cx("absolute -left-[5px] top-1.5 h-2.5 w-2.5 rounded-full border-2", DOT[tone]) }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [_jsx("span", { className: "text-sm font-medium text-slate-900", children: title }), time != null && _jsx("span", { className: "text-xs tabular-nums text-slate-500", children: time })] }), children != null && _jsx("div", { className: "mt-1 text-sm text-slate-700", children: children })] }));
|
|
29
|
+
}
|
package/dist/Video.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactElement, VideoHTMLAttributes } from "react";
|
|
2
|
+
import type { Unstyled } from "./props.js";
|
|
3
|
+
/**
|
|
4
|
+
* A `<video>` with controls, full width and rounded; it loads metadata only, so the page does not
|
|
5
|
+
* pull the file until it is played. `src` is the app's own video; `captions` is a WebVTT file,
|
|
6
|
+
* offered as the default track. `children` is the fallback for a browser that cannot play it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function Video({ src, captions, children, ...rest }: {
|
|
9
|
+
/** The video file, the app's own. */
|
|
10
|
+
src: string;
|
|
11
|
+
/** A WebVTT file, offered as the default captions track. */
|
|
12
|
+
captions?: string | undefined;
|
|
13
|
+
} & Unstyled<Omit<VideoHTMLAttributes<HTMLVideoElement>, "src" | "controls">>): ReactElement;
|
package/dist/Video.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A `<video>` with controls, full width and rounded; it loads metadata only, so the page does not
|
|
4
|
+
* pull the file until it is played. `src` is the app's own video; `captions` is a WebVTT file,
|
|
5
|
+
* offered as the default track. `children` is the fallback for a browser that cannot play it.
|
|
6
|
+
*/
|
|
7
|
+
export function Video({ src, captions, children, ...rest }) {
|
|
8
|
+
return (_jsxs("video", { controls: true, preload: "metadata", src: src, ...rest, className: "w-full rounded-lg", children: [captions && _jsx("track", { kind: "captions", src: captions, default: true }), children] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ColourScheme = "light" | "dark";
|
|
2
|
+
/** The class each scheme puts on `<html>`: none for light and `dark` for dark, unless the app's theme says otherwise (a light theme with a scope class of its own). */
|
|
3
|
+
export type ColourSchemeClasses = {
|
|
4
|
+
light: string;
|
|
5
|
+
dark: string;
|
|
6
|
+
};
|
|
7
|
+
/** The class for `scheme`: what to render on `<html>` on the server so the first paint is right. `""` for the default light scheme. */
|
|
8
|
+
export declare function schemeClass(scheme: ColourScheme, schemes?: ColourSchemeClasses): string;
|
|
9
|
+
/** Which scheme `root` is in: dark when it carries the dark class, else light. */
|
|
10
|
+
export declare function appliedColourScheme(root?: HTMLElement, schemes?: ColourSchemeClasses): ColourScheme;
|
|
11
|
+
/** Put `scheme` on `root` (the `<html>` element by default): its class from `schemes` (default `dark` for dark and none for light), the other scheme's class taken off, and `color-scheme` so native controls and scrollbars follow. */
|
|
12
|
+
export declare function applyColourScheme(scheme: ColourScheme, root?: HTMLElement, schemes?: ColourSchemeClasses): void;
|
|
13
|
+
/** The stored choice, else the system preference. `fallback` (default `"light"`) is for when neither is available: server rendering, or storage that throws with no `matchMedia`. */
|
|
14
|
+
export declare function readColourScheme(fallback?: ColourScheme): ColourScheme;
|
|
15
|
+
/** Remember `scheme` for the next visit. A storage error is swallowed, so private mode does not crash. */
|
|
16
|
+
export declare function storeColourScheme(scheme: ColourScheme): void;
|
|
17
|
+
/**
|
|
18
|
+
* JavaScript to inline in `<head>`, before first paint: applies the stored scheme, else the
|
|
19
|
+
* system one, to `<html>` as `applyColourScheme` does. Without it a light user sees a dark flash.
|
|
20
|
+
*/
|
|
21
|
+
export declare const colourSchemeScript: string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const STORAGE_KEY = "hackcove:colour-scheme";
|
|
2
|
+
const DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
3
|
+
const isScheme = (value) => value === "light" || value === "dark";
|
|
4
|
+
const DEFAULT_CLASSES = { light: "", dark: "dark" };
|
|
5
|
+
/** The class for `scheme`: what to render on `<html>` on the server so the first paint is right. `""` for the default light scheme. */
|
|
6
|
+
export function schemeClass(scheme, schemes = DEFAULT_CLASSES) {
|
|
7
|
+
return schemes[scheme];
|
|
8
|
+
}
|
|
9
|
+
/** Which scheme `root` is in: dark when it carries the dark class, else light. */
|
|
10
|
+
export function appliedColourScheme(root = document.documentElement, schemes = DEFAULT_CLASSES) {
|
|
11
|
+
return schemes.dark !== "" && root.classList.contains(schemes.dark) ? "dark" : "light";
|
|
12
|
+
}
|
|
13
|
+
/** Put `scheme` on `root` (the `<html>` element by default): its class from `schemes` (default `dark` for dark and none for light), the other scheme's class taken off, and `color-scheme` so native controls and scrollbars follow. */
|
|
14
|
+
export function applyColourScheme(scheme, root = document.documentElement, schemes = DEFAULT_CLASSES) {
|
|
15
|
+
const other = scheme === "dark" ? "light" : "dark";
|
|
16
|
+
if (schemes[other] !== "")
|
|
17
|
+
root.classList.remove(schemes[other]);
|
|
18
|
+
if (schemes[scheme] !== "")
|
|
19
|
+
root.classList.add(schemes[scheme]);
|
|
20
|
+
root.style.colorScheme = scheme;
|
|
21
|
+
}
|
|
22
|
+
function storedScheme() {
|
|
23
|
+
try {
|
|
24
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
25
|
+
return isScheme(stored) ? stored : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined; // storage blocked: private mode, or no window at all
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function systemScheme() {
|
|
32
|
+
if (typeof matchMedia !== "function")
|
|
33
|
+
return undefined;
|
|
34
|
+
return matchMedia(DARK_QUERY).matches ? "dark" : "light";
|
|
35
|
+
}
|
|
36
|
+
/** The stored choice, else the system preference. `fallback` (default `"light"`) is for when neither is available: server rendering, or storage that throws with no `matchMedia`. */
|
|
37
|
+
export function readColourScheme(fallback = "light") {
|
|
38
|
+
return storedScheme() ?? systemScheme() ?? fallback;
|
|
39
|
+
}
|
|
40
|
+
/** Remember `scheme` for the next visit. A storage error is swallowed, so private mode does not crash. */
|
|
41
|
+
export function storeColourScheme(scheme) {
|
|
42
|
+
try {
|
|
43
|
+
localStorage.setItem(STORAGE_KEY, scheme);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Not remembered; the page still switches for this visit.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* JavaScript to inline in `<head>`, before first paint: applies the stored scheme, else the
|
|
51
|
+
* system one, to `<html>` as `applyColourScheme` does. Without it a light user sees a dark flash.
|
|
52
|
+
*/
|
|
53
|
+
export const colourSchemeScript = `(function(){var r=document.documentElement,s;try{s=localStorage.getItem(${JSON.stringify(STORAGE_KEY)})}catch(e){}if(s!=="light"&&s!=="dark")s=typeof matchMedia==="function"?matchMedia(${JSON.stringify(DARK_QUERY)}).matches?"dark":"light":null;if(s){r.classList.toggle("dark",s==="dark");r.style.colorScheme=s}})()`;
|