@supertype.ai/foundations 0.1.29 → 0.1.31
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 +26 -22
- package/bin/foundations.mjs +5 -2
- package/dist/blocks/accordion.js +2 -1
- package/dist/blocks/anchor.d.ts +27 -0
- package/dist/blocks/anchor.js +28 -0
- package/dist/blocks/badge.d.ts +7 -2
- package/dist/blocks/badge.js +10 -4
- package/dist/blocks/button.d.ts +25 -4
- package/dist/blocks/button.js +16 -6
- package/dist/blocks/card.d.ts +5 -6
- package/dist/blocks/card.js +8 -12
- package/dist/blocks/index.d.ts +1 -0
- package/dist/blocks/index.js +1 -0
- package/dist/blocks/tabs.d.ts +5 -5
- package/dist/blocks/tabs.js +3 -3
- package/dist/cjs/eslint.js +37 -11
- package/dist/contrast.d.ts +34 -0
- package/dist/contrast.js +208 -10
- package/dist/eslint.d.ts +20 -4
- package/dist/eslint.js +36 -11
- package/dist/essay/layout.d.ts +1 -1
- package/dist/essay/layout.js +1 -1
- package/dist/essay/rail.d.ts +11 -1
- package/dist/essay/scroll.js +1 -1
- package/dist/href.d.ts +42 -0
- package/dist/href.js +63 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -2
- package/dist/tone.d.ts +58 -9
- package/dist/tone.js +60 -12
- package/dist/typography/header.d.ts +2 -2
- package/dist/typography/header.js +4 -4
- package/dist/typography/highlight.d.ts +3 -2
- package/dist/typography/highlight.js +11 -5
- package/dist/typography/paragraph.d.ts +9 -12
- package/dist/typography/paragraph.js +29 -22
- package/llms.txt +97 -33
- package/package.json +2 -2
- package/src/theme.css +73 -20
- package/src/tokens.css +5 -4
- package/src/type.css +1 -1
package/dist/tone.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
1
2
|
/**
|
|
2
3
|
* One semantic colour vocabulary, for every component that carries meaning in a
|
|
3
4
|
* hue: Button, Callout, TypographyLink. Before this there were three lists —
|
|
@@ -30,8 +31,8 @@
|
|
|
30
31
|
*/
|
|
31
32
|
/**
|
|
32
33
|
* `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
|
|
34
|
-
* the tuned answer there,
|
|
34
|
+
* ink at 45% is right for a hue and wrong for the absence of one. `--border` is
|
|
35
|
+
* the tuned answer there, in place of a wash of `--foreground`.
|
|
35
36
|
*
|
|
36
37
|
* Its hue is `--foreground` rather than `--muted-foreground` on purpose. `muted`
|
|
37
38
|
* says the control carries no meaning, not that it carries less contrast — a
|
|
@@ -47,15 +48,15 @@ export declare const TONE: {
|
|
|
47
48
|
/** No meaning: chrome, toolbars, anything that must not compete. */
|
|
48
49
|
readonly muted: "[--tone-fill:var(--muted)] [--tone-ink:var(--foreground)] [--tone-hue:var(--foreground)] [--tone-line:var(--border)]";
|
|
49
50
|
/** 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
|
+
readonly primary: "[--tone-fill:var(--primary)] [--tone-ink:var(--primary-foreground)] [--tone-hue:var(--primary-ink,var(--primary))]";
|
|
51
52
|
/** The warm accent. `--secondary-ink` is its readable cut; the fill is not. */
|
|
52
53
|
readonly secondary: "[--tone-fill:var(--secondary)] [--tone-ink:var(--secondary-foreground)] [--tone-hue:var(--secondary-ink)]";
|
|
53
54
|
/** 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(--
|
|
55
|
+
readonly brand: "[--tone-fill:var(--brand,var(--primary))] [--tone-ink:var(--brand-foreground,var(--primary-foreground))] [--tone-hue:var(--brand-ink,var(--primary-ink,var(--primary)))]";
|
|
55
56
|
/** It worked. */
|
|
56
|
-
readonly success: "[--tone-fill:var(--success)] [--tone-ink:var(--
|
|
57
|
+
readonly success: "[--tone-fill:var(--success)] [--tone-ink:var(--success-foreground)] [--tone-hue:var(--success-ink)]";
|
|
57
58
|
/** A footgun: the reader can still proceed, but not blindly. */
|
|
58
|
-
readonly warn: "[--tone-fill:var(--warn)] [--tone-ink:var(--
|
|
59
|
+
readonly warn: "[--tone-fill:var(--warn)] [--tone-ink:var(--warn-foreground)] [--tone-hue:var(--warn-ink)]";
|
|
59
60
|
/** It deletes something, or it already failed. */
|
|
60
61
|
readonly destructive: "[--tone-fill:var(--destructive)] [--tone-ink:var(--destructive-foreground)] [--tone-hue:var(--destructive)]";
|
|
61
62
|
};
|
|
@@ -70,9 +71,13 @@ export type Tone = keyof typeof TONE;
|
|
|
70
71
|
* while `color-mix` is what the modifier compiles to anyway — the same CSS, one
|
|
71
72
|
* layer less of trust.
|
|
72
73
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* A hover moves the fill 18% toward `--hover-toward`, the extreme theme.css
|
|
75
|
+
* points away from the page: black on latte, white on espresso. The token
|
|
76
|
+
* carries the direction and the percentage carries the state. Mixing toward
|
|
77
|
+
* `--foreground` instead made the step as long as the gap between the fill and
|
|
78
|
+
* the ink, so a `primary` button moved 4.9 ΔL* on latte against 6.2 on espresso
|
|
79
|
+
* and read as no hover at all. Every tone now clears 6 ΔL* in both themes,
|
|
80
|
+
* measured in test/composition.test.ts. A `dark:` override here is what the
|
|
76
81
|
* package's own ESLint rule exists to prevent.
|
|
77
82
|
*/
|
|
78
83
|
export declare const TONE_SURFACE: string;
|
|
@@ -87,6 +92,50 @@ export declare const TONE_SURFACE: string;
|
|
|
87
92
|
* whose order matters and whose values always travel together is one argument.
|
|
88
93
|
*/
|
|
89
94
|
export declare const toneClass: (tone: Tone) => string;
|
|
95
|
+
/**
|
|
96
|
+
* The ink a nested element inherits, declared by whatever painted the surface
|
|
97
|
+
* under it. Two properties, one rule: paint a background, hand down its ink.
|
|
98
|
+
*
|
|
99
|
+
* `toneClass` alone is a palette, not a surface — a `Callout` spends the same
|
|
100
|
+
* seven values as a filled `Button` and tints at 5%, so the words inside it
|
|
101
|
+
* still sit on the page and still want the page's ink. Only a component that
|
|
102
|
+
* actually fills promotes `--tone-ink` to the inherited ink, and the type
|
|
103
|
+
* primitives read it with the page as their fallback. A tint that promotes
|
|
104
|
+
* nothing is therefore correct by default, which is the failure this replaces:
|
|
105
|
+
* `TypographyLabel` pinned `text-foreground`, won over the `text-primary-foreground`
|
|
106
|
+
* on the anchor around it, and printed 2.34:1 on a filled button.
|
|
107
|
+
*
|
|
108
|
+
* `--ink-muted` collapses to the ink itself, because a hue fill has no second
|
|
109
|
+
* rung: mixing the ink 20% toward `--primary` measures 4.22:1 on the
|
|
110
|
+
* espresso theme and 3.49:1 at 30%. Nothing on a filled control may be quieter
|
|
111
|
+
* than its label. Wanting two rungs is wanting a tinted surface.
|
|
112
|
+
*/
|
|
113
|
+
export declare const INK_ON_FILL = "[--ink:var(--tone-ink)] [--ink-muted:var(--tone-ink)]";
|
|
114
|
+
/**
|
|
115
|
+
* The same contract for a surface the tones do not name. These are tints of the
|
|
116
|
+
* page rather than hues, so both rungs survive and the pair is stated rather
|
|
117
|
+
* than collapsed.
|
|
118
|
+
*
|
|
119
|
+
* Stated one constant at a time, and not built from an argument, for the reason
|
|
120
|
+
* `TONE` above is a table of literals: Tailwind reads this file as text and can
|
|
121
|
+
* only generate a class it can see. A class assembled at runtime is a class that
|
|
122
|
+
* was never generated — no error, no style, and the nested type quietly keeps
|
|
123
|
+
* the page's ink on a surface that is not the page. The set is closed because
|
|
124
|
+
* the set of surfaces the theme names is closed; a surface with no token is not
|
|
125
|
+
* a surface. For one the package does not name, see `inkOnSurfaceStyle`.
|
|
126
|
+
*/
|
|
127
|
+
export declare const INK_ON_CARD = "[--ink:var(--card-foreground)] [--ink-muted:var(--muted-foreground)]";
|
|
128
|
+
export declare const INK_ON_POPOVER = "[--ink:var(--popover-foreground)] [--ink-muted:var(--muted-foreground)]";
|
|
129
|
+
export declare const INK_ON_SIDEBAR = "[--ink:var(--sidebar-foreground)] [--ink-muted:var(--muted-foreground)]";
|
|
130
|
+
/**
|
|
131
|
+
* The escape hatch, for an app painting a surface of its own.
|
|
132
|
+
*
|
|
133
|
+
* Properties rather than a class, because a class this function returned would
|
|
134
|
+
* have to be generated by a scanner that never saw it. Inline custom properties
|
|
135
|
+
* need no scanner at all, so this works for any token and cannot silently
|
|
136
|
+
* produce nothing. It replaces a version that returned a class string and did.
|
|
137
|
+
*/
|
|
138
|
+
export declare const inkOnSurfaceStyle: (ink: string, muted?: string) => CSSProperties;
|
|
90
139
|
/**
|
|
91
140
|
* What an unstated tone means, given how much ink the component is spending.
|
|
92
141
|
* Shared, because `Button` and `Badge` both need it and two copies of a default
|
package/dist/tone.js
CHANGED
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
*/
|
|
31
31
|
/**
|
|
32
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
|
|
34
|
-
* the tuned answer there,
|
|
33
|
+
* ink at 45% is right for a hue and wrong for the absence of one. `--border` is
|
|
34
|
+
* the tuned answer there, in place of a wash of `--foreground`.
|
|
35
35
|
*
|
|
36
36
|
* Its hue is `--foreground` rather than `--muted-foreground` on purpose. `muted`
|
|
37
37
|
* says the control carries no meaning, not that it carries less contrast — a
|
|
@@ -47,15 +47,15 @@ export const TONE = {
|
|
|
47
47
|
/** No meaning: chrome, toolbars, anything that must not compete. */
|
|
48
48
|
muted: "[--tone-fill:var(--muted)] [--tone-ink:var(--foreground)] [--tone-hue:var(--foreground)] [--tone-line:var(--border)]",
|
|
49
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)]",
|
|
50
|
+
primary: "[--tone-fill:var(--primary)] [--tone-ink:var(--primary-foreground)] [--tone-hue:var(--primary-ink,var(--primary))]",
|
|
51
51
|
/** The warm accent. `--secondary-ink` is its readable cut; the fill is not. */
|
|
52
52
|
secondary: "[--tone-fill:var(--secondary)] [--tone-ink:var(--secondary-foreground)] [--tone-hue:var(--secondary-ink)]",
|
|
53
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(--
|
|
54
|
+
brand: "[--tone-fill:var(--brand,var(--primary))] [--tone-ink:var(--brand-foreground,var(--primary-foreground))] [--tone-hue:var(--brand-ink,var(--primary-ink,var(--primary)))]",
|
|
55
55
|
/** It worked. */
|
|
56
|
-
success: "[--tone-fill:var(--success)] [--tone-ink:var(--
|
|
56
|
+
success: "[--tone-fill:var(--success)] [--tone-ink:var(--success-foreground)] [--tone-hue:var(--success-ink)]",
|
|
57
57
|
/** A footgun: the reader can still proceed, but not blindly. */
|
|
58
|
-
warn: "[--tone-fill:var(--warn)] [--tone-ink:var(--
|
|
58
|
+
warn: "[--tone-fill:var(--warn)] [--tone-ink:var(--warn-foreground)] [--tone-hue:var(--warn-ink)]",
|
|
59
59
|
/** It deletes something, or it already failed. */
|
|
60
60
|
destructive: "[--tone-fill:var(--destructive)] [--tone-ink:var(--destructive-foreground)] [--tone-hue:var(--destructive)]",
|
|
61
61
|
};
|
|
@@ -69,9 +69,13 @@ export const TONE = {
|
|
|
69
69
|
* while `color-mix` is what the modifier compiles to anyway — the same CSS, one
|
|
70
70
|
* layer less of trust.
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* A hover moves the fill 18% toward `--hover-toward`, the extreme theme.css
|
|
73
|
+
* points away from the page: black on latte, white on espresso. The token
|
|
74
|
+
* carries the direction and the percentage carries the state. Mixing toward
|
|
75
|
+
* `--foreground` instead made the step as long as the gap between the fill and
|
|
76
|
+
* the ink, so a `primary` button moved 4.9 ΔL* on latte against 6.2 on espresso
|
|
77
|
+
* and read as no hover at all. Every tone now clears 6 ΔL* in both themes,
|
|
78
|
+
* measured in test/composition.test.ts. A `dark:` override here is what the
|
|
75
79
|
* package's own ESLint rule exists to prevent.
|
|
76
80
|
*/
|
|
77
81
|
export const TONE_SURFACE = [
|
|
@@ -81,10 +85,10 @@ export const TONE_SURFACE = [
|
|
|
81
85
|
"[--tone-veil:color-mix(in_oklab,var(--tone-hue)_5%,transparent)]",
|
|
82
86
|
/** A control's tint at rest. */
|
|
83
87
|
"[--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)
|
|
88
|
+
/** The same control under the pointer: twice the tint, so the step reads. */
|
|
89
|
+
"[--tone-wash-hover:color-mix(in_oklab,var(--tone-hue)_20%,transparent)]",
|
|
86
90
|
/** A filled control under the pointer. */
|
|
87
|
-
"[--tone-fill-hover:color-mix(in_oklab,var(--tone-fill)
|
|
91
|
+
"[--tone-fill-hover:color-mix(in_oklab,var(--tone-fill),var(--hover-toward)_18%)]",
|
|
88
92
|
].join(" ");
|
|
89
93
|
/**
|
|
90
94
|
* A tone, as one class list. This is the only thing a component should need.
|
|
@@ -97,6 +101,50 @@ export const TONE_SURFACE = [
|
|
|
97
101
|
* whose order matters and whose values always travel together is one argument.
|
|
98
102
|
*/
|
|
99
103
|
export const toneClass = (tone) => `${TONE_SURFACE} ${TONE[tone]}`;
|
|
104
|
+
/**
|
|
105
|
+
* The ink a nested element inherits, declared by whatever painted the surface
|
|
106
|
+
* under it. Two properties, one rule: paint a background, hand down its ink.
|
|
107
|
+
*
|
|
108
|
+
* `toneClass` alone is a palette, not a surface — a `Callout` spends the same
|
|
109
|
+
* seven values as a filled `Button` and tints at 5%, so the words inside it
|
|
110
|
+
* still sit on the page and still want the page's ink. Only a component that
|
|
111
|
+
* actually fills promotes `--tone-ink` to the inherited ink, and the type
|
|
112
|
+
* primitives read it with the page as their fallback. A tint that promotes
|
|
113
|
+
* nothing is therefore correct by default, which is the failure this replaces:
|
|
114
|
+
* `TypographyLabel` pinned `text-foreground`, won over the `text-primary-foreground`
|
|
115
|
+
* on the anchor around it, and printed 2.34:1 on a filled button.
|
|
116
|
+
*
|
|
117
|
+
* `--ink-muted` collapses to the ink itself, because a hue fill has no second
|
|
118
|
+
* rung: mixing the ink 20% toward `--primary` measures 4.22:1 on the
|
|
119
|
+
* espresso theme and 3.49:1 at 30%. Nothing on a filled control may be quieter
|
|
120
|
+
* than its label. Wanting two rungs is wanting a tinted surface.
|
|
121
|
+
*/
|
|
122
|
+
export const INK_ON_FILL = "[--ink:var(--tone-ink)] [--ink-muted:var(--tone-ink)]";
|
|
123
|
+
/**
|
|
124
|
+
* The same contract for a surface the tones do not name. These are tints of the
|
|
125
|
+
* page rather than hues, so both rungs survive and the pair is stated rather
|
|
126
|
+
* than collapsed.
|
|
127
|
+
*
|
|
128
|
+
* Stated one constant at a time, and not built from an argument, for the reason
|
|
129
|
+
* `TONE` above is a table of literals: Tailwind reads this file as text and can
|
|
130
|
+
* only generate a class it can see. A class assembled at runtime is a class that
|
|
131
|
+
* was never generated — no error, no style, and the nested type quietly keeps
|
|
132
|
+
* the page's ink on a surface that is not the page. The set is closed because
|
|
133
|
+
* the set of surfaces the theme names is closed; a surface with no token is not
|
|
134
|
+
* a surface. For one the package does not name, see `inkOnSurfaceStyle`.
|
|
135
|
+
*/
|
|
136
|
+
export const INK_ON_CARD = "[--ink:var(--card-foreground)] [--ink-muted:var(--muted-foreground)]";
|
|
137
|
+
export const INK_ON_POPOVER = "[--ink:var(--popover-foreground)] [--ink-muted:var(--muted-foreground)]";
|
|
138
|
+
export const INK_ON_SIDEBAR = "[--ink:var(--sidebar-foreground)] [--ink-muted:var(--muted-foreground)]";
|
|
139
|
+
/**
|
|
140
|
+
* The escape hatch, for an app painting a surface of its own.
|
|
141
|
+
*
|
|
142
|
+
* Properties rather than a class, because a class this function returned would
|
|
143
|
+
* have to be generated by a scanner that never saw it. Inline custom properties
|
|
144
|
+
* need no scanner at all, so this works for any token and cannot silently
|
|
145
|
+
* produce nothing. It replaces a version that returned a class string and did.
|
|
146
|
+
*/
|
|
147
|
+
export const inkOnSurfaceStyle = (ink, muted = "--muted-foreground") => ({ "--ink": `var(${ink})`, "--ink-muted": `var(${muted})` });
|
|
100
148
|
/**
|
|
101
149
|
* What an unstated tone means, given how much ink the component is spending.
|
|
102
150
|
* Shared, because `Button` and `Badge` both need it and two copies of a default
|
|
@@ -59,8 +59,8 @@ declare const eyebrowVariants: (props?: ({
|
|
|
59
59
|
/**
|
|
60
60
|
* The eyebrow's ramp as a class, for a caller that cannot render our element —
|
|
61
61
|
* a dialog title primitive, a motion element. Same escape hatch as
|
|
62
|
-
* `headingClass
|
|
63
|
-
* hand-
|
|
62
|
+
* `headingClass`. It exists so a consumer needing the class can take ours rather
|
|
63
|
+
* than hand-rolling a copy that drifts from the component.
|
|
64
64
|
*/
|
|
65
65
|
export declare const eyebrowClass: (tone?: VariantProps<typeof eyebrowVariants>["tone"]) => string;
|
|
66
66
|
/**
|
|
@@ -32,7 +32,7 @@ import { TextAs } from "./as.js";
|
|
|
32
32
|
* the face had no door, arriving with a `scroll-m-20` for an anchor it does not have.
|
|
33
33
|
*/
|
|
34
34
|
export const headingFace = "font-heading font-[number:var(--heading-weight)]";
|
|
35
|
-
const HEADING_BASE = `scroll-m-20 ${headingFace} text-foreground`;
|
|
35
|
+
const HEADING_BASE = `scroll-m-20 ${headingFace} text-[color:var(--ink,var(--foreground))]`;
|
|
36
36
|
const h1Variants = cva(`${HEADING_BASE} tracking-tight`, {
|
|
37
37
|
variants: {
|
|
38
38
|
variant: {
|
|
@@ -98,7 +98,7 @@ const eyebrowVariants = cva("block uppercase tracking-wider", {
|
|
|
98
98
|
* Primary ink, stated not inherited — an eyebrow names the section under it,
|
|
99
99
|
* and one that turns red from its surroundings is not a heading.
|
|
100
100
|
*/
|
|
101
|
-
heading: "text-xs font-semibold text-foreground",
|
|
101
|
+
heading: "text-xs font-semibold text-[color:var(--ink,var(--foreground))]",
|
|
102
102
|
/** Stat cards invert it: the figure is the headline, so the label yields. */
|
|
103
103
|
label: "text-2xs font-medium text-accent-foreground",
|
|
104
104
|
},
|
|
@@ -108,8 +108,8 @@ const eyebrowVariants = cva("block uppercase tracking-wider", {
|
|
|
108
108
|
/**
|
|
109
109
|
* The eyebrow's ramp as a class, for a caller that cannot render our element —
|
|
110
110
|
* a dialog title primitive, a motion element. Same escape hatch as
|
|
111
|
-
* `headingClass
|
|
112
|
-
* hand-
|
|
111
|
+
* `headingClass`. It exists so a consumer needing the class can take ours rather
|
|
112
|
+
* than hand-rolling a copy that drifts from the component.
|
|
113
113
|
*/
|
|
114
114
|
export const eyebrowClass = (tone) => eyebrowVariants({ tone });
|
|
115
115
|
/**
|
|
@@ -5,8 +5,7 @@ import type { ComponentProps } from "react";
|
|
|
5
5
|
* hue for nothing. That is why the earth tones enter as `-foreground`: those are
|
|
6
6
|
* the ink-grade pair, mixed to hold at text weight in both themes.
|
|
7
7
|
*
|
|
8
|
-
* Emphasis, never status. Warn and info and destructive are absent on purpose
|
|
9
|
-
* a swipe of red under a phrase says less than the words do.
|
|
8
|
+
* Emphasis, never status. Warn and info and destructive are absent on purpose.
|
|
10
9
|
*
|
|
11
10
|
* The four earth tones are one palette, not a menu: each is a different hue,
|
|
12
11
|
* because two a reader cannot tell apart are one tone with two names.
|
|
@@ -24,6 +23,8 @@ export type HighlightTone = keyof typeof MARKER_TONES;
|
|
|
24
23
|
* Marker highlight for inline text. Painted as the run's own background, never a
|
|
25
24
|
* mask — a mask would shave the glyph tops. `luminosity` lets letters borrow the
|
|
26
25
|
* marker's hue while keeping their own lightness, so contrast holds in both themes.
|
|
26
|
+
* That lightness comes from `--marker-ink`, a deepened `--foreground`, so the run
|
|
27
|
+
* still reads as the emphasised one rather than sinking into its own wash.
|
|
27
28
|
*/
|
|
28
29
|
export declare function TypographyHighlight({ tone, seed, className, style, children, ...props }: ComponentProps<"span"> & {
|
|
29
30
|
tone?: HighlightTone;
|
|
@@ -6,8 +6,7 @@ import { cn } from "../cn.js";
|
|
|
6
6
|
* hue for nothing. That is why the earth tones enter as `-foreground`: those are
|
|
7
7
|
* the ink-grade pair, mixed to hold at text weight in both themes.
|
|
8
8
|
*
|
|
9
|
-
* Emphasis, never status. Warn and info and destructive are absent on purpose
|
|
10
|
-
* a swipe of red under a phrase says less than the words do.
|
|
9
|
+
* Emphasis, never status. Warn and info and destructive are absent on purpose.
|
|
11
10
|
*
|
|
12
11
|
* The four earth tones are one palette, not a menu: each is a different hue,
|
|
13
12
|
* because two a reader cannot tell apart are one tone with two names.
|
|
@@ -41,7 +40,8 @@ const DABS = [
|
|
|
41
40
|
// Integer-only: `Math.sin` is implementation-defined, so Node and the browser
|
|
42
41
|
// disagreed in the last bits and the swipe hydrated as a mismatch.
|
|
43
42
|
const hash = (seed, i) => {
|
|
44
|
-
let h = Math.imul(seed ^ 0x9e3779b9, 0x85ebca6b) ^
|
|
43
|
+
let h = Math.imul(seed ^ 0x9e3779b9, 0x85ebca6b) ^
|
|
44
|
+
Math.imul(i + 0x165667b1, 0xc2b2ae35);
|
|
45
45
|
h ^= h >>> 15;
|
|
46
46
|
h = Math.imul(h, 0x2545f491);
|
|
47
47
|
h ^= h >>> 13;
|
|
@@ -88,11 +88,17 @@ const markerFill = (seed) => {
|
|
|
88
88
|
* Marker highlight for inline text. Painted as the run's own background, never a
|
|
89
89
|
* mask — a mask would shave the glyph tops. `luminosity` lets letters borrow the
|
|
90
90
|
* marker's hue while keeping their own lightness, so contrast holds in both themes.
|
|
91
|
+
* That lightness comes from `--marker-ink`, a deepened `--foreground`, so the run
|
|
92
|
+
* still reads as the emphasised one rather than sinking into its own wash.
|
|
91
93
|
*/
|
|
92
94
|
export function TypographyHighlight({ tone = "primary", seed = 3, className, style, children, ...props }) {
|
|
93
|
-
return (_jsx("span", { className: cn("isolate inline px-[0.3em] py-[0.06em]", "[-webkit-box-decoration-break:clone] [box-decoration-break:clone]", "[--marker-alpha:44%] dark:[--marker-alpha:58%]",
|
|
95
|
+
return (_jsx("span", { className: cn("isolate inline px-[0.3em] py-[0.06em]", "[-webkit-box-decoration-break:clone] [box-decoration-break:clone]", "[--marker-alpha:44%] dark:[--marker-alpha:58%]",
|
|
96
|
+
// The blend keeps a glyph's lightness and nothing else, so a run over the
|
|
97
|
+
// wash reads flatter than the same words beside it. Pushing the lightness
|
|
98
|
+
// past --foreground buys that punch back without touching the hue borrow.
|
|
99
|
+
"[--marker-ink:color-mix(in_oklab,var(--foreground)_85%,black)]", "dark:[--marker-ink:color-mix(in_oklab,var(--foreground)_88%,white)]", className), style: {
|
|
94
100
|
"--marker": MARKER_TONES[tone],
|
|
95
101
|
backgroundImage: markerFill(seed),
|
|
96
102
|
...style,
|
|
97
|
-
}, ...props, children: _jsx("span", { className: "[mix-blend-mode:luminosity]", children: children }) }));
|
|
103
|
+
}, ...props, children: _jsx("span", { className: "text-(color:--marker-ink) [mix-blend-mode:luminosity]", children: children }) }));
|
|
98
104
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LinkBehavior } from "../href.js";
|
|
1
2
|
import { type VariantProps } from "class-variance-authority";
|
|
2
3
|
import type { ComponentProps, ReactNode } from "react";
|
|
3
4
|
import { type Tone } from "../tone.js";
|
|
@@ -101,7 +102,7 @@ export declare function TypographyLabel({ className, size, as, children, ...prop
|
|
|
101
102
|
* than a constant. Keep `tabular` anywhere a value updates in place.
|
|
102
103
|
*/
|
|
103
104
|
declare const statVariants: (props?: ({
|
|
104
|
-
size?: "inherit" | "
|
|
105
|
+
size?: "inherit" | "page" | "display" | "card" | "panel" | null | undefined;
|
|
105
106
|
figures?: "tabular" | "proportional" | null | undefined;
|
|
106
107
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
107
108
|
export type StatVariants = VariantProps<typeof statVariants>;
|
|
@@ -114,28 +115,24 @@ export declare function TypographyStat({ className, size, figures, children, ...
|
|
|
114
115
|
* optical correction — the mono face carries a taller x-height than the sans.
|
|
115
116
|
*/
|
|
116
117
|
export declare function TypographyInlineCode({ className, children, ...props }: ComponentProps<"code">): import("react").JSX.Element;
|
|
117
|
-
type TypographyLinkProps = Omit<ComponentProps<"a">, "href"> & {
|
|
118
|
+
type TypographyLinkProps = Omit<ComponentProps<"a">, "href"> & LinkBehavior & {
|
|
118
119
|
href: string;
|
|
119
120
|
children: ReactNode;
|
|
120
121
|
tone?: Tone;
|
|
121
|
-
/** Defaults on for an off-site link. Turn it off for one that starts a flow the reader should stay in. */
|
|
122
|
-
newTab?: boolean;
|
|
123
122
|
/**
|
|
124
123
|
* A trailing arrow, for a link that ends a sentence and leads somewhere. The
|
|
125
124
|
* glyph follows the href: `↗` when the link leaves the site, `→` when it does
|
|
126
|
-
* not.
|
|
125
|
+
* not. The href picks it, so a call site never has to.
|
|
127
126
|
*/
|
|
128
127
|
addArrow?: boolean;
|
|
129
128
|
};
|
|
130
129
|
/**
|
|
131
130
|
* The inline link.
|
|
132
131
|
*
|
|
133
|
-
* Internal and external are decided from the href, never at the call site
|
|
134
|
-
* href
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* the reader should stay in. Call-site props apply last, so a passed
|
|
138
|
-
* `target`/`rel` still wins.
|
|
132
|
+
* Internal and external are decided from the href, never at the call site —
|
|
133
|
+
* ../href.ts holds that decision, and Button, Badge and Card make the same one.
|
|
134
|
+
* `newTab` and `external` are the overrides. Call-site props apply last, so a
|
|
135
|
+
* passed `target`/`rel` still wins.
|
|
139
136
|
*
|
|
140
137
|
* The router is `next-view-transitions`, imported rather than injected. Every
|
|
141
138
|
* project on this package is a Next app and wants the same link, and a factory
|
|
@@ -143,5 +140,5 @@ type TypographyLinkProps = Omit<ComponentProps<"a">, "href"> & {
|
|
|
143
140
|
* not be imported by name. One call site ended up on the unbound version that
|
|
144
141
|
* way and lost its decoration.
|
|
145
142
|
*/
|
|
146
|
-
export declare function TypographyLink({ href, children, tone, newTab, addArrow, className, ...props }: TypographyLinkProps): import("react").JSX.Element;
|
|
143
|
+
export declare function TypographyLink({ href, children, tone, external: leavesApp, newTab, addArrow, className, ...props }: TypographyLinkProps): import("react").JSX.Element;
|
|
147
144
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveLink } from "../href.js";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { cn } from "../cn.js";
|
|
5
5
|
import { toneClass } from "../tone.js";
|
|
@@ -19,8 +19,8 @@ const pVariants = cva("", {
|
|
|
19
19
|
prose: "text-pretty text-lg leading-relaxed",
|
|
20
20
|
},
|
|
21
21
|
tone: {
|
|
22
|
-
default: "text-foreground",
|
|
23
|
-
muted: "text-muted-foreground",
|
|
22
|
+
default: "text-[color:var(--ink,var(--foreground))]",
|
|
23
|
+
muted: "text-[color:var(--ink-muted,var(--muted-foreground))]",
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
26
|
defaultVariants: { variant: "ui", tone: "default" },
|
|
@@ -34,7 +34,10 @@ export function TypographyMuted(props) {
|
|
|
34
34
|
return _jsx(TypographyP, { ...props, ...MUTED });
|
|
35
35
|
}
|
|
36
36
|
/** Reading-size body copy. `TypographyMuted` is the same ink one rung down. */
|
|
37
|
-
const PROSE = {
|
|
37
|
+
const PROSE = {
|
|
38
|
+
variant: "prose",
|
|
39
|
+
tone: "muted",
|
|
40
|
+
};
|
|
38
41
|
export function TypographyProse(props) {
|
|
39
42
|
return _jsx(TypographyP, { ...props, ...PROSE });
|
|
40
43
|
}
|
|
@@ -86,7 +89,7 @@ export function TypographyProseList(props) {
|
|
|
86
89
|
* reason to sit there is to be quieter than the thing you qualify — and every
|
|
87
90
|
* container that sets a size for you sets a weight too.
|
|
88
91
|
*/
|
|
89
|
-
const captionVariants = cva("text-muted-foreground", {
|
|
92
|
+
const captionVariants = cva("text-[color:var(--ink-muted,var(--muted-foreground))]", {
|
|
90
93
|
variants: {
|
|
91
94
|
size: {
|
|
92
95
|
sm: "text-sm leading-normal",
|
|
@@ -127,7 +130,7 @@ export function TypographySmall(props) {
|
|
|
127
130
|
* `as` is here for the same reason it is on `TypographyEyebrow`: a config panel
|
|
128
131
|
* names its sections at this size, and those names are the page's outline.
|
|
129
132
|
*/
|
|
130
|
-
const labelVariants = cva("font-medium text-foreground", {
|
|
133
|
+
const labelVariants = cva("font-medium text-[color:var(--ink,var(--foreground))]", {
|
|
131
134
|
variants: {
|
|
132
135
|
size: {
|
|
133
136
|
sm: "text-sm",
|
|
@@ -188,11 +191,11 @@ export function TypographyStat({ className, size, figures, children, ...props })
|
|
|
188
191
|
* optical correction — the mono face carries a taller x-height than the sans.
|
|
189
192
|
*/
|
|
190
193
|
export function TypographyInlineCode({ className, children, ...props }) {
|
|
191
|
-
return (_jsx("code", { className: cn("rounded-[3px] bg-
|
|
194
|
+
return (_jsx("code", { className: cn("rounded-[3px] bg-current/[0.06] px-[0.3em] py-[0.1em] font-mono text-[0.9em] text-[color:var(--ink,var(--secondary-ink))]", className), ...props, children: children }));
|
|
192
195
|
}
|
|
193
196
|
/**
|
|
194
197
|
* A statement about the surface, not the link: `muted` inside a paragraph,
|
|
195
|
-
* `primary` when the link is the
|
|
198
|
+
* `primary` when the link is the main thing on the line, `secondary` for a note
|
|
196
199
|
* beneath a hero where `primary` would compete with the CTA beside it. The other
|
|
197
200
|
* four come free, and a link inside a warning should be able to say so.
|
|
198
201
|
*
|
|
@@ -203,17 +206,22 @@ export function TypographyInlineCode({ className, children, ...props }) {
|
|
|
203
206
|
* `font-medium`, which read as a lighter link rather than a differently-coloured
|
|
204
207
|
* one.
|
|
205
208
|
*/
|
|
209
|
+
const INHERITED_INK = "text-[color:var(--ink,var(--foreground))]";
|
|
206
210
|
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";
|
|
207
|
-
|
|
211
|
+
/**
|
|
212
|
+
* No tone stated means "read the surface": the ink comes from whatever painted
|
|
213
|
+
* the ground, which on a page is `--foreground` and inside a filled control is
|
|
214
|
+
* that control's label ink. The old default spelled this `muted`, whose hue is
|
|
215
|
+
* `--foreground` — identical on a page, and 2.28:1 on a filled button.
|
|
216
|
+
*/
|
|
217
|
+
const linkClass = (tone, className) => cn(tone ? cn(toneClass(tone), "text-(color:--tone-hue)") : INHERITED_INK, "font-medium", LINK_DECORATION, className);
|
|
208
218
|
/**
|
|
209
219
|
* The inline link.
|
|
210
220
|
*
|
|
211
|
-
* Internal and external are decided from the href, never at the call site
|
|
212
|
-
* href
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* the reader should stay in. Call-site props apply last, so a passed
|
|
216
|
-
* `target`/`rel` still wins.
|
|
221
|
+
* Internal and external are decided from the href, never at the call site —
|
|
222
|
+
* ../href.ts holds that decision, and Button, Badge and Card make the same one.
|
|
223
|
+
* `newTab` and `external` are the overrides. Call-site props apply last, so a
|
|
224
|
+
* passed `target`/`rel` still wins.
|
|
217
225
|
*
|
|
218
226
|
* The router is `next-view-transitions`, imported rather than injected. Every
|
|
219
227
|
* project on this package is a Next app and wants the same link, and a factory
|
|
@@ -221,13 +229,12 @@ const linkClass = (tone = "muted", className) => cn(toneClass(tone), "font-mediu
|
|
|
221
229
|
* not be imported by name. One call site ended up on the unbound version that
|
|
222
230
|
* way and lost its decoration.
|
|
223
231
|
*/
|
|
224
|
-
export function TypographyLink({ href, children, tone = "muted", newTab, addArrow, className, ...props }) {
|
|
232
|
+
export function TypographyLink({ href, children, tone = "muted", external: leavesApp, newTab, addArrow, className, ...props }) {
|
|
225
233
|
const style = linkClass(tone, className);
|
|
226
|
-
const external =
|
|
234
|
+
const { Component, props: link, external } = resolveLink(href, {
|
|
235
|
+
external: leavesApp,
|
|
236
|
+
newTab,
|
|
237
|
+
});
|
|
227
238
|
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" }) }))] }));
|
|
228
|
-
|
|
229
|
-
const away = newTab ?? href.startsWith("http");
|
|
230
|
-
return (_jsx("a", { href: href, className: style, ...(away ? { target: "_blank", rel: "noopener noreferrer" } : {}), ...props, children: body }));
|
|
231
|
-
}
|
|
232
|
-
return (_jsx(Link, { href: href, className: style, ...props, children: body }));
|
|
239
|
+
return (_jsx(Component, { className: style, ...link, ...props, children: body }));
|
|
233
240
|
}
|