@supertype.ai/foundations 0.1.28 → 0.1.29
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 +2 -2
- package/dist/blocks/badge.d.ts +24 -0
- package/dist/blocks/badge.js +80 -0
- package/dist/blocks/button.d.ts +24 -0
- package/dist/blocks/button.js +117 -0
- package/dist/blocks/callout.d.ts +2 -39
- package/dist/blocks/callout.js +13 -38
- package/dist/blocks/card.js +22 -2
- package/dist/blocks/focus.d.ts +9 -0
- package/dist/blocks/focus.js +9 -0
- package/dist/blocks/index.d.ts +3 -1
- package/dist/blocks/index.js +3 -1
- package/dist/blocks/interactive-accordion.js +2 -1
- package/dist/blocks/render-as.d.ts +11 -0
- package/dist/blocks/render-as.js +22 -0
- package/dist/blocks/segment.d.ts +22 -14
- package/dist/blocks/segment.js +23 -14
- package/dist/blocks/tabs.d.ts +52 -17
- package/dist/blocks/tabs.js +86 -37
- package/dist/cjs/eslint.js +17 -8
- package/dist/contrast.d.ts +29 -0
- package/dist/contrast.js +26 -8
- package/dist/eslint.d.ts +24 -6
- package/dist/eslint.js +16 -8
- package/dist/essay/essay.js +14 -6
- package/dist/essay/index.d.ts +1 -1
- package/dist/essay/index.js +1 -1
- package/dist/essay/layout.d.ts +36 -0
- package/dist/essay/layout.js +32 -0
- package/dist/essay/reading.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/mdx.d.ts +26 -4
- package/dist/mdx.js +30 -4
- package/dist/tone.d.ts +101 -0
- package/dist/tone.js +111 -0
- package/dist/typography/header.d.ts +30 -0
- package/dist/typography/header.js +7 -2
- package/dist/typography/highlight.d.ts +4 -4
- package/dist/typography/highlight.js +4 -4
- package/dist/typography/paragraph.d.ts +3 -13
- package/dist/typography/paragraph.js +13 -9
- package/llms.txt +34 -12
- package/package.json +2 -1
- package/src/theme.css +70 -26
package/dist/essay/layout.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentProps, ComponentType, ReactNode } from "react";
|
|
2
|
+
import type { TocHeading } from "./toc.js";
|
|
2
3
|
/**
|
|
3
4
|
* Three tracks with the third empty: two would push the prose off-centre the
|
|
4
5
|
* moment an aside appeared, setting body copy on a different axis per page.
|
|
@@ -24,6 +25,41 @@ import type { ComponentProps, ComponentType, ReactNode } from "react";
|
|
|
24
25
|
export declare function EssayColumns({ aside, children, className, ...props }: ComponentProps<"div"> & {
|
|
25
26
|
aside?: ReactNode;
|
|
26
27
|
}): import("react").JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* The margin track's contents, pinned as the column scrolls.
|
|
30
|
+
*
|
|
31
|
+
* The offset is stated here and nowhere else: it has to clear the same sticky
|
|
32
|
+
* site nav that `EssaySection`'s `scroll-mt` clears, and two literals a file
|
|
33
|
+
* apart is how an anchored heading ends up under the chrome that the rail
|
|
34
|
+
* scrolled it to.
|
|
35
|
+
*/
|
|
36
|
+
export declare function EssayAside({ children, className, }: {
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
className?: string;
|
|
39
|
+
}): import("react").JSX.Element;
|
|
40
|
+
/**
|
|
41
|
+
* The join between a header and the body under it: the one place in the package that draws
|
|
42
|
+
* it, so a seam cannot be ruled twice by a header and a layout that cannot see each other.
|
|
43
|
+
*
|
|
44
|
+
* The rule is for the narrow layout alone. Past `@6xl` the margin rail marks the join, and
|
|
45
|
+
* the header's own bottom padding is the whole of the gap.
|
|
46
|
+
*/
|
|
47
|
+
export declare function EssayBody({ children, className, }: {
|
|
48
|
+
children: ReactNode;
|
|
49
|
+
className?: string;
|
|
50
|
+
}): import("react").JSX.Element;
|
|
51
|
+
/**
|
|
52
|
+
* The reading column with a scroll-spied rail in its margin: an article whose
|
|
53
|
+
* body is prose or MDX, rather than the declared sections `EssayLayout` sets.
|
|
54
|
+
*
|
|
55
|
+
* The rail is dropped when a piece has no headings, so a short post gets a
|
|
56
|
+
* centred measure instead of a margin holding an empty nav.
|
|
57
|
+
*/
|
|
58
|
+
export declare function ReadingLayout({ headings, children, className, }: {
|
|
59
|
+
headings: readonly TocHeading[];
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
className?: string;
|
|
62
|
+
}): import("react").JSX.Element;
|
|
27
63
|
/** A separator between meta items. Decorative, so it is hidden from assistive tech. */
|
|
28
64
|
export declare function MetaDot({ className }: {
|
|
29
65
|
className?: string;
|
package/dist/essay/layout.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../cn.js";
|
|
3
|
+
import { ReadingRail } from "./reading.js";
|
|
3
4
|
/**
|
|
4
5
|
* Three tracks with the third empty: two would push the prose off-centre the
|
|
5
6
|
* moment an aside appeared, setting body copy on a different axis per page.
|
|
@@ -25,6 +26,37 @@ import { cn } from "../cn.js";
|
|
|
25
26
|
export function EssayColumns({ aside, children, className, ...props }) {
|
|
26
27
|
return (_jsx("div", { className: "@container w-full", children: _jsxs("div", { className: cn("mx-auto grid w-full max-w-6xl gap-10 px-6", "@6xl:grid-cols-[1fr_minmax(0,42rem)_1fr] @6xl:gap-0", "@7xl:max-w-7xl @7xl:grid-cols-[1fr_minmax(0,44rem)_1fr]", "@min-[84rem]:max-w-[84rem] @min-[84rem]:grid-cols-[1fr_minmax(0,46rem)_1fr]", className), ...props, children: [_jsx("div", { className: "hidden @6xl:block @6xl:pr-10", children: aside }), _jsx("div", { className: "mx-auto w-full min-w-0 max-w-2xl @6xl:max-w-none", children: children }), _jsx("div", { className: "hidden @6xl:block" })] }) }));
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The margin track's contents, pinned as the column scrolls.
|
|
31
|
+
*
|
|
32
|
+
* The offset is stated here and nowhere else: it has to clear the same sticky
|
|
33
|
+
* site nav that `EssaySection`'s `scroll-mt` clears, and two literals a file
|
|
34
|
+
* apart is how an anchored heading ends up under the chrome that the rail
|
|
35
|
+
* scrolled it to.
|
|
36
|
+
*/
|
|
37
|
+
export function EssayAside({ children, className, }) {
|
|
38
|
+
return _jsx("div", { className: cn("sticky top-24", className), children: children });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The join between a header and the body under it: the one place in the package that draws
|
|
42
|
+
* it, so a seam cannot be ruled twice by a header and a layout that cannot see each other.
|
|
43
|
+
*
|
|
44
|
+
* The rule is for the narrow layout alone. Past `@6xl` the margin rail marks the join, and
|
|
45
|
+
* the header's own bottom padding is the whole of the gap.
|
|
46
|
+
*/
|
|
47
|
+
export function EssayBody({ children, className, }) {
|
|
48
|
+
return (_jsx("div", { className: cn("border-t border-border pt-12 @6xl:border-t-0 @6xl:pt-0", className), children: children }));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The reading column with a scroll-spied rail in its margin: an article whose
|
|
52
|
+
* body is prose or MDX, rather than the declared sections `EssayLayout` sets.
|
|
53
|
+
*
|
|
54
|
+
* The rail is dropped when a piece has no headings, so a short post gets a
|
|
55
|
+
* centred measure instead of a margin holding an empty nav.
|
|
56
|
+
*/
|
|
57
|
+
export function ReadingLayout({ headings, children, className, }) {
|
|
58
|
+
return (_jsx(EssayColumns, { className: cn("pb-16 sm:pb-24", className), aside: headings.length > 0 ? (_jsx(EssayAside, { children: _jsx(ReadingRail, { headings: headings }) })) : undefined, children: _jsx(EssayBody, { children: children }) }));
|
|
59
|
+
}
|
|
28
60
|
/** A separator between meta items. Decorative, so it is hidden from assistive tech. */
|
|
29
61
|
export function MetaDot({ className }) {
|
|
30
62
|
return (_jsx("span", { "aria-hidden": true, className: cn("text-muted-foreground/50", className), children: "\u00B7" }));
|
package/dist/essay/reading.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export declare function ReadingProgressBar({ className }: {
|
|
|
12
12
|
* stores, so mounting them together costs one scroll subscription, not two.
|
|
13
13
|
*/
|
|
14
14
|
export declare function ReadingRail({ headings, className, }: {
|
|
15
|
-
headings: TocHeading[];
|
|
15
|
+
headings: readonly TocHeading[];
|
|
16
16
|
className?: string;
|
|
17
17
|
}): import("react").JSX.Element | null;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export { cn } from "./cn.js";
|
|
2
|
+
// The semantic colour vocabulary. Exported from the root because typography
|
|
3
|
+
// takes it too — a link has a tone, and it is the same seven a button has.
|
|
4
|
+
// `toneClass` only: the raw table and its derived half used to ship separately,
|
|
5
|
+
// and the order they were combined in was load-bearing.
|
|
6
|
+
export { toneClass, impliedTone } from "./tone.js";
|
|
2
7
|
export * from "./typography/index.js";
|
|
3
8
|
// NOTE: blocks, the MDX map, and the Shiki plugin are all deliberately absent
|
|
4
9
|
// from this barrel.
|
package/dist/mdx.d.ts
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
2
|
import { Disclosure, DisclosureGroup } from "./blocks/accordion.js";
|
|
3
3
|
import { Callout } from "./blocks/callout.js";
|
|
4
4
|
import { Card, Cards } from "./blocks/card.js";
|
|
5
5
|
import { Step, Steps } from "./blocks/steps.js";
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* `<Tabs items={["npm","pnpm"]}>` with a `<Tab>` per panel: the shape an MDX author
|
|
8
|
+
* writes, and the only place in the package that speaks it.
|
|
9
|
+
*
|
|
10
|
+
* Children pair with `items` **by position**, since a caller writing markdown has no
|
|
11
|
+
* value to bind. `value` on a `<Tab>` is for the author's eye and is not matched —
|
|
12
|
+
* matching it would silently drop a panel the moment a label was edited. The label is
|
|
13
|
+
* the value here, so the strip still survives a reorder.
|
|
14
|
+
*
|
|
15
|
+
* This lived in `TabGroup` and made `TabGroup` unusable everywhere else: an app has data,
|
|
16
|
+
* not positional children, so every one of them rebuilt the adapter by hand. Positional
|
|
17
|
+
* children are an authoring convenience, not a component API, so they stop here.
|
|
18
|
+
*/
|
|
19
|
+
declare function MdxTabs({ items, children }: {
|
|
20
|
+
items: string[];
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
23
|
+
/** `value` names the panel at the call site; it is not used for matching. */
|
|
24
|
+
declare function MdxTab({ children }: {
|
|
25
|
+
value?: string;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
7
28
|
/**
|
|
8
29
|
* Rendered from markdown syntax, so there is no call site and no knob — a knob
|
|
9
30
|
* here is one every project turns differently. Headings carry variants and this
|
|
@@ -19,8 +40,8 @@ export declare const proseMdxComponents: {
|
|
|
19
40
|
Accordions: typeof DisclosureGroup;
|
|
20
41
|
Accordion: typeof Disclosure;
|
|
21
42
|
Banner: (props: ComponentProps<typeof Callout>) => import("react").JSX.Element;
|
|
22
|
-
Tabs: typeof
|
|
23
|
-
Tab: typeof
|
|
43
|
+
Tabs: typeof MdxTabs;
|
|
44
|
+
Tab: typeof MdxTab;
|
|
24
45
|
Steps: typeof Steps;
|
|
25
46
|
Step: typeof Step;
|
|
26
47
|
h1: (props: ComponentProps<"h1">) => import("react").JSX.Element;
|
|
@@ -45,3 +66,4 @@ export declare const proseMdxComponents: {
|
|
|
45
66
|
th: (props: ComponentProps<"th">) => import("react").JSX.Element;
|
|
46
67
|
td: (props: ComponentProps<"td">) => import("react").JSX.Element;
|
|
47
68
|
};
|
|
69
|
+
export {};
|
package/dist/mdx.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Children } from "react";
|
|
2
3
|
import { TypographyH1, TypographyH2, TypographyH3, TypographyH4, } from "./typography/header.js";
|
|
3
4
|
import { cn } from "./cn.js";
|
|
4
5
|
import Image from "next/image";
|
|
@@ -6,8 +7,33 @@ import { Disclosure, DisclosureGroup } from "./blocks/accordion.js";
|
|
|
6
7
|
import { Callout } from "./blocks/callout.js";
|
|
7
8
|
import { Card, Cards } from "./blocks/card.js";
|
|
8
9
|
import { Step, Steps } from "./blocks/steps.js";
|
|
9
|
-
import { TabGroup
|
|
10
|
+
import { TabGroup } from "./blocks/tabs.js";
|
|
10
11
|
import { TypographyProse, TypographyProseList, TypographyLink, } from "./typography/paragraph.js";
|
|
12
|
+
/**
|
|
13
|
+
* `<Tabs items={["npm","pnpm"]}>` with a `<Tab>` per panel: the shape an MDX author
|
|
14
|
+
* writes, and the only place in the package that speaks it.
|
|
15
|
+
*
|
|
16
|
+
* Children pair with `items` **by position**, since a caller writing markdown has no
|
|
17
|
+
* value to bind. `value` on a `<Tab>` is for the author's eye and is not matched —
|
|
18
|
+
* matching it would silently drop a panel the moment a label was edited. The label is
|
|
19
|
+
* the value here, so the strip still survives a reorder.
|
|
20
|
+
*
|
|
21
|
+
* This lived in `TabGroup` and made `TabGroup` unusable everywhere else: an app has data,
|
|
22
|
+
* not positional children, so every one of them rebuilt the adapter by hand. Positional
|
|
23
|
+
* children are an authoring convenience, not a component API, so they stop here.
|
|
24
|
+
*/
|
|
25
|
+
function MdxTabs({ items, children }) {
|
|
26
|
+
const panels = Children.toArray(children);
|
|
27
|
+
return (_jsx(TabGroup, { tabs: items.map((label, i) => ({
|
|
28
|
+
value: label,
|
|
29
|
+
label,
|
|
30
|
+
content: panels[i],
|
|
31
|
+
})) }));
|
|
32
|
+
}
|
|
33
|
+
/** `value` names the panel at the call site; it is not used for matching. */
|
|
34
|
+
function MdxTab({ children }) {
|
|
35
|
+
return _jsx(_Fragment, { children: children });
|
|
36
|
+
}
|
|
11
37
|
/**
|
|
12
38
|
* Rendered from markdown syntax, so there is no call site and no knob — a knob
|
|
13
39
|
* here is one every project turns differently. Headings carry variants and this
|
|
@@ -26,8 +52,8 @@ export const proseMdxComponents = {
|
|
|
26
52
|
Accordions: DisclosureGroup,
|
|
27
53
|
Accordion: Disclosure,
|
|
28
54
|
Banner: (props) => (_jsx(Callout, { density: "editorial", ...props })),
|
|
29
|
-
Tabs:
|
|
30
|
-
Tab,
|
|
55
|
+
Tabs: MdxTabs,
|
|
56
|
+
Tab: MdxTab,
|
|
31
57
|
Steps,
|
|
32
58
|
Step,
|
|
33
59
|
h1: (props) => (_jsx(TypographyH1, { variant: "display", ...props })),
|
package/dist/tone.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One semantic colour vocabulary, for every component that carries meaning in a
|
|
3
|
+
* hue: Button, Callout, TypographyLink. Before this there were three lists —
|
|
4
|
+
* Callout's `muted`, TypographyLink's `foreground` and a button's `default` were
|
|
5
|
+
* three spellings of "no meaning at all", and `accent` named `--accent` on a
|
|
6
|
+
* button and `--primary` in a callout.
|
|
7
|
+
*
|
|
8
|
+
* A component that wants a tone does not get to invent a name for one.
|
|
9
|
+
*
|
|
10
|
+
* Seven tones, seven tokens, one to one. That is the test for admitting a new
|
|
11
|
+
* one: `muted` is `--muted`, `warn` is `--warn`, and a proposed tone with no
|
|
12
|
+
* token of its own is a second name for a tone that already has one. It is what
|
|
13
|
+
* ruled out `neutral` (no such token, and `muted` is the word the rest of the
|
|
14
|
+
* package already uses — `--muted-foreground`, `TypographyMuted`,
|
|
15
|
+
* `TypographyP tone="muted"`), `accent` (that is `--primary`'s hover tint, so a
|
|
16
|
+
* washed `primary` renders the same control), and `info` (a real token, but
|
|
17
|
+
* `success | warn | destructive` is already the complete good/careful/bad
|
|
18
|
+
* triad, and nothing in either app had ever reached for a fourth).
|
|
19
|
+
*
|
|
20
|
+
* The rule the package already applies to every tinted role holds here too, and
|
|
21
|
+
* is why each row names three values rather than one: `--tone-fill` is a surface,
|
|
22
|
+
* `--tone-ink` is the label printed on it, and `--tone-hue` is the same colour
|
|
23
|
+
* used as words. A fill is a mark and clears 3:1; an ink is read and clears
|
|
24
|
+
* 4.5:1. Tuning one value to do both jobs sinks whichever job it was not tuned
|
|
25
|
+
* for. `checkSignals` in /contrast is the test.
|
|
26
|
+
*
|
|
27
|
+
* Custom properties rather than thirty-five class strings: a tone declares, a
|
|
28
|
+
* component spends. `Button` has five variants and `Callout` two densities, and
|
|
29
|
+
* neither has a per-tone branch anywhere in it.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* `muted` is the only row that names a fourth value. A hairline derived from the
|
|
33
|
+
* ink at 45% is right for a hue and wrong for the absence of one; `--border` is
|
|
34
|
+
* the tuned answer there, and it is not a wash of `--foreground`.
|
|
35
|
+
*
|
|
36
|
+
* Its hue is `--foreground` rather than `--muted-foreground` on purpose. `muted`
|
|
37
|
+
* says the control carries no meaning, not that it carries less contrast — a
|
|
38
|
+
* cancel button beside a save button is quiet because it is not filled in, and
|
|
39
|
+
* its label still has to be read.
|
|
40
|
+
*
|
|
41
|
+
* `brand` is the consumer's own identity, and the only row whose token the
|
|
42
|
+
* package does not define. It falls back to `--primary`, so an app that has no
|
|
43
|
+
* identity hue of its own gets its principal one — the tone stays meaningful
|
|
44
|
+
* everywhere rather than rendering nothing in the apps that skipped it.
|
|
45
|
+
*/
|
|
46
|
+
export declare const TONE: {
|
|
47
|
+
/** No meaning: chrome, toolbars, anything that must not compete. */
|
|
48
|
+
readonly muted: "[--tone-fill:var(--muted)] [--tone-ink:var(--foreground)] [--tone-hue:var(--foreground)] [--tone-line:var(--border)]";
|
|
49
|
+
/** The principal action, and the package's default wherever a tone is optional. */
|
|
50
|
+
readonly primary: "[--tone-fill:var(--primary)] [--tone-ink:var(--primary-foreground)] [--tone-hue:var(--primary)]";
|
|
51
|
+
/** The warm accent. `--secondary-ink` is its readable cut; the fill is not. */
|
|
52
|
+
readonly secondary: "[--tone-fill:var(--secondary)] [--tone-ink:var(--secondary-foreground)] [--tone-hue:var(--secondary-ink)]";
|
|
53
|
+
/** The consumer's identity hue, if it defined one. Otherwise the principal one. */
|
|
54
|
+
readonly brand: "[--tone-fill:var(--brand,var(--primary))] [--tone-ink:var(--tint-foreground)] [--tone-hue:var(--brand-ink,var(--primary))]";
|
|
55
|
+
/** It worked. */
|
|
56
|
+
readonly success: "[--tone-fill:var(--success)] [--tone-ink:var(--tint-foreground)] [--tone-hue:var(--success-ink)]";
|
|
57
|
+
/** A footgun: the reader can still proceed, but not blindly. */
|
|
58
|
+
readonly warn: "[--tone-fill:var(--warn)] [--tone-ink:var(--tint-foreground)] [--tone-hue:var(--warn-ink)]";
|
|
59
|
+
/** It deletes something, or it already failed. */
|
|
60
|
+
readonly destructive: "[--tone-fill:var(--destructive)] [--tone-ink:var(--destructive-foreground)] [--tone-hue:var(--destructive)]";
|
|
61
|
+
};
|
|
62
|
+
export type Tone = keyof typeof TONE;
|
|
63
|
+
/**
|
|
64
|
+
* What every tone is worth once mixed, derived once here rather than eight times
|
|
65
|
+
* above. Declared before `TONE` in any class list, so `neutral` can override
|
|
66
|
+
* `--tone-line`.
|
|
67
|
+
*
|
|
68
|
+
* Expressed as variables rather than through Tailwind's `/10` modifier because
|
|
69
|
+
* the modifier's support for arbitrary custom properties is a moving target,
|
|
70
|
+
* while `color-mix` is what the modifier compiles to anyway — the same CSS, one
|
|
71
|
+
* layer less of trust.
|
|
72
|
+
*
|
|
73
|
+
* `--tone-fill-hover` mixes toward `--foreground` rather than darkening by a
|
|
74
|
+
* fixed amount, so a filled control deepens on the light theme and lifts on the
|
|
75
|
+
* dark one from a single declaration. A `dark:` override here is what the
|
|
76
|
+
* package's own ESLint rule exists to prevent.
|
|
77
|
+
*/
|
|
78
|
+
export declare const TONE_SURFACE: string;
|
|
79
|
+
/**
|
|
80
|
+
* A tone, as one class list. This is the only thing a component should need.
|
|
81
|
+
*
|
|
82
|
+
* It was two — `cn(TONE_SURFACE, TONE[tone])` — and the order was load-bearing
|
|
83
|
+
* in a way nothing enforced: the derived values have to be declared first so
|
|
84
|
+
* `muted` can override `--tone-line` with `--border`. Getting that backwards
|
|
85
|
+
* gave every quiet control a hairline washed out of its own ink, and the only
|
|
86
|
+
* thing standing between a caller and that bug was a comment. Two arguments
|
|
87
|
+
* whose order matters and whose values always travel together is one argument.
|
|
88
|
+
*/
|
|
89
|
+
export declare const toneClass: (tone: Tone) => string;
|
|
90
|
+
/**
|
|
91
|
+
* What an unstated tone means, given how much ink the component is spending.
|
|
92
|
+
* Shared, because `Button` and `Badge` both need it and two copies of a default
|
|
93
|
+
* is how two components come to disagree about what saying nothing means.
|
|
94
|
+
*
|
|
95
|
+
* Filling a control in is how a page says *this is the action*, so a filled one
|
|
96
|
+
* with nothing else stated is the principal one; anything less is chrome until a
|
|
97
|
+
* call site says otherwise. Measured, not assumed: 244 of the 246 buttons across
|
|
98
|
+
* both apps that name a variant name `outline` or `ghost`, and every one wants
|
|
99
|
+
* the page's own ink.
|
|
100
|
+
*/
|
|
101
|
+
export declare const impliedTone: (variant: string | null | undefined) => Tone;
|
package/dist/tone.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One semantic colour vocabulary, for every component that carries meaning in a
|
|
3
|
+
* hue: Button, Callout, TypographyLink. Before this there were three lists —
|
|
4
|
+
* Callout's `muted`, TypographyLink's `foreground` and a button's `default` were
|
|
5
|
+
* three spellings of "no meaning at all", and `accent` named `--accent` on a
|
|
6
|
+
* button and `--primary` in a callout.
|
|
7
|
+
*
|
|
8
|
+
* A component that wants a tone does not get to invent a name for one.
|
|
9
|
+
*
|
|
10
|
+
* Seven tones, seven tokens, one to one. That is the test for admitting a new
|
|
11
|
+
* one: `muted` is `--muted`, `warn` is `--warn`, and a proposed tone with no
|
|
12
|
+
* token of its own is a second name for a tone that already has one. It is what
|
|
13
|
+
* ruled out `neutral` (no such token, and `muted` is the word the rest of the
|
|
14
|
+
* package already uses — `--muted-foreground`, `TypographyMuted`,
|
|
15
|
+
* `TypographyP tone="muted"`), `accent` (that is `--primary`'s hover tint, so a
|
|
16
|
+
* washed `primary` renders the same control), and `info` (a real token, but
|
|
17
|
+
* `success | warn | destructive` is already the complete good/careful/bad
|
|
18
|
+
* triad, and nothing in either app had ever reached for a fourth).
|
|
19
|
+
*
|
|
20
|
+
* The rule the package already applies to every tinted role holds here too, and
|
|
21
|
+
* is why each row names three values rather than one: `--tone-fill` is a surface,
|
|
22
|
+
* `--tone-ink` is the label printed on it, and `--tone-hue` is the same colour
|
|
23
|
+
* used as words. A fill is a mark and clears 3:1; an ink is read and clears
|
|
24
|
+
* 4.5:1. Tuning one value to do both jobs sinks whichever job it was not tuned
|
|
25
|
+
* for. `checkSignals` in /contrast is the test.
|
|
26
|
+
*
|
|
27
|
+
* Custom properties rather than thirty-five class strings: a tone declares, a
|
|
28
|
+
* component spends. `Button` has five variants and `Callout` two densities, and
|
|
29
|
+
* neither has a per-tone branch anywhere in it.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* `muted` is the only row that names a fourth value. A hairline derived from the
|
|
33
|
+
* ink at 45% is right for a hue and wrong for the absence of one; `--border` is
|
|
34
|
+
* the tuned answer there, and it is not a wash of `--foreground`.
|
|
35
|
+
*
|
|
36
|
+
* Its hue is `--foreground` rather than `--muted-foreground` on purpose. `muted`
|
|
37
|
+
* says the control carries no meaning, not that it carries less contrast — a
|
|
38
|
+
* cancel button beside a save button is quiet because it is not filled in, and
|
|
39
|
+
* its label still has to be read.
|
|
40
|
+
*
|
|
41
|
+
* `brand` is the consumer's own identity, and the only row whose token the
|
|
42
|
+
* package does not define. It falls back to `--primary`, so an app that has no
|
|
43
|
+
* identity hue of its own gets its principal one — the tone stays meaningful
|
|
44
|
+
* everywhere rather than rendering nothing in the apps that skipped it.
|
|
45
|
+
*/
|
|
46
|
+
export const TONE = {
|
|
47
|
+
/** No meaning: chrome, toolbars, anything that must not compete. */
|
|
48
|
+
muted: "[--tone-fill:var(--muted)] [--tone-ink:var(--foreground)] [--tone-hue:var(--foreground)] [--tone-line:var(--border)]",
|
|
49
|
+
/** The principal action, and the package's default wherever a tone is optional. */
|
|
50
|
+
primary: "[--tone-fill:var(--primary)] [--tone-ink:var(--primary-foreground)] [--tone-hue:var(--primary)]",
|
|
51
|
+
/** The warm accent. `--secondary-ink` is its readable cut; the fill is not. */
|
|
52
|
+
secondary: "[--tone-fill:var(--secondary)] [--tone-ink:var(--secondary-foreground)] [--tone-hue:var(--secondary-ink)]",
|
|
53
|
+
/** The consumer's identity hue, if it defined one. Otherwise the principal one. */
|
|
54
|
+
brand: "[--tone-fill:var(--brand,var(--primary))] [--tone-ink:var(--tint-foreground)] [--tone-hue:var(--brand-ink,var(--primary))]",
|
|
55
|
+
/** It worked. */
|
|
56
|
+
success: "[--tone-fill:var(--success)] [--tone-ink:var(--tint-foreground)] [--tone-hue:var(--success-ink)]",
|
|
57
|
+
/** A footgun: the reader can still proceed, but not blindly. */
|
|
58
|
+
warn: "[--tone-fill:var(--warn)] [--tone-ink:var(--tint-foreground)] [--tone-hue:var(--warn-ink)]",
|
|
59
|
+
/** It deletes something, or it already failed. */
|
|
60
|
+
destructive: "[--tone-fill:var(--destructive)] [--tone-ink:var(--destructive-foreground)] [--tone-hue:var(--destructive)]",
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* What every tone is worth once mixed, derived once here rather than eight times
|
|
64
|
+
* above. Declared before `TONE` in any class list, so `neutral` can override
|
|
65
|
+
* `--tone-line`.
|
|
66
|
+
*
|
|
67
|
+
* Expressed as variables rather than through Tailwind's `/10` modifier because
|
|
68
|
+
* the modifier's support for arbitrary custom properties is a moving target,
|
|
69
|
+
* while `color-mix` is what the modifier compiles to anyway — the same CSS, one
|
|
70
|
+
* layer less of trust.
|
|
71
|
+
*
|
|
72
|
+
* `--tone-fill-hover` mixes toward `--foreground` rather than darkening by a
|
|
73
|
+
* fixed amount, so a filled control deepens on the light theme and lifts on the
|
|
74
|
+
* dark one from a single declaration. A `dark:` override here is what the
|
|
75
|
+
* package's own ESLint rule exists to prevent.
|
|
76
|
+
*/
|
|
77
|
+
export const TONE_SURFACE = [
|
|
78
|
+
/** A hairline, or a rule carrying the tone. */
|
|
79
|
+
"[--tone-line:color-mix(in_oklab,var(--tone-hue)_45%,transparent)]",
|
|
80
|
+
/** A panel's tint: large area, so barely there. */
|
|
81
|
+
"[--tone-veil:color-mix(in_oklab,var(--tone-hue)_5%,transparent)]",
|
|
82
|
+
/** A control's tint at rest. */
|
|
83
|
+
"[--tone-wash:color-mix(in_oklab,var(--tone-hue)_10%,transparent)]",
|
|
84
|
+
/** The same control under the pointer. */
|
|
85
|
+
"[--tone-wash-hover:color-mix(in_oklab,var(--tone-hue)_18%,transparent)]",
|
|
86
|
+
/** A filled control under the pointer. */
|
|
87
|
+
"[--tone-fill-hover:color-mix(in_oklab,var(--tone-fill)_88%,var(--foreground))]",
|
|
88
|
+
].join(" ");
|
|
89
|
+
/**
|
|
90
|
+
* A tone, as one class list. This is the only thing a component should need.
|
|
91
|
+
*
|
|
92
|
+
* It was two — `cn(TONE_SURFACE, TONE[tone])` — and the order was load-bearing
|
|
93
|
+
* in a way nothing enforced: the derived values have to be declared first so
|
|
94
|
+
* `muted` can override `--tone-line` with `--border`. Getting that backwards
|
|
95
|
+
* gave every quiet control a hairline washed out of its own ink, and the only
|
|
96
|
+
* thing standing between a caller and that bug was a comment. Two arguments
|
|
97
|
+
* whose order matters and whose values always travel together is one argument.
|
|
98
|
+
*/
|
|
99
|
+
export const toneClass = (tone) => `${TONE_SURFACE} ${TONE[tone]}`;
|
|
100
|
+
/**
|
|
101
|
+
* What an unstated tone means, given how much ink the component is spending.
|
|
102
|
+
* Shared, because `Button` and `Badge` both need it and two copies of a default
|
|
103
|
+
* is how two components come to disagree about what saying nothing means.
|
|
104
|
+
*
|
|
105
|
+
* Filling a control in is how a page says *this is the action*, so a filled one
|
|
106
|
+
* with nothing else stated is the principal one; anything less is chrome until a
|
|
107
|
+
* call site says otherwise. Measured, not assumed: 244 of the 246 buttons across
|
|
108
|
+
* both apps that name a variant name `outline` or `ghost`, and every one wants
|
|
109
|
+
* the page's own ink.
|
|
110
|
+
*/
|
|
111
|
+
export const impliedTone = (variant) => variant == null || variant === "solid" ? "primary" : "muted";
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { type VariantProps } from "class-variance-authority";
|
|
2
2
|
import { type WithAs } from "./as.js";
|
|
3
|
+
/**
|
|
4
|
+
* The heading ladder. Four levels, one rung each.
|
|
5
|
+
*
|
|
6
|
+
* A heading does not pick its size — `--text-h1`…`--text-h4` in type.css do, and
|
|
7
|
+
* `.editorial` retunes all four together. That is the whole design: size is a
|
|
8
|
+
* property of the SURFACE, and level is the only thing a call site knows. When
|
|
9
|
+
* the rungs were shared with body copy the call site had to know both, which is
|
|
10
|
+
* how `larger` and `entry` appeared — variants whose entire job was to climb out
|
|
11
|
+
* of a rung that read fine in the product and landed under the paragraph on a
|
|
12
|
+
* marketing page. Retuning a surface now means editing two lines of CSS.
|
|
13
|
+
*
|
|
14
|
+
* `display` is a role rather than a size: the landing-page heading that has to
|
|
15
|
+
* outrank the same level in the docs. Size is all it changes — the slant it once
|
|
16
|
+
* carried turned out to be unwanted on every surface.
|
|
17
|
+
*
|
|
18
|
+
* Tailwind scans comments — never spell a class out here or it becomes a real
|
|
19
|
+
* utility.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The heading face, stated once. Anything wearing it composes this rather than
|
|
23
|
+
* respelling it: a second literal here is a level that forked, and a literal
|
|
24
|
+
* weight beside the face survives into `.editorial` and synthesises the
|
|
25
|
+
* single-weight serif. viably asserts there is exactly one of these strings.
|
|
26
|
+
*
|
|
27
|
+
* Exported so a non-heading can ask for the face alone. `headingClass()` below is the whole
|
|
28
|
+
* h2 ramp — face, anchor offset, margin reset, rung — and is right only for something that
|
|
29
|
+
* IS a heading wearing another tag. A pull quote is not, and took the ramp anyway because
|
|
30
|
+
* the face had no door, arriving with a `scroll-m-20` for an anchor it does not have.
|
|
31
|
+
*/
|
|
32
|
+
export declare const headingFace = "font-heading font-[number:var(--heading-weight)]";
|
|
3
33
|
declare const h1Variants: (props?: ({
|
|
4
34
|
variant?: "default" | "display" | null | undefined;
|
|
5
35
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -25,9 +25,14 @@ import { TextAs } from "./as.js";
|
|
|
25
25
|
* respelling it: a second literal here is a level that forked, and a literal
|
|
26
26
|
* weight beside the face survives into `.editorial` and synthesises the
|
|
27
27
|
* single-weight serif. viably asserts there is exactly one of these strings.
|
|
28
|
+
*
|
|
29
|
+
* Exported so a non-heading can ask for the face alone. `headingClass()` below is the whole
|
|
30
|
+
* h2 ramp — face, anchor offset, margin reset, rung — and is right only for something that
|
|
31
|
+
* IS a heading wearing another tag. A pull quote is not, and took the ramp anyway because
|
|
32
|
+
* the face had no door, arriving with a `scroll-m-20` for an anchor it does not have.
|
|
28
33
|
*/
|
|
29
|
-
const
|
|
30
|
-
const HEADING_BASE = `scroll-m-20 ${
|
|
34
|
+
export const headingFace = "font-heading font-[number:var(--heading-weight)]";
|
|
35
|
+
const HEADING_BASE = `scroll-m-20 ${headingFace} text-foreground`;
|
|
31
36
|
const h1Variants = cva(`${HEADING_BASE} tracking-tight`, {
|
|
32
37
|
variants: {
|
|
33
38
|
variant: {
|
|
@@ -14,10 +14,10 @@ import type { ComponentProps } from "react";
|
|
|
14
14
|
declare const MARKER_TONES: {
|
|
15
15
|
readonly primary: "var(--primary)";
|
|
16
16
|
readonly success: "var(--success)";
|
|
17
|
-
readonly ochre: "var(--ochre-
|
|
18
|
-
readonly terracotta: "var(--terracotta-
|
|
19
|
-
readonly sage: "var(--sage-
|
|
20
|
-
readonly fig: "var(--fig-
|
|
17
|
+
readonly ochre: "var(--ochre-ink)";
|
|
18
|
+
readonly terracotta: "var(--terracotta-ink)";
|
|
19
|
+
readonly sage: "var(--sage-ink)";
|
|
20
|
+
readonly fig: "var(--fig-ink)";
|
|
21
21
|
};
|
|
22
22
|
export type HighlightTone = keyof typeof MARKER_TONES;
|
|
23
23
|
/**
|
|
@@ -15,10 +15,10 @@ import { cn } from "../cn.js";
|
|
|
15
15
|
const MARKER_TONES = {
|
|
16
16
|
primary: "var(--primary)",
|
|
17
17
|
success: "var(--success)",
|
|
18
|
-
ochre: "var(--ochre-
|
|
19
|
-
terracotta: "var(--terracotta-
|
|
20
|
-
sage: "var(--sage-
|
|
21
|
-
fig: "var(--fig-
|
|
18
|
+
ochre: "var(--ochre-ink)",
|
|
19
|
+
terracotta: "var(--terracotta-ink)",
|
|
20
|
+
sage: "var(--sage-ink)",
|
|
21
|
+
fig: "var(--fig-ink)",
|
|
22
22
|
};
|
|
23
23
|
/** Wash density at a fraction of the theme's base alpha. */
|
|
24
24
|
const ink = (weight) => `color-mix(in srgb, var(--marker) calc(var(--marker-alpha) * ${weight}), transparent)`;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { type VariantProps } from "class-variance-authority";
|
|
2
2
|
import type { ComponentProps, ReactNode } from "react";
|
|
3
|
+
import { type Tone } from "../tone.js";
|
|
3
4
|
import { type WithAs } from "./as.js";
|
|
4
5
|
/** The body layer: two axes and no more. A paragraph picks a rung and an ink; a
|
|
5
6
|
* caption is always secondary ink and picks a size. `lead` was a third rung a
|
|
6
7
|
* breakpoint away from `prose`, and its standfirst role is now the eyebrow's. */
|
|
7
8
|
declare const pVariants: (props?: ({
|
|
8
9
|
variant?: "ui" | "prose" | null | undefined;
|
|
9
|
-
tone?: "
|
|
10
|
+
tone?: "muted" | "default" | null | undefined;
|
|
10
11
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
12
|
export type ParagraphVariants = VariantProps<typeof pVariants>;
|
|
12
13
|
export declare function TypographyP({ className, variant, tone, children, ...props }: ComponentProps<"p"> & ParagraphVariants): import("react").JSX.Element;
|
|
@@ -113,21 +114,10 @@ export declare function TypographyStat({ className, size, figures, children, ...
|
|
|
113
114
|
* optical correction — the mono face carries a taller x-height than the sans.
|
|
114
115
|
*/
|
|
115
116
|
export declare function TypographyInlineCode({ className, children, ...props }: ComponentProps<"code">): import("react").JSX.Element;
|
|
116
|
-
/**
|
|
117
|
-
* A statement about the surface, not the link: `foreground` inside a paragraph,
|
|
118
|
-
* `primary` when the link is the point of the line, `secondary` for a note
|
|
119
|
-
* beneath a hero where `primary` would compete with the CTA beside it.
|
|
120
|
-
*/
|
|
121
|
-
declare const LINK_TONES: {
|
|
122
|
-
readonly foreground: "font-medium text-foreground";
|
|
123
|
-
readonly primary: "font-medium text-primary";
|
|
124
|
-
readonly secondary: "text-secondary-ink";
|
|
125
|
-
};
|
|
126
|
-
export type LinkTone = keyof typeof LINK_TONES;
|
|
127
117
|
type TypographyLinkProps = Omit<ComponentProps<"a">, "href"> & {
|
|
128
118
|
href: string;
|
|
129
119
|
children: ReactNode;
|
|
130
|
-
tone?:
|
|
120
|
+
tone?: Tone;
|
|
131
121
|
/** Defaults on for an off-site link. Turn it off for one that starts a flow the reader should stay in. */
|
|
132
122
|
newTab?: boolean;
|
|
133
123
|
/**
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Link } from "next-view-transitions";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { cn } from "../cn.js";
|
|
5
|
+
import { toneClass } from "../tone.js";
|
|
5
6
|
import { TextAs } from "./as.js";
|
|
6
7
|
/** The body layer: two axes and no more. A paragraph picks a rung and an ink; a
|
|
7
8
|
* caption is always secondary ink and picks a size. `lead` was a third rung a
|
|
@@ -190,17 +191,20 @@ export function TypographyInlineCode({ className, children, ...props }) {
|
|
|
190
191
|
return (_jsx("code", { className: cn("rounded-[3px] bg-foreground/[0.03] px-[0.3em] py-[0.1em] font-mono text-[0.9em] text-secondary-ink", className), ...props, children: children }));
|
|
191
192
|
}
|
|
192
193
|
/**
|
|
193
|
-
* A statement about the surface, not the link: `
|
|
194
|
+
* A statement about the surface, not the link: `muted` inside a paragraph,
|
|
194
195
|
* `primary` when the link is the point of the line, `secondary` for a note
|
|
195
|
-
* beneath a hero where `primary` would compete with the CTA beside it.
|
|
196
|
+
* beneath a hero where `primary` would compete with the CTA beside it. The other
|
|
197
|
+
* four come free, and a link inside a warning should be able to say so.
|
|
198
|
+
*
|
|
199
|
+
* This was `foreground | primary | secondary`, a private list whose first member
|
|
200
|
+
* was "no meaning at all" spelled a third way — `foreground` here, `muted` in
|
|
201
|
+
* Callout, `default` on a button. Now it is ../tone.ts, the same seven the other
|
|
202
|
+
* two take, and the weight is uniform: `secondary` alone used to skip
|
|
203
|
+
* `font-medium`, which read as a lighter link rather than a differently-coloured
|
|
204
|
+
* one.
|
|
196
205
|
*/
|
|
197
|
-
const LINK_TONES = {
|
|
198
|
-
foreground: "font-medium text-foreground",
|
|
199
|
-
primary: "font-medium text-primary",
|
|
200
|
-
secondary: "text-secondary-ink",
|
|
201
|
-
};
|
|
202
206
|
const LINK_DECORATION = "underline decoration-dotted decoration-1 decoration-muted-foreground decoration-skip-ink-none underline-offset-2 hover:decoration-solid hover:decoration-current/70";
|
|
203
|
-
const linkClass = (tone = "
|
|
207
|
+
const linkClass = (tone = "muted", className) => cn(toneClass(tone), "font-medium text-(color:--tone-hue)", LINK_DECORATION, className);
|
|
204
208
|
/**
|
|
205
209
|
* The inline link.
|
|
206
210
|
*
|
|
@@ -217,7 +221,7 @@ const linkClass = (tone = "foreground", className) => cn(LINK_TONES[tone], LINK_
|
|
|
217
221
|
* not be imported by name. One call site ended up on the unbound version that
|
|
218
222
|
* way and lost its decoration.
|
|
219
223
|
*/
|
|
220
|
-
export function TypographyLink({ href, children, tone = "
|
|
224
|
+
export function TypographyLink({ href, children, tone = "muted", newTab, addArrow, className, ...props }) {
|
|
221
225
|
const style = linkClass(tone, className);
|
|
222
226
|
const external = /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
223
227
|
const body = (_jsxs(_Fragment, { children: [children, addArrow && (_jsx("svg", { "aria-hidden": "true", className: "ml-1 inline size-3.5 align-middle", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: external ? "M7 17 17 7M7 7h10v10" : "M5 12h14M12 5l7 7-7 7" }) }))] }));
|