@hackcove/ui-react 0.10.9 → 0.10.10
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/dist/Card.d.ts +6 -0
- package/dist/Card.js +11 -24
- package/dist/CardBody.d.ts +27 -0
- package/dist/CardBody.js +30 -0
- package/dist/Markdown.js +1 -1
- package/dist/ProgressBar.d.ts +4 -4
- package/dist/ProgressBar.js +3 -3
- package/dist/highlight.d.ts +1 -1
- package/dist/highlight.js +2 -2
- package/dist/typography.d.ts +1 -1
- package/dist/typography.js +2 -2
- package/package.json +1 -1
package/dist/Card.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ type CardPanelProps = CardBase & {
|
|
|
47
47
|
actions?: ReactNode;
|
|
48
48
|
/** A tinted strip across the bottom of the card, under a hairline, for the card's own actions or a total. It stays at the foot when the card is stretched in a `CardGrid`. Ignored with `dashed`. */
|
|
49
49
|
footer?: ReactNode;
|
|
50
|
+
/** Take the parent's full height: the title or bar stays put and the body scrolls. The parent sets the height. Ignored with `dashed`. */
|
|
51
|
+
fill?: boolean;
|
|
50
52
|
/**
|
|
51
53
|
* The root element. A `div` takes no name from the title. Not on a card that is a link.
|
|
52
54
|
* @default section
|
|
@@ -59,6 +61,7 @@ type CardPanelProps = CardBase & {
|
|
|
59
61
|
type CardLinkProps = CardBase & {
|
|
60
62
|
actions?: undefined;
|
|
61
63
|
footer?: undefined;
|
|
64
|
+
fill?: undefined;
|
|
62
65
|
as?: undefined;
|
|
63
66
|
/** Opens in a new tab: adds `target="_blank"` and `rel="noreferrer"`. */
|
|
64
67
|
external?: boolean | undefined;
|
|
@@ -97,6 +100,9 @@ type CardLinkProps = CardBase & {
|
|
|
97
100
|
* total; in a `CardGrid` it stays at the foot of a card that is stretched to the row's height. It
|
|
98
101
|
* suits a panel, not a card that is a link, and `dashed` has none.
|
|
99
102
|
*
|
|
103
|
+
* `fill` makes a panel take its parent's full height (the parent sets it): the title or bar stays
|
|
104
|
+
* put and the body scrolls. Not on a link card.
|
|
105
|
+
*
|
|
100
106
|
* `href` makes the whole card one link: a hover border and the focus ring, with the link stretched
|
|
101
107
|
* over the card, so it takes no `actions`, `footer` or `as`. A `TextLink`, `Button` or `Checkbox` in
|
|
102
108
|
* the body sits above the link and is clickable on its own. With `render` it draws the app's own router `<Link>`
|
package/dist/Card.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useId } from "react";
|
|
3
3
|
import { TONE_SURFACE } from "./Callout.js";
|
|
4
4
|
import { CardBadge } from "./CardBadge.js";
|
|
5
|
-
import {
|
|
5
|
+
import { BODY_PAD, cardBody } from "./CardBody.js";
|
|
6
|
+
import { CardFooter } from "./CardParts.js";
|
|
6
7
|
import { cx, focusRing, liftInteractive } from "./cx.js";
|
|
7
8
|
import { renderLink } from "./link.js";
|
|
8
9
|
/** A card with a `footer`: a `dashed` card stands alone and has none. */
|
|
@@ -29,12 +30,11 @@ function cardForm(asked, hasHeader) {
|
|
|
29
30
|
tone: dashed || bar ? undefined : asked.tone,
|
|
30
31
|
attention: on(asked.attention, dashed),
|
|
31
32
|
icon: paddedIcon(asked, dashed || bar),
|
|
33
|
+
fill: on(asked.fill, dashed),
|
|
32
34
|
size: asked.size,
|
|
33
35
|
footer: false,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
|
-
/** The padding of a card's body by `size`. */
|
|
37
|
-
const BODY_PAD = { sm: "p-3", md: "p-5", lg: "p-6" };
|
|
38
38
|
function cardBorder(form) {
|
|
39
39
|
if (form.dashed)
|
|
40
40
|
return "border-dashed border-slate-300";
|
|
@@ -49,10 +49,10 @@ function cardPadding(form) {
|
|
|
49
49
|
if (form.dashed) {
|
|
50
50
|
return cx(form.filled ? "p-10" : "p-8", "text-center text-sm text-slate-500");
|
|
51
51
|
}
|
|
52
|
-
return !form.flush && !form.bar && !form.footer && BODY_PAD[form.size];
|
|
52
|
+
return !form.flush && !form.bar && !form.footer && !form.fill && BODY_PAD[form.size];
|
|
53
53
|
}
|
|
54
54
|
function cardClass(form) {
|
|
55
|
-
return cx("relative scroll-mt-28 rounded-xl border target:outline-2 target:outline-violet-600", liftInteractive, cardBorder(form), form.filled && "bg-white", cardPadding(form), !form.dashed && !form.tone && "shadow-sm", (form.flush || form.bar || form.footer) && "overflow-hidden", form.footer && "flex flex-col");
|
|
55
|
+
return cx("relative scroll-mt-28 rounded-xl border target:outline-2 target:outline-violet-600", liftInteractive, cardBorder(form), form.filled && "bg-white", cardPadding(form), !form.dashed && !form.tone && "shadow-sm", (form.flush || form.bar || form.footer || form.fill) && "overflow-hidden", form.fill ? "flex h-full min-h-0 flex-col" : form.footer && "flex flex-col");
|
|
56
56
|
}
|
|
57
57
|
function isCardLink(props) {
|
|
58
58
|
return props.href !== undefined || props.render !== undefined;
|
|
@@ -81,6 +81,9 @@ function isCardLink(props) {
|
|
|
81
81
|
* total; in a `CardGrid` it stays at the foot of a card that is stretched to the row's height. It
|
|
82
82
|
* suits a panel, not a card that is a link, and `dashed` has none.
|
|
83
83
|
*
|
|
84
|
+
* `fill` makes a panel take its parent's full height (the parent sets it): the title or bar stays
|
|
85
|
+
* put and the body scrolls. Not on a link card.
|
|
86
|
+
*
|
|
84
87
|
* `href` makes the whole card one link: a hover border and the focus ring, with the link stretched
|
|
85
88
|
* over the card, so it takes no `actions`, `footer` or `as`. A `TextLink`, `Button` or `Checkbox` in
|
|
86
89
|
* the body sits above the link and is clickable on its own. With `render` it draws the app's own router `<Link>`
|
|
@@ -95,10 +98,10 @@ export function Card(props) {
|
|
|
95
98
|
}
|
|
96
99
|
/** A card that is not a link: `as` (a `section` by default) named by its heading. */
|
|
97
100
|
function PanelCard(props) {
|
|
98
|
-
const { title, titleStyle, count, status, actions, footer, bar, flush, dashed, tone, badge, inset, attention, icon, size = "md", children, ...panel } = props;
|
|
101
|
+
const { title, titleStyle, count, status, actions, footer, bar, flush, dashed, tone, badge, inset, attention, icon, fill, size = "md", children, ...panel } = props;
|
|
99
102
|
const { as: Tag = "section", ...rest } = panel;
|
|
100
103
|
const headingId = useId();
|
|
101
|
-
const form = withFooter(cardForm({ flush, bar, dashed, tone, attention, icon, size }, Boolean(title || actions)), footer);
|
|
104
|
+
const form = withFooter(cardForm({ flush, bar, dashed, tone, attention, icon, fill, size }, Boolean(title || actions)), footer);
|
|
102
105
|
// The title names the section or article; a caller's own name wins, and a bare `div` takes none.
|
|
103
106
|
const named = title &&
|
|
104
107
|
Tag !== "div" &&
|
|
@@ -143,19 +146,3 @@ function LinkCard(props) {
|
|
|
143
146
|
headingId: undefined,
|
|
144
147
|
}), badge && _jsx(CardBadge, { size: form.size, children: badge })] }));
|
|
145
148
|
}
|
|
146
|
-
function insetBand(form, inset) {
|
|
147
|
-
if (!inset || form.dashed)
|
|
148
|
-
return null;
|
|
149
|
-
return (_jsx(CardInset, { size: form.size, bleed: !form.flush, children: inset }));
|
|
150
|
-
}
|
|
151
|
-
function cardBody(form, { title, titleStyle, count, status, actions, inset, children, headingId, }) {
|
|
152
|
-
const heading = title && (_jsx(CardHeading, { id: headingId, title: title, titleStyle: titleStyle, count: count, status: status, bar: form.bar, spaced: !form.bar && !actions, size: form.size }));
|
|
153
|
-
const grow = form.footer && "flex-1";
|
|
154
|
-
const band = insetBand(form, inset);
|
|
155
|
-
if (form.bar) {
|
|
156
|
-
return (_jsxs(_Fragment, { children: [_jsx(CardBar, { heading: heading, actions: actions, size: form.size }), _jsxs("div", { className: cx(grow, !form.flush && BODY_PAD[form.size]) || undefined, children: [band, children] })] }));
|
|
157
|
-
}
|
|
158
|
-
const content = (_jsxs(_Fragment, { children: [form.icon && _jsx(CardIcon, { name: form.icon, size: form.size }), _jsx(CardHeader, { heading: heading, actions: actions, size: form.size }), band, children] }));
|
|
159
|
-
// With a footer the root has no padding, so the body pads itself and takes the room left over.
|
|
160
|
-
return form.footer ? (_jsx("div", { className: cx(grow, !form.flush && BODY_PAD[form.size]), children: content })) : (content);
|
|
161
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { IconName } from "./icons.js";
|
|
3
|
+
import type { Size } from "./size.js";
|
|
4
|
+
/** The padding of a card's body by `size`. */
|
|
5
|
+
export declare const BODY_PAD: Record<Size, string>;
|
|
6
|
+
/** The options a card resolved (`cardForm` in `Card.tsx`) that shape its body. */
|
|
7
|
+
type BodyForm = {
|
|
8
|
+
flush: boolean;
|
|
9
|
+
bar: boolean;
|
|
10
|
+
dashed: boolean;
|
|
11
|
+
icon: IconName | undefined;
|
|
12
|
+
fill: boolean;
|
|
13
|
+
footer: boolean;
|
|
14
|
+
size: Size;
|
|
15
|
+
};
|
|
16
|
+
type BodyParts = {
|
|
17
|
+
title: string | undefined;
|
|
18
|
+
titleStyle: "overline" | "name" | undefined;
|
|
19
|
+
count: number | undefined;
|
|
20
|
+
status: ReactNode;
|
|
21
|
+
actions: ReactNode;
|
|
22
|
+
inset: ReactNode;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
headingId: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
export declare function cardBody(form: BodyForm, parts: BodyParts): ReactNode;
|
|
27
|
+
export {};
|
package/dist/CardBody.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CardBar, CardHeader, CardHeading, CardIcon, CardInset } from "./CardParts.js";
|
|
3
|
+
import { cx } from "./cx.js";
|
|
4
|
+
/** The padding of a card's body by `size`. */
|
|
5
|
+
export const BODY_PAD = { sm: "p-3", md: "p-5", lg: "p-6" };
|
|
6
|
+
function insetBand(form, inset) {
|
|
7
|
+
if (!inset || form.dashed)
|
|
8
|
+
return null;
|
|
9
|
+
return (_jsx(CardInset, { size: form.size, bleed: !form.flush, children: inset }));
|
|
10
|
+
}
|
|
11
|
+
/** With `fill` the body takes the room the header (and a footer) leave, and only it scrolls. */
|
|
12
|
+
const SCROLL = "min-h-0 flex-1 overflow-y-auto";
|
|
13
|
+
/** The padding the body takes itself, when the root has none: never on a flush card. */
|
|
14
|
+
const pad = (form) => !form.flush && BODY_PAD[form.size];
|
|
15
|
+
/** The icon, header, inset band and children of a card with no bar. */
|
|
16
|
+
function plainBody(form, heading, parts) {
|
|
17
|
+
const content = (_jsxs(_Fragment, { children: [form.icon && _jsx(CardIcon, { name: form.icon, size: form.size }), _jsx(CardHeader, { heading: heading, actions: parts.actions, size: form.size }), insetBand(form, parts.inset), form.fill ? _jsx("div", { className: SCROLL, children: parts.children }) : parts.children] }));
|
|
18
|
+
if (form.fill) {
|
|
19
|
+
return _jsx("div", { className: cx("flex min-h-0 flex-1 flex-col", pad(form)), children: content });
|
|
20
|
+
}
|
|
21
|
+
// With a footer the root has no padding, so the body pads itself and takes the room left over.
|
|
22
|
+
return form.footer ? _jsx("div", { className: cx("flex-1", pad(form)), children: content }) : content;
|
|
23
|
+
}
|
|
24
|
+
export function cardBody(form, parts) {
|
|
25
|
+
const heading = parts.title && (_jsx(CardHeading, { id: parts.headingId, title: parts.title, titleStyle: parts.titleStyle, count: parts.count, status: parts.status, bar: form.bar, spaced: !form.bar && !parts.actions, size: form.size }));
|
|
26
|
+
if (!form.bar)
|
|
27
|
+
return plainBody(form, heading, parts);
|
|
28
|
+
const grow = form.fill ? SCROLL : form.footer && "flex-1";
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx(CardBar, { heading: heading, actions: parts.actions, size: form.size }), _jsxs("div", { className: cx(grow, pad(form)) || undefined, children: [insetBand(form, parts.inset), parts.children] })] }));
|
|
30
|
+
}
|
package/dist/Markdown.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
// A minimal, SAFE markdown renderer (headings / bullets / bold). No
|
|
3
|
-
// dangerouslySetInnerHTML
|
|
3
|
+
// dangerouslySetInnerHTML: text is rendered as React nodes, so it can't inject HTML.
|
|
4
4
|
function inline(text) {
|
|
5
5
|
return text.split(/(\*\*[^*]+\*\*)/g).map((p, i) => p.startsWith("**") && p.endsWith("**") ? (_jsx("strong", { className: "font-semibold text-slate-900", children: p.slice(2, -2) }, i)) : (_jsx("span", { children: p }, i)));
|
|
6
6
|
}
|
package/dist/ProgressBar.d.ts
CHANGED
|
@@ -7,16 +7,16 @@ export type BarTone = "accent" | "positive" | "warn" | "negative";
|
|
|
7
7
|
export type BarSize = Size;
|
|
8
8
|
export declare const BAR_FILL: Record<BarTone, string>;
|
|
9
9
|
export declare const BAR_TRACK: Record<BarSize, string>;
|
|
10
|
-
/** `part` as a percent of `whole`, held
|
|
10
|
+
/** `part` as a percent of `whole`, held between 0 and 100; 0 when either is not positive. */
|
|
11
11
|
export declare function sharePercent(part: number, whole: number): number;
|
|
12
12
|
/** The share as a whole percent for a screen reader: never 0 or 100 unless it truly is. */
|
|
13
13
|
export declare function announcedPercent(part: number, whole: number): number;
|
|
14
14
|
/**
|
|
15
|
-
* A thin horizontal bar with a filled share, for progress toward a total. `value` is held
|
|
16
|
-
* 0
|
|
15
|
+
* A thin horizontal bar with a filled share, for progress toward a total. `value` is held
|
|
16
|
+
* between 0 and `max`. `label` names what it measures, for a screen reader, which also hears the percentage.
|
|
17
17
|
*/
|
|
18
18
|
export declare function ProgressBar({ value, max, label, tone, size, ...rest }: {
|
|
19
|
-
/** How far along, held
|
|
19
|
+
/** How far along, held between 0 and `max`. */
|
|
20
20
|
value: number;
|
|
21
21
|
/** Defaults to 100. */
|
|
22
22
|
max?: number;
|
package/dist/ProgressBar.js
CHANGED
|
@@ -11,7 +11,7 @@ export const BAR_TRACK = {
|
|
|
11
11
|
md: "h-2",
|
|
12
12
|
lg: "h-3",
|
|
13
13
|
};
|
|
14
|
-
/** `part` as a percent of `whole`, held
|
|
14
|
+
/** `part` as a percent of `whole`, held between 0 and 100; 0 when either is not positive. */
|
|
15
15
|
export function sharePercent(part, whole) {
|
|
16
16
|
if (!(whole > 0) || !(part > 0))
|
|
17
17
|
return 0;
|
|
@@ -23,8 +23,8 @@ export function announcedPercent(part, whole) {
|
|
|
23
23
|
return share <= 0 || share >= 100 ? share : Math.min(99, Math.max(1, Math.round(share)));
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* A thin horizontal bar with a filled share, for progress toward a total. `value` is held
|
|
27
|
-
* 0
|
|
26
|
+
* A thin horizontal bar with a filled share, for progress toward a total. `value` is held
|
|
27
|
+
* between 0 and `max`. `label` names what it measures, for a screen reader, which also hears the percentage.
|
|
28
28
|
*/
|
|
29
29
|
export function ProgressBar({ value, max = 100, label, tone = "accent", size = "md", ...rest }) {
|
|
30
30
|
const share = sharePercent(value, max);
|
package/dist/highlight.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* else: no HTML, no client JS. `CodeText` maps a run to a palette utility, so a theme recolours
|
|
4
4
|
* highlighted code exactly as it recolours everything else.
|
|
5
5
|
*
|
|
6
|
-
* The grammars are deliberately shallow
|
|
6
|
+
* The grammars are deliberately shallow: enough for examples, not a compiler front end. `text`
|
|
7
7
|
* returns the code as one plain run, so nothing is coloured where no language is named. The
|
|
8
8
|
* languages: `text`, `tsx`, `ts`, `js`, `json`, `bash`, `console` (a shell session: `$ ` commands
|
|
9
9
|
* scanned as bash, everything else output), `css`, `html` (with `<script>` scanned as JS and
|
package/dist/highlight.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* else: no HTML, no client JS. `CodeText` maps a run to a palette utility, so a theme recolours
|
|
4
4
|
* highlighted code exactly as it recolours everything else.
|
|
5
5
|
*
|
|
6
|
-
* The grammars are deliberately shallow
|
|
6
|
+
* The grammars are deliberately shallow: enough for examples, not a compiler front end. `text`
|
|
7
7
|
* returns the code as one plain run, so nothing is coloured where no language is named. The
|
|
8
8
|
* languages: `text`, `tsx`, `ts`, `js`, `json`, `bash`, `console` (a shell session: `$ ` commands
|
|
9
9
|
* scanned as bash, everything else output), `css`, `html` (with `<script>` scanned as JS and
|
|
@@ -210,7 +210,7 @@ function childrenStep(scan) {
|
|
|
210
210
|
takeChar(scan);
|
|
211
211
|
}
|
|
212
212
|
/**
|
|
213
|
-
* JS, TS and TSX. Three modes
|
|
213
|
+
* JS, TS and TSX. Three modes (code, inside a tag, and children text) keep an apostrophe in
|
|
214
214
|
* `<p>Don't</p>` as text rather than the start of a runaway string, and every open element records
|
|
215
215
|
* the mode to return to, so a `{…}` expression that holds JSX closes back into code.
|
|
216
216
|
*/
|
package/dist/typography.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export type InlineTypographyProps<E extends ElementType> = Own & {
|
|
|
56
56
|
as?: E;
|
|
57
57
|
} & Omit<ComponentPropsWithoutRef<E>, keyof Own | "as" | "className" | "style">;
|
|
58
58
|
/**
|
|
59
|
-
* Marketing-scale headline
|
|
59
|
+
* Marketing-scale headline: the one line at the top of a public page. `h1`,
|
|
60
60
|
* text-4xl/tight semibold, primary text, growing to text-6xl on wider screens.
|
|
61
61
|
* `PageHeading` is the title of an ordinary page, a step below; a brochure hero needs a display
|
|
62
62
|
* scale, so it gets its own pinned role rather than callers overriding PageHeading's size.
|
package/dist/typography.js
CHANGED
|
@@ -25,7 +25,7 @@ const ALIGN = {
|
|
|
25
25
|
/**
|
|
26
26
|
* Build a semantic component that pins `variantClass` (size + weight + tracking)
|
|
27
27
|
* and a default colour. The scale/weight for each role therefore lives in exactly
|
|
28
|
-
* one place (the call below), and callers can only vary the colour and the element
|
|
28
|
+
* one place (the call below), and callers can only vary the colour and the element,
|
|
29
29
|
* never the pinned size/weight, and never with a class.
|
|
30
30
|
*/
|
|
31
31
|
function typography(displayName, defaultAs, variantClass, defaultColor) {
|
|
@@ -70,7 +70,7 @@ function plainText(children) {
|
|
|
70
70
|
}
|
|
71
71
|
// --- Structural roles (block by default) -----------------------------------
|
|
72
72
|
/**
|
|
73
|
-
* Marketing-scale headline
|
|
73
|
+
* Marketing-scale headline: the one line at the top of a public page. `h1`,
|
|
74
74
|
* text-4xl/tight semibold, primary text, growing to text-6xl on wider screens.
|
|
75
75
|
* `PageHeading` is the title of an ordinary page, a step below; a brochure hero needs a display
|
|
76
76
|
* scale, so it gets its own pinned role rather than callers overriding PageHeading's size.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hackcove/ui-react",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.10",
|
|
4
4
|
"description": "Hack Cove React components: primitives, typography roles and a safe Markdown renderer, written against Tailwind v4 utilities. Pair with a theme from @hackcove/themes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|