@supertype.ai/foundations 0.1.30 → 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 +1 -1
- package/dist/blocks/accordion.js +2 -2
- package/dist/blocks/anchor.d.ts +27 -0
- package/dist/blocks/anchor.js +28 -0
- package/dist/blocks/badge.d.ts +2 -0
- package/dist/blocks/button.d.ts +14 -1
- package/dist/blocks/card.js +2 -2
- package/dist/blocks/index.d.ts +1 -0
- package/dist/blocks/index.js +1 -0
- package/dist/contrast.d.ts +7 -0
- package/dist/contrast.js +92 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tone.d.ts +24 -4
- package/dist/tone.js +23 -4
- package/llms.txt +14 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ untagged git dependency resolves to a different commit on a fresh install.
|
|
|
66
66
|
|
|
67
67
|
```jsonc
|
|
68
68
|
// package.json
|
|
69
|
-
"@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.1.
|
|
69
|
+
"@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.1.31"
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
</details>
|
package/dist/blocks/accordion.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Children, cloneElement, isValidElement } from "react";
|
|
3
3
|
import { cn } from "../cn.js";
|
|
4
|
-
import {
|
|
4
|
+
import { INK_ON_CARD } from "../tone.js";
|
|
5
5
|
/**
|
|
6
6
|
* A disclosure group: `<details>`/`<summary>`, no JS, correct before hydration.
|
|
7
7
|
*
|
|
@@ -56,5 +56,5 @@ function deriveGroupName(children) {
|
|
|
56
56
|
return `accordion-${(hash >>> 0).toString(36)}`;
|
|
57
57
|
}
|
|
58
58
|
export function Disclosure({ title, children, className, ...props }) {
|
|
59
|
-
return (_jsxs("details", { className: cn("group bg-card",
|
|
59
|
+
return (_jsxs("details", { className: cn("group bg-card", INK_ON_CARD, className), ...props, children: [_jsxs("summary", { className: "flex cursor-pointer list-none items-center justify-between gap-4 px-4 py-3 text-sm font-medium text-foreground marker:hidden hover:bg-accent [&::-webkit-details-marker]:hidden", children: [title, _jsx("svg", { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-muted-foreground transition-transform group-open:rotate-180", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m6 9 6 6 6-6" }) })] }), _jsx("div", { className: "px-4 pb-4 text-sm text-muted-foreground", children: children })] }));
|
|
60
60
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { type LinkBehavior } from "../href.js";
|
|
3
|
+
/**
|
|
4
|
+
* An anchor whose destination is this package's decision rather than the call
|
|
5
|
+
* site's — and nothing else.
|
|
6
|
+
*
|
|
7
|
+
* `Button`, `Badge`, `Card` and `TypographyLink` all take an `href` and hand it
|
|
8
|
+
* to ../href.ts. What was left over was every anchor that is none of those: a
|
|
9
|
+
* thumbnail, a chip, a tooltip trigger, a footer row, an icon in a dialog
|
|
10
|
+
* header. One consumer had thirty-six, each writing `target="_blank"
|
|
11
|
+
* rel="noopener noreferrer"` out by hand, five of them missing the `rel`, four
|
|
12
|
+
* of them putting the pair on a router `Link` — which asks for a client
|
|
13
|
+
* navigation and a new tab in the same breath.
|
|
14
|
+
*
|
|
15
|
+
* No styling, deliberately. `TypographyLink` is the inline link and brings a
|
|
16
|
+
* weight, an ink and an underline with it, which is why it could not take these:
|
|
17
|
+
* they wrap something already drawn, and the only thing they have in common is
|
|
18
|
+
* where they go.
|
|
19
|
+
*
|
|
20
|
+
* Pass `external` for a same-origin path that is not a route — an `/api/…`
|
|
21
|
+
* redirect, a file endpoint. `Link` prefetches on viewport entry, so a signed-URL
|
|
22
|
+
* endpoint would mint one for a link nobody clicked. Call-site props land after
|
|
23
|
+
* the resolved ones, so a link needing a `rel` of its own can still say so.
|
|
24
|
+
*/
|
|
25
|
+
export declare function Anchor({ href, external, newTab, ...props }: Omit<ComponentProps<"a">, "href"> & LinkBehavior & {
|
|
26
|
+
href: string;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { resolveLink } from "../href.js";
|
|
3
|
+
/**
|
|
4
|
+
* An anchor whose destination is this package's decision rather than the call
|
|
5
|
+
* site's — and nothing else.
|
|
6
|
+
*
|
|
7
|
+
* `Button`, `Badge`, `Card` and `TypographyLink` all take an `href` and hand it
|
|
8
|
+
* to ../href.ts. What was left over was every anchor that is none of those: a
|
|
9
|
+
* thumbnail, a chip, a tooltip trigger, a footer row, an icon in a dialog
|
|
10
|
+
* header. One consumer had thirty-six, each writing `target="_blank"
|
|
11
|
+
* rel="noopener noreferrer"` out by hand, five of them missing the `rel`, four
|
|
12
|
+
* of them putting the pair on a router `Link` — which asks for a client
|
|
13
|
+
* navigation and a new tab in the same breath.
|
|
14
|
+
*
|
|
15
|
+
* No styling, deliberately. `TypographyLink` is the inline link and brings a
|
|
16
|
+
* weight, an ink and an underline with it, which is why it could not take these:
|
|
17
|
+
* they wrap something already drawn, and the only thing they have in common is
|
|
18
|
+
* where they go.
|
|
19
|
+
*
|
|
20
|
+
* Pass `external` for a same-origin path that is not a route — an `/api/…`
|
|
21
|
+
* redirect, a file endpoint. `Link` prefetches on viewport entry, so a signed-URL
|
|
22
|
+
* endpoint would mint one for a link nobody clicked. Call-site props land after
|
|
23
|
+
* the resolved ones, so a link needing a `rel` of its own can still say so.
|
|
24
|
+
*/
|
|
25
|
+
export function Anchor({ href, external, newTab, ...props }) {
|
|
26
|
+
const { Component, props: link } = resolveLink(href, { external, newTab });
|
|
27
|
+
return _jsx(Component, { ...link, ...props });
|
|
28
|
+
}
|
package/dist/blocks/badge.d.ts
CHANGED
|
@@ -23,5 +23,7 @@ export declare function badgeVariants(props?: Parameters<typeof badge>[0]): stri
|
|
|
23
23
|
export declare function Badge({ className, variant, tone, size, pill, render, href, external, newTab, ...props }: ComponentProps<"span"> & BadgeLook & LinkBehavior & {
|
|
24
24
|
render?: ReactElement;
|
|
25
25
|
href?: string;
|
|
26
|
+
/** A link that is not a navigation. See Button, which documents the pairing with `external`. */
|
|
27
|
+
download?: ComponentProps<"a">["download"];
|
|
26
28
|
}): import("react").JSX.Element;
|
|
27
29
|
export {};
|
package/dist/blocks/button.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
1
2
|
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
3
|
import { type VariantProps } from "class-variance-authority";
|
|
3
4
|
import { type LinkBehavior } from "../href.js";
|
|
@@ -6,7 +7,7 @@ declare const button: (props?: ({
|
|
|
6
7
|
size?: "sm" | "xs" | "md" | "lg" | "xl" | null | undefined;
|
|
7
8
|
icon?: boolean | null | undefined;
|
|
8
9
|
pill?: boolean | null | undefined;
|
|
9
|
-
variant?: "
|
|
10
|
+
variant?: "solid" | "link" | "soft" | "outline" | "ghost" | null | undefined;
|
|
10
11
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
12
|
export type ButtonLook = VariantProps<typeof button>;
|
|
12
13
|
/**
|
|
@@ -28,5 +29,17 @@ export declare function buttonVariants(props?: Parameters<typeof button>[0]): st
|
|
|
28
29
|
*/
|
|
29
30
|
export declare function Button({ className, variant, tone, size, icon, pill, render, nativeButton, href, external, newTab, ...props }: ButtonPrimitive.Props & ButtonLook & LinkBehavior & {
|
|
30
31
|
href?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The one anchor attribute the `href` branch has to name itself. A download
|
|
34
|
+
* is a link that is not a navigation, so it is the case `href` alone cannot
|
|
35
|
+
* express — and `render={<a download />}`, which is how every call site said
|
|
36
|
+
* it before, is exactly what `linkRules()` now flags. Card already takes it,
|
|
37
|
+
* off `ComponentProps<"a">`; Button and Badge are anchors here too.
|
|
38
|
+
*
|
|
39
|
+
* Pair it with `external` for a same-origin route. `Link` steps aside on the
|
|
40
|
+
* click, but it still prefetches the href on viewport entry, which for an
|
|
41
|
+
* export endpoint means running the export to throw the rows away.
|
|
42
|
+
*/
|
|
43
|
+
download?: ComponentProps<"a">["download"];
|
|
31
44
|
}): import("react").JSX.Element;
|
|
32
45
|
export {};
|
package/dist/blocks/card.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../cn.js";
|
|
3
3
|
import { FOCUS_RING } from "./focus.js";
|
|
4
|
-
import {
|
|
4
|
+
import { INK_ON_CARD, toneClass } from "../tone.js";
|
|
5
5
|
import { resolveLink } from "../href.js";
|
|
6
6
|
/** Two columns from `sm` up: a pair reads as a set rather than two panels. */
|
|
7
7
|
export function Cards({ className, children, ...props }) {
|
|
@@ -12,7 +12,7 @@ export function Cards({ className, children, ...props }) {
|
|
|
12
12
|
* grid and `overflow-hidden` clips a bleed image cleanly. Padding is vertical
|
|
13
13
|
* only — the horizontal inset belongs to the slots, so bands can run edge to edge.
|
|
14
14
|
*/
|
|
15
|
-
const CARD_CLASS = `flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-border ${
|
|
15
|
+
const CARD_CLASS = `flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-border ${INK_ON_CARD} ` +
|
|
16
16
|
"has-[>img:first-child]:pt-0 " +
|
|
17
17
|
"*:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl";
|
|
18
18
|
/**
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Cards, Card, CardHeader, CardTitle, CardDescription, CardContent, } from "./card.js";
|
|
2
2
|
export { Disclosure, DisclosureGroup } from "./accordion.js";
|
|
3
3
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "./interactive-accordion.js";
|
|
4
|
+
export { Anchor } from "./anchor.js";
|
|
4
5
|
export { Callout } from "./callout.js";
|
|
5
6
|
export { Button, buttonVariants, type ButtonLook } from "./button.js";
|
|
6
7
|
export { Badge, badgeVariants, type BadgeLook } from "./badge.js";
|
package/dist/blocks/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Cards, Card, CardHeader, CardTitle, CardDescription, CardContent, } from "./card.js";
|
|
2
2
|
export { Disclosure, DisclosureGroup } from "./accordion.js";
|
|
3
3
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "./interactive-accordion.js";
|
|
4
|
+
export { Anchor } from "./anchor.js";
|
|
4
5
|
export { Callout } from "./callout.js";
|
|
5
6
|
export { Button, buttonVariants } from "./button.js";
|
|
6
7
|
export { Badge, badgeVariants } from "./badge.js";
|
package/dist/contrast.d.ts
CHANGED
|
@@ -45,6 +45,13 @@ export interface LegibilityFailure {
|
|
|
45
45
|
surface: string;
|
|
46
46
|
ratio: number;
|
|
47
47
|
required: number;
|
|
48
|
+
/**
|
|
49
|
+
* The token the pair was *supposed* to use, set only when it was undeclared
|
|
50
|
+
* and the cascade fell through to the next link of its `var()` chain. Without
|
|
51
|
+
* it a report reads as though the app chose `--primary-foreground` for its
|
|
52
|
+
* brand fill, when in truth it chose nothing and CSS chose for it.
|
|
53
|
+
*/
|
|
54
|
+
via?: string;
|
|
48
55
|
}
|
|
49
56
|
/**
|
|
50
57
|
* Every ink on every surface, both themes. Missing or non-literal tokens are
|
package/dist/contrast.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
* and `.dark` separately measures an intention, and ssite shipped a `.dark` at a
|
|
4
4
|
* healthy 15.7:1 while the page rendered white on white. Build-time only.
|
|
5
5
|
*/
|
|
6
|
+
// The tone vocabulary, for the pairs it names. Value import, not just a type:
|
|
7
|
+
// this file measures what `TONE` declares rather than keeping a second list of
|
|
8
|
+
// it. tone.js resolves no React and imports nothing at runtime, so the bare-Node
|
|
9
|
+
// contract this entry point owes still holds — `check-candidates` and the CLI
|
|
10
|
+
// both import it from plain Node.
|
|
11
|
+
import { TONE } from "./tone.js";
|
|
6
12
|
/**
|
|
7
13
|
* Specificity over what token blocks use. `:root` and `.dark` both score 1 —
|
|
8
14
|
* the tie above. `:not(…)` contributes its argument's score, per spec.
|
|
@@ -290,17 +296,89 @@ const TERTIARY = ["--subtle-foreground"];
|
|
|
290
296
|
* tone="warn" variant="solid"` renders one, and white on amber measured 2.44:1
|
|
291
297
|
* on the dark theme for as long as the pair went unnamed here.
|
|
292
298
|
*/
|
|
293
|
-
const
|
|
294
|
-
["--primary", "--primary-foreground"],
|
|
295
|
-
["--secondary", "--secondary-foreground"],
|
|
296
|
-
["--destructive", "--destructive-foreground"],
|
|
297
|
-
["--success", "--success-foreground"],
|
|
298
|
-
["--warn", "--warn-foreground"],
|
|
299
|
+
const ON_SURFACE = [
|
|
299
300
|
["--accent", "--accent-foreground"],
|
|
300
301
|
["--card", "--card-foreground"],
|
|
301
302
|
["--popover", "--popover-foreground"],
|
|
302
303
|
["--sidebar", "--sidebar-foreground"],
|
|
303
304
|
];
|
|
305
|
+
/**
|
|
306
|
+
* Every cut a tone names, read off `TONE` instead of restated here.
|
|
307
|
+
*
|
|
308
|
+
* The tone rows used to be five hand-written pairs in the list above, which is
|
|
309
|
+
* the arrangement the comment on `tokenCuts` warns about: a palette checked
|
|
310
|
+
* against one taxonomy and declared from another drifts, and `brand` is the
|
|
311
|
+
* proof. It was in `TONE` from the day the vocabulary landed and never in this
|
|
312
|
+
* file, so the one tone whose tokens an app supplies was the one tone nothing
|
|
313
|
+
* measured.
|
|
314
|
+
*
|
|
315
|
+
* Parsed rather than shared as data because `TONE` has to stay a table of
|
|
316
|
+
* literal class strings — Tailwind scans this package as text and generates
|
|
317
|
+
* only the classes it can read, so a row assembled from a record would style
|
|
318
|
+
* nothing. Parsing the literal keeps one declaration; a second table would be
|
|
319
|
+
* the drift all over again.
|
|
320
|
+
*/
|
|
321
|
+
const TONE_CUT = /\[--tone-(fill|ink|hue):([^\]]*)\]/g;
|
|
322
|
+
/**
|
|
323
|
+
* A cut's `var()` fallback chain, outermost first: `var(--brand,var(--primary))`
|
|
324
|
+
* is `["--brand", "--primary"]`. The order is the order CSS tries them in.
|
|
325
|
+
*/
|
|
326
|
+
const toneChains = (classes) => {
|
|
327
|
+
const chains = { fill: [], ink: [], hue: [] };
|
|
328
|
+
for (const [, cut, value] of classes.matchAll(TONE_CUT))
|
|
329
|
+
chains[cut] = [...value.matchAll(/--[a-z0-9-]+/g)].map((m) => m[0]);
|
|
330
|
+
return chains;
|
|
331
|
+
};
|
|
332
|
+
const TONE_CHAINS = Object.entries(TONE).map(([tone, classes]) => [tone, toneChains(classes)]);
|
|
333
|
+
/** The pair a tone names, for the taxonomy `tokenCuts` reports. */
|
|
334
|
+
const ON_FILL = [
|
|
335
|
+
...TONE_CHAINS.map(([, c]) => [c.fill[0], c.ink[0]]),
|
|
336
|
+
...ON_SURFACE,
|
|
337
|
+
];
|
|
338
|
+
/**
|
|
339
|
+
* A tone's label on a tone's fill, resolved the way a browser resolves it.
|
|
340
|
+
*
|
|
341
|
+
* `checkLegibility` skips a token it cannot find, and that is right: an app that
|
|
342
|
+
* declares no `--sidebar` has not failed a bar, it has declined a role. But a
|
|
343
|
+
* token missing *while its siblings are present* is a different animal. The tone
|
|
344
|
+
* still renders — `var(--brand-foreground,var(--primary-foreground))` simply
|
|
345
|
+
* moves to the next link — so the control is painted in a pair the app never
|
|
346
|
+
* chose and skipping it reads as a pass.
|
|
347
|
+
*
|
|
348
|
+
* That is how a bronze `--brand` shipped with a white label at 2.80:1: the fill
|
|
349
|
+
* was declared, the label was not, and nothing measured the pair that actually
|
|
350
|
+
* reached the screen. So this follows the chain to whichever link is really
|
|
351
|
+
* there, and measures that. An app declaring a whole tone is measured on its own
|
|
352
|
+
* tokens; one declaring none falls through to the package's, which are measured
|
|
353
|
+
* anyway; one declaring half hears about it in the only terms that matter, the
|
|
354
|
+
* two colours a reader is going to see.
|
|
355
|
+
*/
|
|
356
|
+
function checkToneCuts(css, { themes = ["light", "dark"] } = {}) {
|
|
357
|
+
const failures = [];
|
|
358
|
+
for (const theme of themes) {
|
|
359
|
+
const tokens = resolveTokens(css, theme);
|
|
360
|
+
const colorOf = (token) => parseColor(tokens[token] ?? "");
|
|
361
|
+
for (const [, chains] of TONE_CHAINS) {
|
|
362
|
+
const fill = chains.fill.find((token) => colorOf(token));
|
|
363
|
+
const ink = chains.ink.find((token) => colorOf(token));
|
|
364
|
+
if (!fill || !ink)
|
|
365
|
+
continue;
|
|
366
|
+
const ratio = contrast(colorOf(ink), colorOf(fill));
|
|
367
|
+
if (ratio >= 4.5)
|
|
368
|
+
continue;
|
|
369
|
+
failures.push({
|
|
370
|
+
theme,
|
|
371
|
+
ink,
|
|
372
|
+
surface: fill,
|
|
373
|
+
ratio,
|
|
374
|
+
required: 4.5,
|
|
375
|
+
// Only when the tone's own ink was not the one that answered.
|
|
376
|
+
...(ink === chains.ink[0] ? {} : { via: chains.ink[0] }),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return failures;
|
|
381
|
+
}
|
|
304
382
|
/**
|
|
305
383
|
* The cuts a token ships, read off the same three sets `checkSignals` measures.
|
|
306
384
|
*
|
|
@@ -376,12 +454,18 @@ export function checkSignals(css, { themes = ["light", "dark"] } = {}) {
|
|
|
376
454
|
}),
|
|
377
455
|
...checkLegibility(css, { inks: INKS_TINTED, themes }),
|
|
378
456
|
...checkLegibility(css, { inks: TERTIARY, minimum: 3, themes }),
|
|
379
|
-
...
|
|
457
|
+
...ON_SURFACE.flatMap(([fill, label]) => checkLegibility(css, { inks: [label], surfaces: [fill], themes })),
|
|
458
|
+
...checkToneCuts(css, { themes }),
|
|
380
459
|
];
|
|
381
460
|
}
|
|
382
461
|
/** A one-line report per failure, for a test's assertion message. */
|
|
383
462
|
export function formatFailures(failures) {
|
|
384
463
|
return failures
|
|
385
|
-
.map((f) =>
|
|
464
|
+
.map((f) => {
|
|
465
|
+
const measured = `${f.theme}: ${f.ink} on ${f.surface} is ${f.ratio.toFixed(2)}:1, below ${f.required}:1`;
|
|
466
|
+
return f.via
|
|
467
|
+
? `${measured} — ${f.via} is not declared, so ${f.surface} took ${f.ink} from the fallback`
|
|
468
|
+
: measured;
|
|
469
|
+
})
|
|
386
470
|
.join("\n");
|
|
387
471
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { cn } from "./cn.js";
|
|
2
|
-
export { toneClass, impliedTone, INK_ON_FILL,
|
|
2
|
+
export { toneClass, impliedTone, INK_ON_FILL, INK_ON_CARD, INK_ON_POPOVER, INK_ON_SIDEBAR, inkOnSurfaceStyle, type Tone, } from "./tone.js";
|
|
3
3
|
export { resolveLink, isExternalHref, type LinkBehavior, type ResolvedLink, } from "./href.js";
|
|
4
4
|
export * from "./typography/index.js";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { cn } from "./cn.js";
|
|
|
3
3
|
// takes it too: a link has a tone, drawn from the same seven a button has.
|
|
4
4
|
// `toneClass` only: the raw table and its derived half used to ship separately,
|
|
5
5
|
// and the order they were combined in was load-bearing.
|
|
6
|
-
export { toneClass, impliedTone, INK_ON_FILL,
|
|
6
|
+
export { toneClass, impliedTone, INK_ON_FILL, INK_ON_CARD, INK_ON_POPOVER, INK_ON_SIDEBAR, inkOnSurfaceStyle, } from "./tone.js";
|
|
7
7
|
// Where an href goes, for the rare call site that styles someone else's element
|
|
8
8
|
// and cannot render a Card/Button/TypographyLink — the same pairing with
|
|
9
9
|
// `buttonVariants`. Prefer passing `href` to a component over calling this.
|
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 —
|
|
@@ -111,11 +112,30 @@ export declare const toneClass: (tone: Tone) => string;
|
|
|
111
112
|
*/
|
|
112
113
|
export declare const INK_ON_FILL = "[--ink:var(--tone-ink)] [--ink-muted:var(--tone-ink)]";
|
|
113
114
|
/**
|
|
114
|
-
* The same contract for a surface the tones do not name
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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.
|
|
117
137
|
*/
|
|
118
|
-
export declare const
|
|
138
|
+
export declare const inkOnSurfaceStyle: (ink: string, muted?: string) => CSSProperties;
|
|
119
139
|
/**
|
|
120
140
|
* What an unstated tone means, given how much ink the component is spending.
|
|
121
141
|
* Shared, because `Button` and `Badge` both need it and two copies of a default
|
package/dist/tone.js
CHANGED
|
@@ -121,11 +121,30 @@ export const toneClass = (tone) => `${TONE_SURFACE} ${TONE[tone]}`;
|
|
|
121
121
|
*/
|
|
122
122
|
export const INK_ON_FILL = "[--ink:var(--tone-ink)] [--ink-muted:var(--tone-ink)]";
|
|
123
123
|
/**
|
|
124
|
-
* The same contract for a surface the tones do not name
|
|
125
|
-
*
|
|
126
|
-
*
|
|
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.
|
|
127
146
|
*/
|
|
128
|
-
export const
|
|
147
|
+
export const inkOnSurfaceStyle = (ink, muted = "--muted-foreground") => ({ "--ink": `var(${ink})`, "--ink-muted": `var(${muted})` });
|
|
129
148
|
/**
|
|
130
149
|
* What an unstated tone means, given how much ink the component is spending.
|
|
131
150
|
* Shared, because `Button` and `Badge` both need it and two copies of a default
|
package/llms.txt
CHANGED
|
@@ -22,8 +22,12 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
22
22
|
`border-border`. No hex values, no `bg-zinc-800`, no `dark:` overrides that
|
|
23
23
|
swap one token for another.
|
|
24
24
|
5. **Paint a surface, hand down its ink.** Any element you give a background
|
|
25
|
-
needs `INK_ON_FILL` (a tone fill) or
|
|
26
|
-
the type inside it keeps the page's ink
|
|
25
|
+
needs `INK_ON_FILL` (a tone fill) or one of `INK_ON_CARD`, `INK_ON_POPOVER`,
|
|
26
|
+
`INK_ON_SIDEBAR` (a tinted one), or the type inside it keeps the page's ink
|
|
27
|
+
and fails contrast silently. Never build one of these class strings
|
|
28
|
+
yourself: Tailwind only generates a class it can read as text, so an
|
|
29
|
+
assembled one styles nothing. For a surface with no constant, spread
|
|
30
|
+
`inkOnSurfaceStyle(token)` into `style`.
|
|
27
31
|
6. **Write the words like a person would.** See Writing copy below. It applies
|
|
28
32
|
to every string a reader sees and to the comments you leave behind.
|
|
29
33
|
|
|
@@ -45,6 +49,7 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
45
49
|
| a number or metric | `TypographyStat` | root |
|
|
46
50
|
| inline code | `TypographyInlineCode` | root |
|
|
47
51
|
| a link | `TypographyLink` | root |
|
|
52
|
+
| an anchor around something already drawn | `Anchor` | `/blocks` |
|
|
48
53
|
| where an href goes, without a component | `resolveLink` | root |
|
|
49
54
|
| a highlighted phrase | `TypographyHighlight` | root |
|
|
50
55
|
| a button, or a link that looks like one | `Button` | `/blocks` |
|
|
@@ -68,14 +73,14 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
68
73
|
| import | exports |
|
|
69
74
|
|---|---|
|
|
70
75
|
| `@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` |
|
|
71
|
-
| `@supertype.ai/foundations/blocks` | `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`. Types: `ButtonLook`, `BadgeLook`, `TabItem` |
|
|
76
|
+
| `@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`. Types: `ButtonLook`, `BadgeLook`, `TabItem` |
|
|
72
77
|
| `@supertype.ai/foundations/mdx` | `proseMdxComponents` |
|
|
73
78
|
| `@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` |
|
|
74
79
|
| `@supertype.ai/foundations/seo` | `createSeo`. Types: `SeoConfig`, `ArticleAuthor`, `ArticleOptions`, `PageMetadata` |
|
|
75
80
|
| `@supertype.ai/foundations/og` | `ogCard`, `OG_SIZE`. Types: `OgCardOptions` |
|
|
76
81
|
| `@supertype.ai/foundations/eslint` | `designRules` (every rule as one array, the one to spread), `designConfig` (the same set wrapped as a flat-config entry). The builders `colourRules`, `typographyRules`, `linkRules`, `themeOverrideRules`, `surfaceAsInkRules`, `renamedTokenRules` are exported too, though spreading them by hand is how a consumer ends up missing one. Types: `FlatConfigEntry`, `DesignRuleOptions`, `DesignConfigOptions`, `RestrictedSyntax`, `ColourOptions`, `TypographyOptions` |
|
|
77
82
|
| `@supertype.ai/foundations/rehype` | `rehypeProseCode`, `proseCodeOptions`, `PROSE_LANGS`, `PROSE_THEMES`. Build-time only, must not resolve React |
|
|
78
|
-
| `@supertype.ai/foundations/contrast` | `checkLegibility` (inks at 4.5:1), `checkSignals` (fills at 3:1 — status hues, the categorical earth hues and the six chart series alike — 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`: a rule is exempt from the ink and mark bars, but it still has to read as the same weight in both themes), `resolveTokens`, `formatFailures`, `specificity`, `parseColor`, `luminance`, `contrast` (WCAG ratio), `lc` (APCA lightness contrast: polarity-aware, for checking that an ink ramp is perceptually ordered rather than merely ordered by ratio), `tokenCuts` (which cuts a token ships: fill, the label printed on it, the hue as words, the taxonomy `checkSignals` measures against). Types: `Rgb`, `Theme`, `LegibilityFailure`, `TokenCuts`. Build-time only |
|
|
83
|
+
| `@supertype.ai/foundations/contrast` | `checkLegibility` (inks at 4.5:1), `checkSignals` (fills at 3:1 — status hues, the categorical earth hues and the six chart series alike — tinted inks at 4.5:1, `--subtle-foreground` at the 3:1 it is documented for, labels against their own fill: the tone rows are read off `TONE` and each cut is resolved along its `var()` fallback chain, so an app that declares `--brand` without `--brand-foreground` is measured on the label the cascade really reaches for rather than skipped), `checkHairlines` (`--border` and `--input` at 1.4:1 on `--background` and `--card`, `--sidebar-border` on `--sidebar`: a rule is exempt from the ink and mark bars, but it still has to read as the same weight in both themes), `resolveTokens`, `formatFailures`, `specificity`, `parseColor`, `luminance`, `contrast` (WCAG ratio), `lc` (APCA lightness contrast: polarity-aware, for checking that an ink ramp is perceptually ordered rather than merely ordered by ratio), `tokenCuts` (which cuts a token ships: fill, the label printed on it, the hue as words, the taxonomy `checkSignals` measures against). Types: `Rgb`, `Theme`, `LegibilityFailure`, `TokenCuts`. Build-time only |
|
|
79
84
|
|
|
80
85
|
## Props worth knowing
|
|
81
86
|
|
|
@@ -90,8 +95,12 @@ silently. Full reference: https://github.com/supertypeai/foundations
|
|
|
90
95
|
- `TypographyLink`: `href` (required), `tone?: Tone` (default `muted`), `addArrow?`, `newTab?`. The href decides internal versus external.
|
|
91
96
|
- `TypographyHighlight`: `tone?: HighlightTone`, one of `"primary" | "success" | "ochre" | "terracotta" | "sage" | "fig"`, plus `seed?: number`. A separate type from `Tone` on purpose: this axis is categorical (which one it is) where `Tone` is semantic (what it means), the same split theme.css draws between the earth swatches and the status tokens.
|
|
92
97
|
- `Card`: `href`, `title`, `description`, `icon`, `external`. An href makes the whole card a link.
|
|
98
|
+
- `Anchor`: `href` (required), plus `external`/`newTab`. An unstyled anchor for a
|
|
99
|
+
link that is not typography and not a control — a thumbnail, a chip, a tooltip
|
|
100
|
+
trigger. It exists so `target`/`rel` are never written at a call site; `external`
|
|
101
|
+
is for a same-origin path that is not a route, which the router would prefetch.
|
|
93
102
|
- `Tone` is the one 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`. Seven tones, seven tokens, one to one, which is the bar for adding one. Four names map onto others: `neutral` and `foreground` are `muted`, the word the rest of the package uses; `accent` is `--primary`'s hover tint, so a washed `primary` renders the same thing; `info` is covered by the `success | warn | destructive` triad. `brand` falls back to `--primary` in an app that defines no `--brand`.
|
|
94
|
-
- **Ink is handed down by whatever paints.** `toneClass(tone)` is a palette and sets no ink. A surface that fills adds `INK_ON_FILL`; a tinted one
|
|
103
|
+
- **Ink is handed down by whatever paints.** `toneClass(tone)` is a palette and sets no ink. A surface that fills adds `INK_ON_FILL`; a tinted one adds `INK_ON_CARD`, `INK_ON_POPOVER` or `INK_ON_SIDEBAR`, and one the package does not name spreads `inkOnSurfaceStyle(token)` into `style` rather than building a class. Both declare `--ink` and `--ink-muted`, which every type primitive reads, falling back to the page. Paint a background without them and a nested `TypographyLabel` prints `--foreground` on your fill, which measures 2.34:1 on `--primary`. On a hue fill `--ink-muted` equals `--ink`: a filled control has one ink, and wanting a second rung means wanting a tinted surface.
|
|
95
104
|
- `Button`: `variant?: "solid" | "soft" | "outline" | "ghost" | "link"` (default `solid`), `tone?: Tone` (defaults to `primary` on a solid button and `muted` on every other variant — filling a button in is how a page says this is the action), `size?: "xs" | "sm" | "md" | "lg" | "xl"` (default `md`), `icon?: boolean` for a square glyph box, `pill?: boolean` for full-round corners, `href` to make it a link, `render` for an element that is neither a button nor a link. Variant is how much ink it spends and tone is what the ink means, on separate axes, so a quiet delete is `variant="ghost" tone="destructive"`.
|
|
96
105
|
- `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
106
|
- `Callout`: `tone?: Tone`, `density?: "compact" | "editorial"`, `title`, `icon`, `action`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supertype.ai/foundations",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"./package.json": "./package.json"
|
|
83
83
|
},
|
|
84
84
|
"scripts": {
|
|
85
|
-
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/cjs-marker.mjs && node scripts/check-llms.mjs && node scripts/pins.mjs",
|
|
85
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/cjs-marker.mjs && node scripts/check-candidates.mjs && node scripts/check-llms.mjs && node scripts/pins.mjs",
|
|
86
86
|
"dev": "tsc -p tsconfig.json --watch",
|
|
87
87
|
"test": "yarn build && vitest run",
|
|
88
88
|
"test:watch": "vitest",
|