@supertype.ai/foundations 0.1.36 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -27
- package/dist/cjs/eslint.js +0 -30
- package/dist/eslint.d.ts +3 -56
- package/dist/eslint.js +6 -29
- package/dist/optical.d.ts +56 -0
- package/dist/optical.js +56 -0
- package/dist/rehype.d.ts +0 -12
- package/dist/rehype.js +2 -2
- package/dist/typography/align.d.ts +57 -0
- package/dist/typography/align.js +57 -0
- package/dist/typography/index.d.ts +1 -0
- package/dist/typography/index.js +1 -0
- package/dist/typography/trim.d.ts +28 -0
- package/dist/typography/trim.js +28 -0
- package/llms.txt +67 -102
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -43,10 +43,13 @@ npx @supertype.ai/foundations init # edits your CSS entry, prints the rest
|
|
|
43
43
|
npx @supertype.ai/foundations doctor # checks this app against everything below
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
`init` edits one file: the CSS entry that imports Tailwind. It adds
|
|
47
|
-
|
|
46
|
+
`init` edits one file: the CSS entry that imports Tailwind. It adds any missing
|
|
47
|
+
imports and reorders the existing ones if needed. Run it with `--dry-run` first
|
|
48
|
+
to preview the patch. It also prints the font bindings and the `llms.txt` snippet
|
|
49
|
+
your coding agent should read.
|
|
48
50
|
|
|
49
|
-
The steps performed by `init` are
|
|
51
|
+
The steps performed by `init` are listed below. See [the CLI](docs/cli.md) for
|
|
52
|
+
the full list of checks and details.
|
|
50
53
|
|
|
51
54
|
### 1. Add the package
|
|
52
55
|
|
|
@@ -74,7 +77,7 @@ untagged git dependency resolves to a different commit on a fresh install.
|
|
|
74
77
|
|
|
75
78
|
```jsonc
|
|
76
79
|
// package.json
|
|
77
|
-
"@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.
|
|
80
|
+
"@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.2.0"
|
|
78
81
|
```
|
|
79
82
|
|
|
80
83
|
</details>
|
|
@@ -89,10 +92,10 @@ untagged git dependency resolves to a different commit on a fresh install.
|
|
|
89
92
|
|
|
90
93
|
That one line carries `tokens.css`, `theme.css`, `type.css` and `prose.css` in
|
|
91
94
|
the order the cascade needs, and registers the package’s own `@source` so
|
|
92
|
-
Tailwind scans the components it ships.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
Tailwind scans the components it ships. You do not need to work out a path or
|
|
96
|
+
keep an order: Tailwind v4 resolves `@source` relative to the file that declares
|
|
97
|
+
it, so the package points at its own `dist/`, correctly, wherever it happens to
|
|
98
|
+
be installed.
|
|
96
99
|
|
|
97
100
|
Add `@import "@supertype.ai/foundations/shiki.css";` after it only if you render
|
|
98
101
|
code fences.
|
|
@@ -239,8 +242,8 @@ Two rules cover most of the API:
|
|
|
239
242
|
`yarn example` (above) builds the package, syncs it in and starts the dev
|
|
240
243
|
server. `yarn example:build` is what CI would run.
|
|
241
244
|
|
|
242
|
-
It installs the package from a git tag rather than
|
|
243
|
-
|
|
245
|
+
It installs the package from a git tag rather than the registry and updates it
|
|
246
|
+
with `yarn sync`.
|
|
244
247
|
|
|
245
248
|
`/recipes` holds whole pages rather than single components: a marketing hero, a
|
|
246
249
|
metrics panel, pricing tiers, a docs page, an article index, and examples of MDX-rendered pages. Each one lives in
|
|
@@ -268,19 +271,19 @@ package.
|
|
|
268
271
|
|
|
269
272
|
## Entry points
|
|
270
273
|
|
|
271
|
-
| import | contains
|
|
272
|
-
| --------------------------------------------------------------------- |
|
|
273
|
-
| `@supertype.ai/foundations` | all typography primitives, `cn`
|
|
274
|
+
| import | contains | docs |
|
|
275
|
+
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
276
|
+
| `@supertype.ai/foundations` | all typography primitives, `cn` | [Typography](docs/typography.md) |
|
|
274
277
|
| `@supertype.ai/foundations/blocks` | `Button`, `Badge`, `Card`, `Callout`, `Steps`, `TabGroup`, `Accordion`, `SEGMENT` | [Blocks](docs/blocks.md) |
|
|
275
|
-
| `@supertype.ai/foundations/mdx` | `proseMdxComponents` — the MDX element map
|
|
276
|
-
| `@supertype.ai/foundations/essay` | the long-form shell, TOC, reading rail, post meta
|
|
277
|
-
| `@supertype.ai/foundations/seo` | `createSeo(...)` — metadata + JSON-LD
|
|
278
|
-
| `@supertype.ai/foundations/og` | `ogCard`, `OG_SIZE` — an element for `next/og`
|
|
279
|
-
| `@supertype.ai/foundations/eslint` | the design rules as ESLint selectors
|
|
280
|
-
| `@supertype.ai/foundations/rehype` | `rehypeProseCode` — **build-time only**
|
|
281
|
-
| `@supertype.ai/foundations/contrast` | token resolution + legibility checks, build-time only
|
|
282
|
-
| `./tokens.css` `./theme.css` `./type.css` `./prose.css` `./shiki.css` | the style layer
|
|
283
|
-
| `foundations` (bin) | `init` and `doctor`
|
|
278
|
+
| `@supertype.ai/foundations/mdx` | `proseMdxComponents` — the MDX element map | [In MDX](docs/blocks.md#in-mdx) |
|
|
279
|
+
| `@supertype.ai/foundations/essay` | the long-form shell, TOC, reading rail, post meta | [Essay](docs/essay.md) |
|
|
280
|
+
| `@supertype.ai/foundations/seo` | `createSeo(...)` — metadata + JSON-LD | [Tooling](docs/tooling.md#seo-and-og-images) |
|
|
281
|
+
| `@supertype.ai/foundations/og` | `ogCard`, `OG_SIZE` — an element for `next/og` | [Tooling](docs/tooling.md#seo-and-og-images) |
|
|
282
|
+
| `@supertype.ai/foundations/eslint` | the design rules as ESLint selectors | [Tooling](docs/tooling.md#lint-rules) |
|
|
283
|
+
| `@supertype.ai/foundations/rehype` | `rehypeProseCode` — **build-time only** | [In MDX](docs/blocks.md#in-mdx) |
|
|
284
|
+
| `@supertype.ai/foundations/contrast` | token resolution + legibility checks, build-time only | [Tooling](docs/tooling.md#contrast-checks) |
|
|
285
|
+
| `./tokens.css` `./theme.css` `./type.css` `./prose.css` `./shiki.css` | the style layer | [Tokens and theming](#tokens-and-theming) |
|
|
286
|
+
| `foundations` (bin) | `init` and `doctor` | [The CLI](docs/cli.md) |
|
|
284
287
|
|
|
285
288
|
---
|
|
286
289
|
|
|
@@ -341,12 +344,12 @@ surfaces set body at different sizes: 13px in the product, 18px on `.editorial`.
|
|
|
341
344
|
|
|
342
345
|
1. **The package owns its final classnames.** Retune with CSS custom properties
|
|
343
346
|
(the `--text-*`, `--heading-weight`, the colour tokens) rather than by
|
|
344
|
-
patching classes. A property the package declares is read by the package
|
|
345
|
-
`test/tokens-live.test.ts` fails on
|
|
346
|
-
|
|
347
|
+
patching classes. A property the package declares is read by the package; if a
|
|
348
|
+
knob turns nothing, `test/tokens-live.test.ts` fails on it; that is worse
|
|
349
|
+
than no knob at all.
|
|
347
350
|
2. **No variant props on the MDX map.** Elements that MDX renders automatically
|
|
348
|
-
take no options
|
|
349
|
-
|
|
351
|
+
take no options. No call site exists to make the choice. Components you invoke
|
|
352
|
+
by hand can have variants.
|
|
350
353
|
3. **Use the platform first, and a library only where it falls short.**
|
|
351
354
|
`Disclosure` is a `<details>`/`<summary>` pair: no JavaScript, correct before
|
|
352
355
|
hydration, and available to an MDX author. `Accordion` and `Tabs` use Base UI,
|
|
@@ -360,6 +363,38 @@ surfaces set body at different sizes: 13px in the product, 18px on `.editorial`.
|
|
|
360
363
|
|
|
361
364
|
---
|
|
362
365
|
|
|
366
|
+
## Upgrading to 0.2
|
|
367
|
+
|
|
368
|
+
Two entry points lost exports. Both had one function doing the work and several
|
|
369
|
+
more standing beside it, and the extras are what a config got wrong.
|
|
370
|
+
|
|
371
|
+
**`/eslint` is one function.** `designRules` assembles every rule, so replace
|
|
372
|
+
`designConfig({ accents, weights })` with your own flat-config entry around it:
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
{
|
|
376
|
+
files: ["app/**/*.tsx", "components/**/*.tsx"],
|
|
377
|
+
rules: {
|
|
378
|
+
"no-restricted-syntax": ["error", ...designRules({ accents: "the brand tints" })],
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`colourRules`, `typographyRules`, `linkRules`, `themeOverrideRules`,
|
|
384
|
+
`surfaceAsInkRules` and `renamedTokenRules` are internal now. Spreading a subset
|
|
385
|
+
was how a config came to be running four of the six sets, unaware of the other
|
|
386
|
+
two, so the whole set is what the package hands out. `typography: false` still
|
|
387
|
+
drops the type rules for a surface that sets its own ramp.
|
|
388
|
+
|
|
389
|
+
**`/rehype` is `rehypeProseCode` and `PROSE_THEMES`.** `PROSE_LANGS` and
|
|
390
|
+
`proseCodeOptions` are what that plugin is built from rather than things to pass.
|
|
391
|
+
|
|
392
|
+
New in the same release: `CAP_TRIM` and `ON_FIRST_LINE` for lining a mark up with
|
|
393
|
+
the words beside it, and `/optical`, which reads a face's metrics and names the
|
|
394
|
+
rungs where a centred mark misses the letters.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
363
398
|
## In production
|
|
364
399
|
|
|
365
400
|
Sites running the package:
|
package/dist/cjs/eslint.js
CHANGED
|
@@ -5,14 +5,7 @@
|
|
|
5
5
|
* They see values, never the shape of a class list; a primitive fixes that.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.colourRules = colourRules;
|
|
9
|
-
exports.themeOverrideRules = themeOverrideRules;
|
|
10
|
-
exports.surfaceAsInkRules = surfaceAsInkRules;
|
|
11
|
-
exports.renamedTokenRules = renamedTokenRules;
|
|
12
|
-
exports.linkRules = linkRules;
|
|
13
|
-
exports.typographyRules = typographyRules;
|
|
14
8
|
exports.designRules = designRules;
|
|
15
|
-
exports.designConfig = designConfig;
|
|
16
9
|
/** A className written as a plain string, or as a chunk of a template literal. */
|
|
17
10
|
const classString = (pattern) => [
|
|
18
11
|
`Literal[value=${pattern}]`,
|
|
@@ -182,26 +175,3 @@ function designRules({ accents, typography = true, ...type } = {}) {
|
|
|
182
175
|
...renamedTokenRules(),
|
|
183
176
|
];
|
|
184
177
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Every rule in one flat-config entry, ready to spread into eslint.config.js:
|
|
187
|
-
*
|
|
188
|
-
* import { designConfig } from "@supertype.ai/foundations/eslint";
|
|
189
|
-
* export default [ ...designConfig({ accents: "the brand tints" }) ];
|
|
190
|
-
*
|
|
191
|
-
* One entry is not a detail. Flat config replaces a rule's options rather than
|
|
192
|
-
* merging them, so two blocks covering overlapping files leave only the last
|
|
193
|
-
* one's rules in effect. Combining them here is what stops a consumer losing
|
|
194
|
-
* half the set by accident. If you need a second scope, call this again with a
|
|
195
|
-
* different `files` and no overlap.
|
|
196
|
-
*/
|
|
197
|
-
function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"], ...options } = {}) {
|
|
198
|
-
return [
|
|
199
|
-
{
|
|
200
|
-
name: "@supertype.ai/foundations/design",
|
|
201
|
-
files,
|
|
202
|
-
rules: {
|
|
203
|
-
"no-restricted-syntax": ["error", ...designRules(options)],
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
];
|
|
207
|
-
}
|
package/dist/eslint.d.ts
CHANGED
|
@@ -7,41 +7,11 @@ export interface RestrictedSyntax {
|
|
|
7
7
|
selector: string;
|
|
8
8
|
message: string;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
interface ColourOptions {
|
|
11
11
|
/** Named in the message: "use a token" without naming one sends people hunting. */
|
|
12
12
|
accents?: string;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
export declare function colourRules({ accents, }?: ColourOptions): RestrictedSyntax[];
|
|
16
|
-
/**
|
|
17
|
-
* A token already knows what it does in the dark; `dark:` on one says the token
|
|
18
|
-
* is wrong. Only the solid form is restricted — `dark:bg-destructive/20` against
|
|
19
|
-
* a `/10` is the same token at the density a darker ground needs.
|
|
20
|
-
*/
|
|
21
|
-
export declare function themeOverrideRules(): RestrictedSyntax[];
|
|
22
|
-
/**
|
|
23
|
-
* `--muted` is a fill at L92%, so `text-muted` lands at ~1.1:1. Invisible, and
|
|
24
|
-
* it shipped at 17 sites. `text-background` stays legal: inverse ink is a real
|
|
25
|
-
* role.
|
|
26
|
-
*/
|
|
27
|
-
export declare function surfaceAsInkRules(): RestrictedSyntax[];
|
|
28
|
-
export declare function renamedTokenRules(): RestrictedSyntax[];
|
|
29
|
-
/**
|
|
30
|
-
* `render={<a href="…" />}` on a component that takes an `href`. It reads as a
|
|
31
|
-
* styling choice and is a routing one: the cloned anchor skips the router, so
|
|
32
|
-
* the page fully reloads and the view transition is lost, and an off-site href
|
|
33
|
-
* never grows a `rel`. Button, Badge and Card each decide internal vs external
|
|
34
|
-
* from the href itself, so the anchor is never needed and cannot be right more
|
|
35
|
-
* often than the one shared rule is.
|
|
36
|
-
*
|
|
37
|
-
* Narrow on both axes, so it never fires on a line that is correct. Only those
|
|
38
|
-
* three components — `RailLink` deliberately takes a router element through
|
|
39
|
-
* `render`, because its module has to stay importable without Next. And only a
|
|
40
|
-
* bare `<a>`: `render={<Link/>}` is redundant beside `href` but it still routes,
|
|
41
|
-
* so it is not a bug.
|
|
42
|
-
*/
|
|
43
|
-
export declare function linkRules(): RestrictedSyntax[];
|
|
44
|
-
export interface TypographyOptions {
|
|
14
|
+
interface TypographyOptions {
|
|
45
15
|
/** Three-weight ramp. Off for editorial, where 700 is a register not a shout. */
|
|
46
16
|
weights?: boolean;
|
|
47
17
|
/** The rungs, named in the message, since they differ per consumer. */
|
|
@@ -63,7 +33,6 @@ export interface TypographyOptions {
|
|
|
63
33
|
* adopting it has a backlog to clear first. */
|
|
64
34
|
leading?: boolean;
|
|
65
35
|
}
|
|
66
|
-
export declare function typographyRules({ weights, ramp, pairing, axis, leading, }?: TypographyOptions): RestrictedSyntax[];
|
|
67
36
|
/**
|
|
68
37
|
* Every design rule, as one list.
|
|
69
38
|
*
|
|
@@ -86,26 +55,4 @@ export interface DesignRuleOptions extends ColourOptions, TypographyOptions {
|
|
|
86
55
|
typography?: boolean;
|
|
87
56
|
}
|
|
88
57
|
export declare function designRules({ accents, typography, ...type }?: DesignRuleOptions): RestrictedSyntax[];
|
|
89
|
-
|
|
90
|
-
export interface FlatConfigEntry {
|
|
91
|
-
name: string;
|
|
92
|
-
files: string[];
|
|
93
|
-
rules: Record<string, unknown>;
|
|
94
|
-
}
|
|
95
|
-
export interface DesignConfigOptions extends DesignRuleOptions {
|
|
96
|
-
/** What the rules apply to. Narrow it to exclude generated or vendored code. */
|
|
97
|
-
files?: string[];
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Every rule in one flat-config entry, ready to spread into eslint.config.js:
|
|
101
|
-
*
|
|
102
|
-
* import { designConfig } from "@supertype.ai/foundations/eslint";
|
|
103
|
-
* export default [ ...designConfig({ accents: "the brand tints" }) ];
|
|
104
|
-
*
|
|
105
|
-
* One entry is not a detail. Flat config replaces a rule's options rather than
|
|
106
|
-
* merging them, so two blocks covering overlapping files leave only the last
|
|
107
|
-
* one's rules in effect. Combining them here is what stops a consumer losing
|
|
108
|
-
* half the set by accident. If you need a second scope, call this again with a
|
|
109
|
-
* different `files` and no overlap.
|
|
110
|
-
*/
|
|
111
|
-
export declare function designConfig({ files, ...options }?: DesignConfigOptions): FlatConfigEntry[];
|
|
58
|
+
export {};
|
package/dist/eslint.js
CHANGED
|
@@ -12,7 +12,7 @@ const rule = (pattern, message) => classString(pattern).map((selector) => ({ sel
|
|
|
12
12
|
const VARIANTS = "(dark:|hover:|focus:|group-hover:|active:|disabled:|sm:|md:|lg:|xl:)*";
|
|
13
13
|
const PALETTE = "(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)";
|
|
14
14
|
/** Applies everywhere, marketing included — the tints exist for those pages. */
|
|
15
|
-
|
|
15
|
+
function colourRules({ accents = "a brand accent", } = {}) {
|
|
16
16
|
return [
|
|
17
17
|
...rule(`/(^| )${VARIANTS}(text|bg|border|ring|from|to|via|fill|stroke|divide|outline|placeholder|shadow|decoration|accent|caret)-${PALETTE}-\\d+/`, `Raw Tailwind palette. Use a token — text-foreground / text-muted-foreground / text-subtle-foreground, bg-background / bg-card / bg-muted, border-border — a status token (success, warn, destructive), or ${accents}.`),
|
|
18
18
|
// Split by prefix: alpha on a fill is a scrim (the effect), on ink or a
|
|
@@ -35,7 +35,7 @@ const COLOUR_PREFIX = "text|bg|border|ring|from|to|via|fill|stroke|divide|placeh
|
|
|
35
35
|
* is wrong. Only the solid form is restricted — `dark:bg-destructive/20` against
|
|
36
36
|
* a `/10` is the same token at the density a darker ground needs.
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
function themeOverrideRules() {
|
|
39
39
|
return rule(`/(^| )dark:(${COLOUR_PREFIX})-(${TOKEN})($| )/`, "A `dark:` override on a token means the token is wrong — fix it in theme.css, where one change covers every call site, rather than here. Alpha variants (dark:bg-destructive/20) stay legal: those tune a wash's density, not the token.");
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
@@ -43,7 +43,7 @@ export function themeOverrideRules() {
|
|
|
43
43
|
* it shipped at 17 sites. `text-background` stays legal: inverse ink is a real
|
|
44
44
|
* role.
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
function surfaceAsInkRules() {
|
|
47
47
|
return [
|
|
48
48
|
...rule("/(^| )(dark:|hover:|focus:|group-hover:)*text-(muted|card|popover|input)($| )/", "That is a surface token, not an ink — as text it has no defined contrast (text-muted measures ~1.1:1 on a light page). Use text-muted-foreground for secondary ink, text-subtle-foreground for tertiary, or text-card-foreground on a card."),
|
|
49
49
|
// The three fills with no legitimate use as a foreground, glyph or word.
|
|
@@ -70,7 +70,7 @@ export function surfaceAsInkRules() {
|
|
|
70
70
|
* fill, and `Button tone="warn" variant="solid"` is what reads it.
|
|
71
71
|
*/
|
|
72
72
|
const RENAMED_INKS = "terracotta|ochre|moss|fern|sage|stone|fig|cocoa";
|
|
73
|
-
|
|
73
|
+
function renamedTokenRules() {
|
|
74
74
|
return rule(`/(^| )(dark:|hover:|focus:|group-hover:)*(text|bg|border|ring|fill|stroke|decoration)-(${RENAMED_INKS})-foreground($| )/`, "That is the deprecated name for the same hue's `-ink`. In this package `-foreground` is the label printed on a fill and `-ink` is the hue used as words, and none of these hues has a printed-on label — they are checked at 4.5:1 against the page, and printing one on its own fill measures about 1.2:1. Use `-ink`.");
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
@@ -87,7 +87,7 @@ export function renamedTokenRules() {
|
|
|
87
87
|
* bare `<a>`: `render={<Link/>}` is redundant beside `href` but it still routes,
|
|
88
88
|
* so it is not a bug.
|
|
89
89
|
*/
|
|
90
|
-
|
|
90
|
+
function linkRules() {
|
|
91
91
|
return [
|
|
92
92
|
{
|
|
93
93
|
selector: 'JSXOpeningElement[name.name=/^(Button|Badge|Card)$/] > JSXAttribute[name.name="render"] > JSXExpressionContainer > JSXElement > JSXOpeningElement[name.name="a"]',
|
|
@@ -95,7 +95,7 @@ export function linkRules() {
|
|
|
95
95
|
},
|
|
96
96
|
];
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
function typographyRules({ weights = false, ramp = "text-3xs 10 / text-2xs 11 / text-xs 12 / text-sm 14 / text-base 16 and up", pairing = false, axis = false, leading = false, } = {}) {
|
|
99
99
|
return [
|
|
100
100
|
// Alpha ink composites against whatever surface it lands on, so its
|
|
101
101
|
// contrast is unmeasurable. The ink tokens are measured.
|
|
@@ -172,26 +172,3 @@ export function designRules({ accents, typography = true, ...type } = {}) {
|
|
|
172
172
|
...renamedTokenRules(),
|
|
173
173
|
];
|
|
174
174
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Every rule in one flat-config entry, ready to spread into eslint.config.js:
|
|
177
|
-
*
|
|
178
|
-
* import { designConfig } from "@supertype.ai/foundations/eslint";
|
|
179
|
-
* export default [ ...designConfig({ accents: "the brand tints" }) ];
|
|
180
|
-
*
|
|
181
|
-
* One entry is not a detail. Flat config replaces a rule's options rather than
|
|
182
|
-
* merging them, so two blocks covering overlapping files leave only the last
|
|
183
|
-
* one's rules in effect. Combining them here is what stops a consumer losing
|
|
184
|
-
* half the set by accident. If you need a second scope, call this again with a
|
|
185
|
-
* different `files` and no overlap.
|
|
186
|
-
*/
|
|
187
|
-
export function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"], ...options } = {}) {
|
|
188
|
-
return [
|
|
189
|
-
{
|
|
190
|
-
name: "@supertype.ai/foundations/design",
|
|
191
|
-
files,
|
|
192
|
-
rules: {
|
|
193
|
-
"no-restricted-syntax": ["error", ...designRules(options)],
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
];
|
|
197
|
-
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How far a centred mark misses the letters, per rung of a type ramp.
|
|
3
|
+
*
|
|
4
|
+
* `items-center` centres boxes, and the box is the line box: leading, ascent and
|
|
5
|
+
* descent, only some of which the letters use. The mark beside a label therefore
|
|
6
|
+
* centres on the font's box rather than on the band of ink a reader sees, and
|
|
7
|
+
* whether those two agree is a property of the font, not of the design.
|
|
8
|
+
*
|
|
9
|
+
* The whole tool is one line of arithmetic, and the rounding is the reason it is
|
|
10
|
+
* worth shipping. Ratios alone give one constant tilt for a face, 0.0235em for
|
|
11
|
+
* Ubuntu Sans, which reads as every rung being equally out. Browsers quantise
|
|
12
|
+
* ascent, descent and cap height to whole pixels before they lay a line out, and
|
|
13
|
+
* rounded, that same face is half a pixel out at 11px, flat at 13px and half a
|
|
14
|
+
* pixel out again at 22px. The rendered pages agree, so the rounding is what
|
|
15
|
+
* separates a rung that needs the trim from one where it buys nothing.
|
|
16
|
+
*
|
|
17
|
+
* Build-time only, and only as good as the metrics handed to it. Verify cap
|
|
18
|
+
* height against the browser rather than a table: `next/font` ships 693 for
|
|
19
|
+
* Ubuntu Sans where canvas measures 727, and the wrong one flips the answer at
|
|
20
|
+
* every rung.
|
|
21
|
+
*/
|
|
22
|
+
/** A font's vertical metrics, in font units. The four numbers every metrics
|
|
23
|
+
* table carries, `next/font`'s and capsize's alike. */
|
|
24
|
+
export interface FontMetrics {
|
|
25
|
+
unitsPerEm: number;
|
|
26
|
+
ascent: number;
|
|
27
|
+
descent: number;
|
|
28
|
+
capHeight: number;
|
|
29
|
+
}
|
|
30
|
+
/** One step of a ramp: the name an app knows it by, and its size in px. */
|
|
31
|
+
export interface TypeRung {
|
|
32
|
+
name: string;
|
|
33
|
+
fontSize: number;
|
|
34
|
+
}
|
|
35
|
+
export interface OpticalOffset extends TypeRung {
|
|
36
|
+
/** How far below the cap band's centre a centred mark sits, in px. Positive is
|
|
37
|
+
* low, which is the direction rounding takes it. */
|
|
38
|
+
offset: number;
|
|
39
|
+
/** The same miss against the height of the letters it misses. Half a pixel is
|
|
40
|
+
* a twelfth of an 11px cap band and a thirty-second of a 36px one, so this is
|
|
41
|
+
* the number that says whether a reader sees it. */
|
|
42
|
+
share: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Every rung whose centred mark misses the letters by enough to see, worst first.
|
|
46
|
+
*
|
|
47
|
+
* A rung that comes back is one where an icon, a badge or a swatch set beside the
|
|
48
|
+
* text with `items-center` wants `CAP_TRIM` on the text to land on it. Most rungs
|
|
49
|
+
* of a ramp are half a pixel out, so the pixel is not the question: `tolerance` is
|
|
50
|
+
* a share of the cap band, and 0.05 is where a miss stops reading as a rounding
|
|
51
|
+
* artefact and starts reading as two things that do not line up. Raise it for a
|
|
52
|
+
* surface that only sets headlines, lower it to see the whole ramp.
|
|
53
|
+
*/
|
|
54
|
+
export declare function checkOptical(metrics: FontMetrics, rungs: readonly TypeRung[], { tolerance }?: {
|
|
55
|
+
tolerance?: number;
|
|
56
|
+
}): OpticalOffset[];
|
package/dist/optical.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How far a centred mark misses the letters, per rung of a type ramp.
|
|
3
|
+
*
|
|
4
|
+
* `items-center` centres boxes, and the box is the line box: leading, ascent and
|
|
5
|
+
* descent, only some of which the letters use. The mark beside a label therefore
|
|
6
|
+
* centres on the font's box rather than on the band of ink a reader sees, and
|
|
7
|
+
* whether those two agree is a property of the font, not of the design.
|
|
8
|
+
*
|
|
9
|
+
* The whole tool is one line of arithmetic, and the rounding is the reason it is
|
|
10
|
+
* worth shipping. Ratios alone give one constant tilt for a face, 0.0235em for
|
|
11
|
+
* Ubuntu Sans, which reads as every rung being equally out. Browsers quantise
|
|
12
|
+
* ascent, descent and cap height to whole pixels before they lay a line out, and
|
|
13
|
+
* rounded, that same face is half a pixel out at 11px, flat at 13px and half a
|
|
14
|
+
* pixel out again at 22px. The rendered pages agree, so the rounding is what
|
|
15
|
+
* separates a rung that needs the trim from one where it buys nothing.
|
|
16
|
+
*
|
|
17
|
+
* Build-time only, and only as good as the metrics handed to it. Verify cap
|
|
18
|
+
* height against the browser rather than a table: `next/font` ships 693 for
|
|
19
|
+
* Ubuntu Sans where canvas measures 727, and the wrong one flips the answer at
|
|
20
|
+
* every rung.
|
|
21
|
+
*/
|
|
22
|
+
const px = (value, fontSize, unitsPerEm) => Math.round((value * fontSize) / unitsPerEm);
|
|
23
|
+
/**
|
|
24
|
+
* The gap between the line box's centre and the cap band's, at one size.
|
|
25
|
+
*
|
|
26
|
+
* Half-leading cancels, so line height does not appear: a rung that is out stays
|
|
27
|
+
* out however loosely it is set, and no retune of the ramp's leading fixes it.
|
|
28
|
+
* Paint rounds the baseline a second time, in the same direction, so treat this
|
|
29
|
+
* as the floor of the error rather than the whole of it.
|
|
30
|
+
*/
|
|
31
|
+
function capBandOffset(metrics, fontSize) {
|
|
32
|
+
const ascent = px(metrics.ascent, fontSize, metrics.unitsPerEm);
|
|
33
|
+
const descent = px(metrics.descent, fontSize, metrics.unitsPerEm);
|
|
34
|
+
const capHeight = px(metrics.capHeight, fontSize, metrics.unitsPerEm);
|
|
35
|
+
return capHeight / 2 - (ascent - descent) / 2;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Every rung whose centred mark misses the letters by enough to see, worst first.
|
|
39
|
+
*
|
|
40
|
+
* A rung that comes back is one where an icon, a badge or a swatch set beside the
|
|
41
|
+
* text with `items-center` wants `CAP_TRIM` on the text to land on it. Most rungs
|
|
42
|
+
* of a ramp are half a pixel out, so the pixel is not the question: `tolerance` is
|
|
43
|
+
* a share of the cap band, and 0.05 is where a miss stops reading as a rounding
|
|
44
|
+
* artefact and starts reading as two things that do not line up. Raise it for a
|
|
45
|
+
* surface that only sets headlines, lower it to see the whole ramp.
|
|
46
|
+
*/
|
|
47
|
+
export function checkOptical(metrics, rungs, { tolerance = 0.05 } = {}) {
|
|
48
|
+
return rungs
|
|
49
|
+
.map((rung) => {
|
|
50
|
+
const offset = capBandOffset(metrics, rung.fontSize);
|
|
51
|
+
const capHeight = px(metrics.capHeight, rung.fontSize, metrics.unitsPerEm);
|
|
52
|
+
return { ...rung, offset, share: Math.abs(offset) / capHeight };
|
|
53
|
+
})
|
|
54
|
+
.filter((rung) => rung.share > tolerance)
|
|
55
|
+
.sort((a, b) => b.share - a.share);
|
|
56
|
+
}
|
package/dist/rehype.d.ts
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import rehypeShiki, { type RehypeShikiOptions } from "@shikijs/rehype";
|
|
2
|
-
/**
|
|
3
|
-
* Explicit rather than Shiki's full set — every grammar is build-time parsing.
|
|
4
|
-
* Anything unlisted falls back to `text`: content uses labels that are not real
|
|
5
|
-
* grammars (`tree`, `spark-defaults`), and dying over a fence label is a bad trade.
|
|
6
|
-
*/
|
|
7
|
-
export declare const PROSE_LANGS: readonly ["bash", "csv", "diff", "docker", "json", "python", "sql", "toml", "tsx", "typescript", "javascript", "yaml"];
|
|
8
2
|
export declare const PROSE_THEMES: {
|
|
9
3
|
readonly light: "github-light";
|
|
10
4
|
readonly dark: "github-dark";
|
|
11
5
|
};
|
|
12
|
-
/**
|
|
13
|
-
* `defaultColor: false` emits `--shiki-light` / `--shiki-dark` per token instead
|
|
14
|
-
* of a baked colour, so one compiled document serves both themes; `shiki.css`
|
|
15
|
-
* maps them. A `[plugin, options]` tuple, so it drops into `rehypePlugins`.
|
|
16
|
-
*/
|
|
17
|
-
export declare const proseCodeOptions: RehypeShikiOptions;
|
|
18
6
|
export declare const rehypeProseCode: [typeof rehypeShiki, RehypeShikiOptions];
|
package/dist/rehype.js
CHANGED
|
@@ -4,7 +4,7 @@ import rehypeShiki, {} from "@shikijs/rehype";
|
|
|
4
4
|
* Anything unlisted falls back to `text`: content uses labels that are not real
|
|
5
5
|
* grammars (`tree`, `spark-defaults`), and dying over a fence label is a bad trade.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
const PROSE_LANGS = [
|
|
8
8
|
"bash",
|
|
9
9
|
"csv",
|
|
10
10
|
"diff",
|
|
@@ -27,7 +27,7 @@ export const PROSE_THEMES = {
|
|
|
27
27
|
* of a baked colour, so one compiled document serves both themes; `shiki.css`
|
|
28
28
|
* maps them. A `[plugin, options]` tuple, so it drops into `rehypePlugins`.
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
const proseCodeOptions = {
|
|
31
31
|
themes: PROSE_THEMES,
|
|
32
32
|
defaultColor: false,
|
|
33
33
|
langs: [...PROSE_LANGS],
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lining a mark up with the words beside it, from either side.
|
|
3
|
+
*
|
|
4
|
+
* `items-center` centres boxes, and a line box is not the letters: it holds the
|
|
5
|
+
* leading, the ascent and the descent, whatever the string uses of them. There
|
|
6
|
+
* are two ways to close that gap and they apply to opposite elements. `CAP_TRIM`
|
|
7
|
+
* shrinks the text to its ink, for a row whose items are all on one line.
|
|
8
|
+
* `ON_FIRST_LINE` grows the mark to one line, for a row whose text runs on and
|
|
9
|
+
* whose mark belongs to the name at the top of it.
|
|
10
|
+
*
|
|
11
|
+
* Reach for one or the other, never both on the same row.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The optical box: cap top to baseline, with the leading either side removed.
|
|
15
|
+
*
|
|
16
|
+
* A line box reserves room for the ascenders and descenders a string may not
|
|
17
|
+
* use, so `items-center` beside an icon centres that reservation rather than
|
|
18
|
+
* the letters. On an 11px uppercase label the mark next to it renders about a
|
|
19
|
+
* pixel low, which is a whole device pixel against an eight pixel cap band.
|
|
20
|
+
* Trimming makes the element as tall as its own ink, so the row centres what
|
|
21
|
+
* the reader actually sees.
|
|
22
|
+
*
|
|
23
|
+
* Two things follow. It goes on the text element, since `text-box` is not
|
|
24
|
+
* inherited and a row cannot hand it down. And it shortens that element, so a
|
|
25
|
+
* row of trimmed text needs a height floor of its own: without one, the card
|
|
26
|
+
* whose label carries no mark sits shorter than the three beside it and its
|
|
27
|
+
* figure rides high. Chrome and Safari trim, and a browser that does not know
|
|
28
|
+
* `text-box` keeps the untrimmed box, which is the behaviour of every consumer
|
|
29
|
+
* today.
|
|
30
|
+
*
|
|
31
|
+
* Never on a string that also clips. The bottom edge is the baseline, so
|
|
32
|
+
* descenders sit outside the box, and `truncate` or any other overflow hidden
|
|
33
|
+
* cuts the tails off every g and p in it.
|
|
34
|
+
*
|
|
35
|
+
* Uppercase is where it pays. A cap band fills half of an 11px line box and the
|
|
36
|
+
* mark beside it lands a whole device pixel low, where 13px mixed case measures
|
|
37
|
+
* the same trimmed or not: ascenders reach the top of the line box on their own,
|
|
38
|
+
* so there is little leading left to take.
|
|
39
|
+
*/
|
|
40
|
+
export declare const CAP_TRIM = "[text-box:trim-both_cap_alphabetic]";
|
|
41
|
+
/**
|
|
42
|
+
* A box one line tall with the mark centred in it, for a mark that belongs to the
|
|
43
|
+
* first line of something taller.
|
|
44
|
+
*
|
|
45
|
+
* The row is `items-start`, so its own height is whatever the text grew to and
|
|
46
|
+
* centring on it would drop the mark into the middle of a paragraph. This gives
|
|
47
|
+
* the mark the first line's height instead, which is the thing it names. It goes
|
|
48
|
+
* on a wrapper rather than the mark: a height on the mark itself would stretch
|
|
49
|
+
* the glyph rather than move it.
|
|
50
|
+
*
|
|
51
|
+
* `1lh` resolves against the wrapper's own inherited leading, so a first line set
|
|
52
|
+
* at a rung the row does not share wants that rung on the wrapper too. The
|
|
53
|
+
* alternative is what this replaces, a top margin holding the mark down by a
|
|
54
|
+
* number that is right at one pairing of rung and mark size and silently wrong at
|
|
55
|
+
* every other.
|
|
56
|
+
*/
|
|
57
|
+
export declare const ON_FIRST_LINE = "flex h-[1lh] shrink-0 items-center";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lining a mark up with the words beside it, from either side.
|
|
3
|
+
*
|
|
4
|
+
* `items-center` centres boxes, and a line box is not the letters: it holds the
|
|
5
|
+
* leading, the ascent and the descent, whatever the string uses of them. There
|
|
6
|
+
* are two ways to close that gap and they apply to opposite elements. `CAP_TRIM`
|
|
7
|
+
* shrinks the text to its ink, for a row whose items are all on one line.
|
|
8
|
+
* `ON_FIRST_LINE` grows the mark to one line, for a row whose text runs on and
|
|
9
|
+
* whose mark belongs to the name at the top of it.
|
|
10
|
+
*
|
|
11
|
+
* Reach for one or the other, never both on the same row.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The optical box: cap top to baseline, with the leading either side removed.
|
|
15
|
+
*
|
|
16
|
+
* A line box reserves room for the ascenders and descenders a string may not
|
|
17
|
+
* use, so `items-center` beside an icon centres that reservation rather than
|
|
18
|
+
* the letters. On an 11px uppercase label the mark next to it renders about a
|
|
19
|
+
* pixel low, which is a whole device pixel against an eight pixel cap band.
|
|
20
|
+
* Trimming makes the element as tall as its own ink, so the row centres what
|
|
21
|
+
* the reader actually sees.
|
|
22
|
+
*
|
|
23
|
+
* Two things follow. It goes on the text element, since `text-box` is not
|
|
24
|
+
* inherited and a row cannot hand it down. And it shortens that element, so a
|
|
25
|
+
* row of trimmed text needs a height floor of its own: without one, the card
|
|
26
|
+
* whose label carries no mark sits shorter than the three beside it and its
|
|
27
|
+
* figure rides high. Chrome and Safari trim, and a browser that does not know
|
|
28
|
+
* `text-box` keeps the untrimmed box, which is the behaviour of every consumer
|
|
29
|
+
* today.
|
|
30
|
+
*
|
|
31
|
+
* Never on a string that also clips. The bottom edge is the baseline, so
|
|
32
|
+
* descenders sit outside the box, and `truncate` or any other overflow hidden
|
|
33
|
+
* cuts the tails off every g and p in it.
|
|
34
|
+
*
|
|
35
|
+
* Uppercase is where it pays. A cap band fills half of an 11px line box and the
|
|
36
|
+
* mark beside it lands a whole device pixel low, where 13px mixed case measures
|
|
37
|
+
* the same trimmed or not: ascenders reach the top of the line box on their own,
|
|
38
|
+
* so there is little leading left to take.
|
|
39
|
+
*/
|
|
40
|
+
export const CAP_TRIM = "[text-box:trim-both_cap_alphabetic]";
|
|
41
|
+
/**
|
|
42
|
+
* A box one line tall with the mark centred in it, for a mark that belongs to the
|
|
43
|
+
* first line of something taller.
|
|
44
|
+
*
|
|
45
|
+
* The row is `items-start`, so its own height is whatever the text grew to and
|
|
46
|
+
* centring on it would drop the mark into the middle of a paragraph. This gives
|
|
47
|
+
* the mark the first line's height instead, which is the thing it names. It goes
|
|
48
|
+
* on a wrapper rather than the mark: a height on the mark itself would stretch
|
|
49
|
+
* the glyph rather than move it.
|
|
50
|
+
*
|
|
51
|
+
* `1lh` resolves against the wrapper's own inherited leading, so a first line set
|
|
52
|
+
* at a rung the row does not share wants that rung on the wrapper too. The
|
|
53
|
+
* alternative is what this replaces, a top margin holding the mark down by a
|
|
54
|
+
* number that is right at one pairing of rung and mark size and silently wrong at
|
|
55
|
+
* every other.
|
|
56
|
+
*/
|
|
57
|
+
export const ON_FIRST_LINE = "flex h-[1lh] shrink-0 items-center";
|
package/dist/typography/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The optical box: cap top to baseline, with the leading either side removed.
|
|
3
|
+
*
|
|
4
|
+
* A line box reserves room for the ascenders and descenders a string may not
|
|
5
|
+
* use, so `items-center` beside an icon centres that reservation rather than
|
|
6
|
+
* the letters. On an 11px uppercase label the mark next to it renders about a
|
|
7
|
+
* pixel low, which is a whole device pixel against an eight pixel cap band.
|
|
8
|
+
* Trimming makes the element as tall as its own ink, so the row centres what
|
|
9
|
+
* the reader actually sees.
|
|
10
|
+
*
|
|
11
|
+
* Two things follow. It goes on the text element, since `text-box` is not
|
|
12
|
+
* inherited and a row cannot hand it down. And it shortens that element, so a
|
|
13
|
+
* row of trimmed text needs a height floor of its own: without one, the card
|
|
14
|
+
* whose label carries no mark sits shorter than the three beside it and its
|
|
15
|
+
* figure rides high. Chrome and Safari trim, and a browser that does not know
|
|
16
|
+
* `text-box` keeps the untrimmed box, which is the behaviour of every consumer
|
|
17
|
+
* today.
|
|
18
|
+
*
|
|
19
|
+
* Never on a string that also clips. The bottom edge is the baseline, so
|
|
20
|
+
* descenders sit outside the box, and `truncate` or any other overflow hidden
|
|
21
|
+
* cuts the tails off every g and p in it.
|
|
22
|
+
*
|
|
23
|
+
* Uppercase is where it pays. A cap band fills half of an 11px line box and the
|
|
24
|
+
* mark beside it lands a whole device pixel low, where 13px mixed case measures
|
|
25
|
+
* the same trimmed or not: ascenders reach the top of the line box on their own,
|
|
26
|
+
* so there is little leading left to take.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CAP_TRIM = "[text-box:trim-both_cap_alphabetic]";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The optical box: cap top to baseline, with the leading either side removed.
|
|
3
|
+
*
|
|
4
|
+
* A line box reserves room for the ascenders and descenders a string may not
|
|
5
|
+
* use, so `items-center` beside an icon centres that reservation rather than
|
|
6
|
+
* the letters. On an 11px uppercase label the mark next to it renders about a
|
|
7
|
+
* pixel low, which is a whole device pixel against an eight pixel cap band.
|
|
8
|
+
* Trimming makes the element as tall as its own ink, so the row centres what
|
|
9
|
+
* the reader actually sees.
|
|
10
|
+
*
|
|
11
|
+
* Two things follow. It goes on the text element, since `text-box` is not
|
|
12
|
+
* inherited and a row cannot hand it down. And it shortens that element, so a
|
|
13
|
+
* row of trimmed text needs a height floor of its own: without one, the card
|
|
14
|
+
* whose label carries no mark sits shorter than the three beside it and its
|
|
15
|
+
* figure rides high. Chrome and Safari trim, and a browser that does not know
|
|
16
|
+
* `text-box` keeps the untrimmed box, which is the behaviour of every consumer
|
|
17
|
+
* today.
|
|
18
|
+
*
|
|
19
|
+
* Never on a string that also clips. The bottom edge is the baseline, so
|
|
20
|
+
* descenders sit outside the box, and `truncate` or any other overflow hidden
|
|
21
|
+
* cuts the tails off every g and p in it.
|
|
22
|
+
*
|
|
23
|
+
* Uppercase is where it pays. A cap band fills half of an 11px line box and the
|
|
24
|
+
* mark beside it lands a whole device pixel low, where 13px mixed case measures
|
|
25
|
+
* the same trimmed or not: ascenders reach the top of the line box on their own,
|
|
26
|
+
* so there is little leading left to take.
|
|
27
|
+
*/
|
|
28
|
+
export const CAP_TRIM = "[text-box:trim-both_cap_alphabetic]";
|
package/llms.txt
CHANGED
|
@@ -14,19 +14,18 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
14
14
|
`text-xs`, `font-medium`, `leading-tight`, `text-muted-foreground` or similar
|
|
15
15
|
on a text element, there is a primitive for it. Use that instead.
|
|
16
16
|
2. **Retune with CSS variables, not classes.** The package owns its classnames.
|
|
17
|
-
Change a `--text-*` rung, `--heading-weight` or a colour token
|
|
18
|
-
|
|
17
|
+
Change a `--text-*` rung, `--heading-weight` or a colour token instead of
|
|
18
|
+
overriding the package's utilities.
|
|
19
19
|
3. **Import from the right entry point.** `blocks`, `essay`, `mdx`, `seo`, `og`,
|
|
20
20
|
`eslint`, `rehype` and `contrast` are subpaths, not part of the root barrel.
|
|
21
21
|
4. **Use colour tokens, never literal colours.** `bg-card`, `text-foreground`,
|
|
22
22
|
`border-border`. No hex values, no `bg-zinc-800`, no `dark:` overrides that
|
|
23
23
|
swap one token for another.
|
|
24
|
-
5. **Paint a surface, hand down its ink.** Any element
|
|
24
|
+
5. **Paint a surface, hand down its ink.** Any element that gets a background
|
|
25
25
|
needs `INK_ON_FILL` (a tone fill) or one of `INK_ON_CARD`, `INK_ON_POPOVER`,
|
|
26
|
-
`INK_ON_SIDEBAR` (a tinted
|
|
27
|
-
and fails contrast silently.
|
|
28
|
-
|
|
29
|
-
assembled one styles nothing. For a surface with no constant, spread
|
|
26
|
+
`INK_ON_SIDEBAR` (a tinted surface), or the text inside it keeps the page's ink
|
|
27
|
+
and fails contrast silently. Tailwind only generates a class it can read as
|
|
28
|
+
text, so a built string styles nothing. For a surface without a constant, spread
|
|
30
29
|
`inkOnSurfaceStyle(token)` into `style`.
|
|
31
30
|
6. **Write the words like a person would.** See Writing copy below. It applies
|
|
32
31
|
to every string a reader sees and to the comments you leave behind.
|
|
@@ -50,7 +49,7 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
50
49
|
| inline code | `TypographyInlineCode` | root |
|
|
51
50
|
| a link | `TypographyLink` | root |
|
|
52
51
|
| an anchor around something already drawn | `Anchor` | `/blocks` |
|
|
53
|
-
| where an href goes, without a component | `resolveLink` | root |
|
|
52
|
+
| where an `href` goes, without a component | `resolveLink` | root |
|
|
54
53
|
| a highlighted phrase | `TypographyHighlight` | root |
|
|
55
54
|
| a button, or a link that looks like one | `Button` | `/blocks` |
|
|
56
55
|
| a status label, count or chip | `Badge` | `/blocks` |
|
|
@@ -67,6 +66,7 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
67
66
|
| an article whose body is prose or MDX | `EssayHeader` + `ReadingLayout` | `/essay` |
|
|
68
67
|
| a post meta row (date, read time, tags) | `PostMetaRow` and friends | `/essay` |
|
|
69
68
|
| a table of contents | `TableOfContents`, `ReadingRail` | `/essay` |
|
|
69
|
+
| where a centred mark lands on a rung | `checkOptical` | `/optical` |
|
|
70
70
|
| page metadata and JSON-LD | `createSeo` | `/seo` |
|
|
71
71
|
| an OG image | `ogCard`, `OG_SIZE` | `/og` |
|
|
72
72
|
| to merge classnames | `cn` | root |
|
|
@@ -75,15 +75,16 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
75
75
|
|
|
76
76
|
| import | exports |
|
|
77
77
|
|---|---|
|
|
78
|
-
| `@supertype.ai/foundations` | `cn`, `TypographyH1`, `TypographyH2`, `TypographyH3`, `TypographyH4`, `TypographyEyebrow`, `TypographyP`, `TypographyMuted`, `TypographyProse`, `TypographyList`, `TypographyProseList`, `TypographyCaption`, `TypographySmall`, `TypographyLabel`, `TypographyStat`, `TypographyInlineCode`, `TypographyLink`, `TypographyHighlight`, `headingClass`, `headingFace`, `eyebrowClass`, `toneClass`, `impliedTone`, `resolveLink`, `isExternalHref`. Types: `TypographyTag`, `ParagraphVariants`, `ListProps`, `CaptionVariants`, `LabelVariants`, `StatVariants`, `Tone`, `HighlightTone`, `LinkBehavior`, `ResolvedLink` |
|
|
78
|
+
| `@supertype.ai/foundations` | `cn`, `TypographyH1`, `TypographyH2`, `TypographyH3`, `TypographyH4`, `TypographyEyebrow`, `TypographyP`, `TypographyMuted`, `TypographyProse`, `TypographyList`, `TypographyProseList`, `TypographyCaption`, `TypographySmall`, `TypographyLabel`, `TypographyStat`, `TypographyInlineCode`, `TypographyLink`, `TypographyHighlight`, `headingClass`, `headingFace`, `eyebrowClass`, `toneClass`, `impliedTone`, `resolveLink`, `isExternalHref`, `CAP_TRIM`, `ON_FIRST_LINE`. Types: `TypographyTag`, `ParagraphVariants`, `ListProps`, `CaptionVariants`, `LabelVariants`, `StatVariants`, `Tone`, `HighlightTone`, `LinkBehavior`, `ResolvedLink` |
|
|
79
79
|
| `@supertype.ai/foundations/blocks` | `Anchor`, `Cards`, `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `Callout`, `Button`, `buttonVariants`, `Badge`, `badgeVariants`, `Steps`, `Step`, `Disclosure`, `DisclosureGroup`, `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`, `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabGroup`, `SEGMENT`, `Bulletin`, `Ribbon`, `EDITORIAL_INKS`, `Colophon`, `BuiltWithFoundations`, `FoundationsMark`, `FOUNDATIONS_URL`. Types: `ButtonLook`, `BadgeLook`, `TabItem`, `BulletinProps`, `BulletinPoint`, `RibbonHue`, `ColophonProps` |
|
|
80
80
|
| `@supertype.ai/foundations/mdx` | `proseMdxComponents` |
|
|
81
81
|
| `@supertype.ai/foundations/essay` | `createEssay`, `EssayHeader`, `EssayLayout`, `EssaySection`, `EssayPullQuote`, `EssayFigure`, `EssayMovements`, `EssayDocument`, `EssayColumns`, `EssayAside`, `EssayBody`, `ReadingLayout`, `TableOfContents`, `ReadingRail`, `ReadingProgressBar`, `Rail`, `RailLink`, `PostMetaRow`, `PostDate`, `ReadTime`, `TagPills`, `MetaDot`, `formatPostDate`, `extractHeadings`, `readingTime`, `createSlugger`, `useReadingProgress`, `useScrollSpy`. Types: `TocHeading`, `EssayDecorations`, `EssayIndexEntry`, `EssayDocSection`, `EssayMovement`, `PostDateFormat` |
|
|
82
82
|
| `@supertype.ai/foundations/seo` | `createSeo`. Types: `SeoConfig`, `ArticleAuthor`, `ArticleOptions`, `PageMetadata` |
|
|
83
83
|
| `@supertype.ai/foundations/og` | `ogCard`, `OG_SIZE`. Types: `OgCardOptions` |
|
|
84
|
-
| `@supertype.ai/foundations/eslint` | `designRules
|
|
85
|
-
| `@supertype.ai/foundations/
|
|
86
|
-
| `@supertype.ai/foundations/
|
|
84
|
+
| `@supertype.ai/foundations/eslint` | `designRules`: every rule as one array, to spread into a `no-restricted-syntax` entry. The set is assembled inside, so it cannot drift. Types: `DesignRuleOptions`, `RestrictedSyntax` |
|
|
85
|
+
| `@supertype.ai/foundations/optical` | `checkOptical`: each rung of a ramp whose centred mark misses the letters by enough to see, worst first. Types: `FontMetrics`, `TypeRung`, `OpticalOffset`. Build-time only |
|
|
86
|
+
| `@supertype.ai/foundations/rehype` | `rehypeProseCode`, `PROSE_THEMES`. Build-time only, must not resolve React |
|
|
87
|
+
| `@supertype.ai/foundations/contrast` | `checkLegibility` (inks at 4.5:1), `checkSignals` (fills at 3:1, tinted inks at 4.5:1, `--subtle-foreground` at the 3:1 it is documented for, labels against their own fill), `checkHairlines` (`--border` and `--input` at 1.4:1 on `--background` and `--card`, `--sidebar-border` on `--sidebar`), `resolveTokens`, `formatFailures`, `specificity`, `parseColor`, `luminance`, `contrast` (WCAG ratio), `lc` (APCA lightness contrast), `tokenCuts`. Types: `Rgb`, `Theme`, `LegibilityFailure`, `TokenCuts`. Build-time only |
|
|
87
88
|
|
|
88
89
|
## Props worth knowing
|
|
89
90
|
|
|
@@ -93,110 +94,74 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
93
94
|
- `TypographyMuted`, `TypographyProse`, `TypographyProseList` are presets. The
|
|
94
95
|
prop each one pins is dropped from its type, so passing it fails to compile.
|
|
95
96
|
- `TypographyCaption`, `TypographyLabel`, `TypographySmall`: `size?: "sm" | "xs" | "2xs" | "inherit"`, `as?`.
|
|
96
|
-
- `TypographyEyebrow`: `tone?: "heading" | "label" | "muted" | "subtle"`, `size?: "sm" | "xs" | "2xs" | "3xs"`, `as?`. Each tone carries the rung it is usually set at and `size` overrides it, so omitting it changes nothing.
|
|
97
|
-
- `TypographyStat`: `size?: "inherit" | "3xs" | "2xs" | "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "card" | "panel" | "section" | "page" | "display"`, `figures?: "tabular" | "proportional"`, `tone?: "default" | "muted"`.
|
|
98
|
-
- `TypographyLink`: `href` (required), `tone?: Tone` (default `muted`), `addArrow?`, `newTab?`. The href decides internal
|
|
99
|
-
- `TypographyHighlight`: `tone?: HighlightTone`, one of `"primary" | "success" | "ochre" | "terracotta" | "sage" | "fig"`, plus `seed?: number`.
|
|
100
|
-
- `Card`: `href`, `title`, `description`, `icon`, `external`. An href makes the whole card a link.
|
|
101
|
-
- `Anchor`: `href` (required), plus `external`/`newTab`.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
- `
|
|
108
|
-
- `Badge`: `variant?: "solid" | "soft" | "outline" | "ghost"` (default `solid`), `tone?: Tone`, `size?: "xs" | "sm"` (default `sm`), `pill?: boolean`, `href` for a badge that leads somewhere. Same axes and same spellings as `Button`, minus `link`, which belongs to things you click. `warning` and `supertype` were `tone="warn"` and `tone="brand"` under invented names.
|
|
97
|
+
- `TypographyEyebrow`: `tone?: "heading" | "label" | "muted" | "subtle"`, `size?: "sm" | "xs" | "2xs" | "3xs"`, `as?`. Each tone carries the rung it is usually set at and `size` overrides it, so omitting it changes nothing. Use `muted` for the uppercase micro-label over a group of controls, and `subtle` for a column head or rail marker read on the way past.
|
|
98
|
+
- `TypographyStat`: `size?: "inherit" | "3xs" | "2xs" | "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "card" | "panel" | "section" | "page" | "display"`, `figures?: "tabular" | "proportional"`, `tone?: "default" | "muted"`.
|
|
99
|
+
- `TypographyLink`: `href` (required), `tone?: Tone` (default `muted`), `addArrow?`, `newTab?`. The `href` decides whether the link is internal or external.
|
|
100
|
+
- `TypographyHighlight`: `tone?: HighlightTone`, one of `"primary" | "success" | "ochre" | "terracotta" | "sage" | "fig"`, plus `seed?: number`.
|
|
101
|
+
- `Card`: `href`, `title`, `description`, `icon`, `external`. An `href` makes the whole card a link.
|
|
102
|
+
- `Anchor`: `href` (required), plus `external`/`newTab`.
|
|
103
|
+
- `Tone` is the semantic colour vocabulary shared by `Button`, `Badge`, `Callout`, `TypographyLink` and `TabsList`: `"muted" | "primary" | "secondary" | "brand" | "success" | "warn" | "destructive"`, defaulting to `muted` everywhere except a solid `Button`.
|
|
104
|
+
- **`CAP_TRIM` centres a mark on the letters.** `items-center` centres line boxes, which reserve room for ascenders and descenders the string may not use. Put `CAP_TRIM` on the text element, never on the row.
|
|
105
|
+
- **`ON_FIRST_LINE` is the same fix from the mark's side.** For rows where a mark names the first line, use `items-start` and `ON_FIRST_LINE` so the mark sits in a wrapper one line tall.
|
|
106
|
+
- **Ink is handed down by whatever paints.** `toneClass(tone)` is a palette and sets no ink. A filled surface adds `INK_ON_FILL`; a tinted one adds `INK_ON_CARD`, `INK_ON_POPOVER` or `INK_ON_SIDEBAR`. If a surface does not declare them, spread `inkOnSurfaceStyle(token)` into `style` instead of building a class.
|
|
107
|
+
- `Button`: `variant?: "solid" | "soft" | "outline" | "ghost" | "link"` (default `solid`), `tone?: Tone`, `size?: "xs" | "sm" | "md" | "lg" | "xl"` (default `md`), `icon?: boolean`, `pill?: boolean`, `href` to make it a link, `render` for an element that is neither a button nor a link.
|
|
108
|
+
- `Badge`: `variant?: "solid" | "soft" | "outline" | "ghost"` (default `solid`), `tone?: Tone`, `size?: "xs" | "sm"` (default `sm`), `pill?: boolean`, `href` for a badge that leads somewhere.
|
|
109
109
|
- `Callout`: `tone?: Tone`, `density?: "compact" | "editorial"`, `title`, `icon`, `action`.
|
|
110
|
-
- `Bulletin`:
|
|
111
|
-
- `Colophon`: `Bulletin` with this package's words in it, and the words are not props. `variant?: "card" | "line"` (default `card`), plus `label` (the link's words, for translation) and `children` (the consumer's own credit, rendered under the rule on `card`). A compact statement block for a page (`card`) or a footer row (`line`). There is nothing to configure, so `<Colophon />` is the whole call. Its copy is deliberately not props: this is the package's only content component — every other block is an empty container whose words come from the call site — which makes it a mark rather than a block, and a mark every site rewrites is not a mark. An app wanting to say something in its own words wants a `Card`. It draws the eight categorical hues and inks two of them, all declared in theme.css: an app that imported tokens.css alone renders a panel with an invisible ribbon and no error.
|
|
112
|
-
- **`Colophon` or `BuiltWithFoundations`.** A colophon is a block with something to say and needs a place to say it — a credits page, an about page, the foot of a long article. `BuiltWithFoundations` is one item in a row somebody else laid out, beside a privacy link and a copyright line, and says only where the site came from. The test is what the surface is for: somewhere with its own columns already takes the link, and somewhere the block itself is being placed takes the colophon. There is no `Colophon` variant rendering the link alone, since that would be a second name for a component that already has one. `BuiltWithFoundations` is a `Button variant="outline" size="sm"` with an `href`, so it sits on the same hairline and the same radius rung as every other control on the page. `FoundationsMark` is the eight-hue chip alone and `FOUNDATIONS_URL` the bare href.
|
|
110
|
+
- `Bulletin`: `variant?: "card" | "line"` (default `card`), `accent`, `eyebrow`, `headline`, `lede`, `points`, `action`, `footnote`, `children`, `className`.
|
|
113
111
|
- `DisclosureGroup`: `type?: "multiple" | "single"`, `defaultValue` (matches the title string).
|
|
114
|
-
- `TabsList`: `variant?: "default" | "line"`, `tone?: Tone` (default `primary`).
|
|
115
|
-
|
|
116
|
-
boxed track keeps its marker flat, so a tone there paints nothing.
|
|
117
|
-
- `TabGroup`: `tabs: readonly TabItem[]` (`{ value, label, icon?, content }`), `defaultValue?`,
|
|
118
|
-
`value?`, `onValueChange?`, `variant?`, `tone?`, `iconPosition?: "inline-start" | "inline-end"`.
|
|
119
|
-
`icon` is an element (`<Icons.Mic />`) that the trigger sizes and inks. Reach for it
|
|
120
|
-
whenever the tabs are data, and compose `Tabs`/`TabsTrigger` by hand only for a shape
|
|
121
|
-
it cannot express.
|
|
112
|
+
- `TabsList`: `variant?: "default" | "line"`, `tone?: Tone` (default `primary`).
|
|
113
|
+
- `TabGroup`: `tabs: readonly TabItem[]` (`{ value, label, icon?, content }`), `defaultValue?`, `value?`, `onValueChange?`, `variant?`, `tone?`, `iconPosition?: "inline-start" | "inline-end"`.
|
|
122
114
|
|
|
123
115
|
## Writing copy
|
|
124
116
|
|
|
125
117
|
Every string a reader sees goes through this: page copy, section notes, button
|
|
126
|
-
labels, callout titles, error messages, and
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- **
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
even when every sentence is true.
|
|
155
|
-
- **Headings are labels.** "How it stays current", not "Why it cannot go stale".
|
|
156
|
-
|
|
157
|
-
Code samples and commit messages stay conventional. This is about prose.
|
|
118
|
+
labels, callout titles, error messages, and comments in the code. Write as a
|
|
119
|
+
person would: direct, clear, and respectful of the reader's time.
|
|
120
|
+
|
|
121
|
+
- **Say the thing.** State what is true and move on. Skip the setup sentence and
|
|
122
|
+
the closing summary of what you just said.
|
|
123
|
+
- **Define forwards.** Prefer a direct description over a negative form unless the
|
|
124
|
+
absence is the point.
|
|
125
|
+
- **One em dash per page, at most.** Prefer a full stop, a comma, or a colon.
|
|
126
|
+
- **No arrow glyphs in prose.** Write “3.44:1 in light, 6.75:1 in dark” instead of
|
|
127
|
+
joining the two with an arrow.
|
|
128
|
+
- **Lead with the point.** If a clause explains the reason, put it up front or make
|
|
129
|
+
it its own sentence.
|
|
130
|
+
- **Cut the AI scaffolding.** Avoid “it is not…”, “there is no…”, “this is not…”,
|
|
131
|
+
“what this does is…”, “the reason is…”, and “because a…” patterns when a
|
|
132
|
+
direct statement does the work.
|
|
133
|
+
- **Write the actual rule.** Prefer “Badges are labels, not links” to “This is not
|
|
134
|
+
a link, because a badge is not a link.” Prefer “One function builds the set” to
|
|
135
|
+
“There is one function because the package builds the whole set in one place.”
|
|
136
|
+
- **Break the triples.** Long “X, Y, and Z” rhythms read like machine prose.
|
|
137
|
+
- **Cut the throat-clearing.** Avoid “it is worth noting”, “it is important to”,
|
|
138
|
+
“simply”, “just”, “of course”, and “in other words”.
|
|
139
|
+
- **Skip the rationale.** Explain what the reader needs to use, not the design
|
|
140
|
+
history behind it.
|
|
141
|
+
- **Concrete over abstract.** Name the class, the prop, or the file.
|
|
142
|
+
- **Vary the sentences.** A paragraph of same-length clauses reads as generated.
|
|
143
|
+
- **Headings are labels.** “How it stays current” is clearer than “Why it cannot go stale”.
|
|
144
|
+
|
|
145
|
+
Code samples and commit messages should stay conventional. This is about prose.
|
|
158
146
|
|
|
159
147
|
## Common mistakes
|
|
160
148
|
|
|
161
149
|
- `<p className="text-sm text-muted-foreground">`: use `<TypographyMuted>`.
|
|
162
|
-
- `<h2 className="text-lg font-semibold">`: use `<TypographyH2>`, or
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
live in `@supertype.ai/foundations/blocks`.
|
|
166
|
-
- `<Button variant="destructive">` or `variant="secondary"`: those are tones.
|
|
167
|
-
Write `variant="soft" tone="destructive"`, `variant="solid" tone="secondary"`.
|
|
150
|
+
- `<h2 className="text-lg font-semibold">`: use `<TypographyH2>`, or `headingClass()`.
|
|
151
|
+
- Importing `Card`, `Callout` or `Button` from `@supertype.ai/foundations`. They live in `@supertype.ai/foundations/blocks`.
|
|
152
|
+
- `<Button variant="destructive">` or `variant="secondary"`: those are tones. Write `variant="soft" tone="destructive"`, `variant="solid" tone="secondary"`.
|
|
168
153
|
- `<Button size="icon-sm">`: one size ladder. Write `size="sm" icon`.
|
|
169
|
-
- `<Badge variant="warning">` or `variant="supertype"`: tones, and misspelled ones.
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
the third.
|
|
174
|
-
- An `<a>` or `<Link>` styled by hand to look like a button, and equally
|
|
175
|
-
`render={<a href="/x" />}` or `render={<Link href="/x" />}` on anything that
|
|
176
|
-
takes an `href`. Write `<Button href="/x">`. `Button`, `Badge`, `Card` and
|
|
177
|
-
`TypographyLink` all take `href` and route it through one rule
|
|
178
|
-
(`resolveLink`): a scheme leaves the app and gets `target`/`rel`, a `#hash`
|
|
179
|
-
stays a plain anchor, everything else goes through the router's `Link` and
|
|
180
|
-
keeps the view transition. A hand-passed anchor skips all of that. `render` is
|
|
181
|
-
for an element that is genuinely not a link — and for `RailLink`, which takes a
|
|
182
|
-
router `Link` that way on purpose, since `essay/rail.tsx` has to import without
|
|
183
|
-
Next.
|
|
184
|
-
- Exporting a constant from a `"use client"` module and importing it into a server
|
|
185
|
-
component. It crosses the boundary as a stub, not a string, so an `href` built that
|
|
186
|
-
way arrives as a function. Keep shared constants in a plain module.
|
|
154
|
+
- `<Badge variant="warning">` or `variant="supertype"`: tones, and misspelled ones. Write `variant="outline" tone="warn"` and `variant="solid" tone="brand"`.
|
|
155
|
+
- `tone="neutral"`, `tone="foreground"` or `tone="accent"`: all three were replaced when the tone lists merged. Write `tone="muted"` for the first two, `tone="primary"` for the third.
|
|
156
|
+
- Hand-styling an `<a>` or `<Link>` to look like a button. Write `<Button href="/x">` instead.
|
|
157
|
+
- Exporting a constant from a `"use client"` module and importing it into a server component.
|
|
187
158
|
- `<TypographyMuted tone="default">`: a type error. Use `TypographyP`.
|
|
188
|
-
- Binding fonts with `font.className` instead of `font.variable`.
|
|
189
|
-
|
|
190
|
-
-
|
|
191
|
-
`@import "tailwindcss";` is the whole of it: it carries tokens, theme, type and
|
|
192
|
-
prose in order and registers the package's own `@source`. Split into the granular
|
|
193
|
-
imports, an app owns the order, the palette and the scan path — and a wrong
|
|
194
|
-
`@source` path purges every class the package ships, silently.
|
|
195
|
-
- Using `Accordion` for a static FAQ. `Disclosure` runs on the browser alone.
|
|
159
|
+
- Binding fonts with `font.className` instead of `font.variable`.
|
|
160
|
+
- Writing the style layer out by hand. `@import "@supertype.ai/foundations";` after `@import "tailwindcss";` is the whole of it.
|
|
161
|
+
- Using `Accordion` for a static FAQ. `Disclosure` is the browser-native version.
|
|
196
162
|
- Adding a second `@custom-variant dark`. `tokens.css` already binds it.
|
|
197
163
|
|
|
198
|
-
Run `npx foundations doctor` in the app to check the font binding, the style layer
|
|
199
|
-
and the dark variant.
|
|
164
|
+
Run `npx foundations doctor` in the app to check the font binding, the style layer and the dark variant.
|
|
200
165
|
|
|
201
166
|
## Surfaces
|
|
202
167
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supertype.ai/foundations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -68,6 +68,10 @@
|
|
|
68
68
|
"require": "./dist/cjs/eslint.js",
|
|
69
69
|
"default": "./dist/eslint.js"
|
|
70
70
|
},
|
|
71
|
+
"./optical": {
|
|
72
|
+
"types": "./dist/optical.d.ts",
|
|
73
|
+
"default": "./dist/optical.js"
|
|
74
|
+
},
|
|
71
75
|
"./rehype": {
|
|
72
76
|
"types": "./dist/rehype.d.ts",
|
|
73
77
|
"default": "./dist/rehype.js"
|
|
@@ -89,6 +93,7 @@
|
|
|
89
93
|
"test": "yarn build && vitest run",
|
|
90
94
|
"test:watch": "vitest",
|
|
91
95
|
"check:llms": "node scripts/check-llms.mjs",
|
|
96
|
+
"check:consumers": "node scripts/consumers.mjs",
|
|
92
97
|
"check:pins": "node scripts/pins.mjs",
|
|
93
98
|
"release": "node scripts/release.mjs",
|
|
94
99
|
"sync": "node scripts/sync.mjs",
|