@supertype.ai/foundations 0.1.27 → 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 -3
- 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/seo.d.ts +4 -2
- package/dist/seo.js +14 -5
- 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/seo.d.ts
CHANGED
|
@@ -76,9 +76,10 @@ export declare function createSeo(config: SeoConfig): {
|
|
|
76
76
|
"@type": string;
|
|
77
77
|
url: string;
|
|
78
78
|
} | undefined;
|
|
79
|
-
"@type": string;
|
|
80
79
|
name: string;
|
|
81
80
|
url: string;
|
|
81
|
+
"@id"?: string | undefined;
|
|
82
|
+
"@type": string;
|
|
82
83
|
};
|
|
83
84
|
mainEntityOfPage: {
|
|
84
85
|
"@type": string;
|
|
@@ -154,9 +155,10 @@ export declare function createSeo(config: SeoConfig): {
|
|
|
154
155
|
"@type": string;
|
|
155
156
|
url: string;
|
|
156
157
|
} | undefined;
|
|
157
|
-
"@type": string;
|
|
158
158
|
name: string;
|
|
159
159
|
url: string;
|
|
160
|
+
"@id"?: string | undefined;
|
|
161
|
+
"@type": string;
|
|
160
162
|
};
|
|
161
163
|
author?: {
|
|
162
164
|
jobTitle?: string | undefined;
|
package/dist/seo.js
CHANGED
|
@@ -7,11 +7,6 @@ export function createSeo(config) {
|
|
|
7
7
|
const { baseUrl, siteName, defaultOgImage, logoUrl, articleBasePath = "notes", publisherUrl, trailingSlash = false, } = config;
|
|
8
8
|
/** Resolves a possibly-relative URL against the site origin. */
|
|
9
9
|
const absolute = (url) => url.startsWith("http") ? url : `${baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
10
|
-
/**
|
|
11
|
-
* Stable `@id` anchors for the site's core entities. Pages reference these
|
|
12
|
-
* rather than re-declaring an Organization node, so crawlers merge them into
|
|
13
|
-
* one entity instead of collecting near-duplicates.
|
|
14
|
-
*/
|
|
15
10
|
/**
|
|
16
11
|
* A page route in the shape this site actually serves. A URL already carrying
|
|
17
12
|
* a query, a fragment or a file extension is left alone — only a route gets
|
|
@@ -34,10 +29,24 @@ export function createSeo(config) {
|
|
|
34
29
|
...(author.jobTitle ? { jobTitle: author.jobTitle } : {}),
|
|
35
30
|
};
|
|
36
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Stable `@id` anchors for the site's core entities. Pages reference these
|
|
34
|
+
* rather than re-declaring an Organization node, so crawlers merge them into
|
|
35
|
+
* one entity instead of collecting near-duplicates.
|
|
36
|
+
*/
|
|
37
37
|
const ORG_ID = `${baseUrl}/#organization`;
|
|
38
38
|
const WEBSITE_ID = `${baseUrl}/#website`;
|
|
39
|
+
/**
|
|
40
|
+
* The publisher every Article and WebPage node points at. It carries `@id`
|
|
41
|
+
* only when the publisher is this site: without one, each page declares a
|
|
42
|
+
* fresh Organization and a crawler has no way to merge a corpus of them into
|
|
43
|
+
* the canonical entity the site emits once. A `publisherUrl` naming a
|
|
44
|
+
* different site gets no `@id` — `ORG_ID` is derived from `baseUrl`, so
|
|
45
|
+
* stamping it there would claim someone else's publisher as this one.
|
|
46
|
+
*/
|
|
39
47
|
const publisher = {
|
|
40
48
|
"@type": "Organization",
|
|
49
|
+
...(!publisherUrl || publisherUrl === baseUrl ? { "@id": ORG_ID } : {}),
|
|
41
50
|
name: siteName,
|
|
42
51
|
url: publisherUrl ?? baseUrl,
|
|
43
52
|
...(logoUrl
|
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
|
/**
|