@supertype.ai/foundations 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/blocks/badge.d.ts +24 -0
- package/dist/blocks/badge.js +80 -0
- package/dist/blocks/button.d.ts +24 -0
- package/dist/blocks/button.js +117 -0
- package/dist/blocks/callout.d.ts +2 -39
- package/dist/blocks/callout.js +13 -38
- package/dist/blocks/card.js +22 -2
- package/dist/blocks/focus.d.ts +9 -0
- package/dist/blocks/focus.js +9 -0
- package/dist/blocks/index.d.ts +3 -1
- package/dist/blocks/index.js +3 -1
- package/dist/blocks/interactive-accordion.js +2 -1
- package/dist/blocks/render-as.d.ts +11 -0
- package/dist/blocks/render-as.js +22 -0
- package/dist/blocks/segment.d.ts +22 -14
- package/dist/blocks/segment.js +23 -14
- package/dist/blocks/tabs.d.ts +52 -17
- package/dist/blocks/tabs.js +86 -37
- package/dist/cjs/eslint.js +17 -8
- package/dist/contrast.d.ts +29 -0
- package/dist/contrast.js +26 -8
- package/dist/eslint.d.ts +24 -6
- package/dist/eslint.js +16 -8
- package/dist/essay/essay.js +14 -6
- package/dist/essay/index.d.ts +1 -1
- package/dist/essay/index.js +1 -1
- package/dist/essay/layout.d.ts +36 -0
- package/dist/essay/layout.js +32 -0
- package/dist/essay/reading.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/mdx.d.ts +26 -4
- package/dist/mdx.js +30 -4
- package/dist/seo.d.ts +4 -2
- package/dist/seo.js +14 -5
- package/dist/tone.d.ts +101 -0
- package/dist/tone.js +111 -0
- package/dist/typography/header.d.ts +30 -0
- package/dist/typography/header.js +7 -2
- package/dist/typography/highlight.d.ts +4 -4
- package/dist/typography/highlight.js +4 -4
- package/dist/typography/paragraph.d.ts +3 -13
- package/dist/typography/paragraph.js +13 -9
- package/llms.txt +34 -12
- package/package.json +2 -1
- package/src/theme.css +70 -26
package/dist/blocks/segment.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FOCUS_RING } from "./focus.js";
|
|
1
2
|
/**
|
|
2
3
|
* The segmented picker, as one set of surfaces.
|
|
3
4
|
*
|
|
@@ -9,29 +10,37 @@
|
|
|
9
10
|
* class strings, which is exactly the kind of duplication that drifts the first time
|
|
10
11
|
* someone retunes the palette.
|
|
11
12
|
*
|
|
12
|
-
* Some entries appear twice, once bare and once `data-active:`-prefixed. That is not
|
|
13
|
-
* laziness: Tailwind generates a utility only if it appears literally in scanned source, so
|
|
14
|
-
* prefixing at runtime would compile to classes that never got built. Writing both forms
|
|
15
|
-
* side by side is the honest version, and it keeps the pair impossible to change by half.
|
|
16
|
-
*
|
|
17
13
|
* The active segment is deliberately flat — a card surface and a hairline, no shadow.
|
|
18
14
|
* Elevation in this system means "this layer left the page plane" (see --elevation-* in
|
|
19
15
|
* theme.css), and a segment sitting inside its own track has not.
|
|
16
|
+
*
|
|
17
|
+
* The other half of that: the segment stays on the page plane because the RAIL drops below
|
|
18
|
+
* it. The well is the shadow and the hairline, not a fill — the track is `bg-background`
|
|
19
|
+
* pressed in. A `--muted` rail cannot work, because `--muted` sits below `--card` in light
|
|
20
|
+
* and above it in dark, so a muted fill reads correct in one theme and inverted in the
|
|
21
|
+
* other, and `dark:` is what the package's own ESLint rule exists to stop.
|
|
20
22
|
*/
|
|
21
23
|
export const SEGMENT = {
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The rail a set of segments sits in. Surface only; each component owns its layout.
|
|
26
|
+
*
|
|
27
|
+
* `rounded-md` over `activeSurface`'s `rounded-sm` is the 2px of `p-0.5`: concentric
|
|
28
|
+
* radii, so the segment's corner runs parallel to the rail's rather than across it.
|
|
29
|
+
*/
|
|
30
|
+
track: "rounded-md border border-border bg-background p-0.5 shadow-recessed",
|
|
24
31
|
/** Affordances every segment shares, whatever its shape or engine. */
|
|
25
|
-
item:
|
|
32
|
+
item: `relative inline-flex items-center gap-1.5 font-medium transition-colors ${FOCUS_RING}`,
|
|
26
33
|
/** Selected: the ink lifts to full strength. */
|
|
27
34
|
active: "text-foreground",
|
|
28
35
|
/**
|
|
29
36
|
* Unselected: quiet, but lighting its own surface on hover so the whole strip reads as
|
|
30
|
-
* reachable rather than only the segment already chosen.
|
|
37
|
+
* reachable rather than only the segment already chosen. It moves toward `activeSurface`
|
|
38
|
+
* and not toward the rail, so a hover previews being picked.
|
|
39
|
+
*/
|
|
40
|
+
idle: "text-muted-foreground hover:bg-card/60 hover:text-foreground",
|
|
41
|
+
/**
|
|
42
|
+
* The flat surface marking the selection. One string, worn by both engines: the marketing
|
|
43
|
+
* picker slides it with `motion`, `Tabs` hands it to the element Base UI positions.
|
|
31
44
|
*/
|
|
32
|
-
|
|
33
|
-
/** The flat surface marking the selection. */
|
|
34
|
-
activeSurface: "bg-card ring-1 ring-border",
|
|
35
|
-
/** `activeSurface`, for an engine that marks its own trigger with `data-active`. */
|
|
36
|
-
dataActiveSurface: "data-active:bg-card data-active:text-foreground data-active:ring-1 data-active:ring-border",
|
|
45
|
+
activeSurface: "rounded-sm bg-card ring-1 ring-border",
|
|
37
46
|
};
|
package/dist/blocks/tabs.d.ts
CHANGED
|
@@ -1,32 +1,67 @@
|
|
|
1
1
|
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { type Tone } from "../tone.js";
|
|
4
5
|
declare function Tabs({ className, orientation, ...props }: TabsPrimitive.Root.Props): import("react").JSX.Element;
|
|
6
|
+
/**
|
|
7
|
+
* Surfaces come from SEGMENT, so this and marketing/segmented-control cannot drift: a
|
|
8
|
+
* reader who meets the picker on a docs page and again on the usage dashboard should not
|
|
9
|
+
* have to learn it twice. Layout stays local, since only this one has orientation to serve.
|
|
10
|
+
*
|
|
11
|
+
* Each variant states its own box, rather than sharing a base tuned for the boxed track
|
|
12
|
+
* that `line` then had to undo at the call site.
|
|
13
|
+
*/
|
|
5
14
|
declare const tabsListVariants: (props?: ({
|
|
6
15
|
variant?: "line" | "default" | null | undefined;
|
|
7
16
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
-
|
|
17
|
+
/**
|
|
18
|
+
* `tone` inks the marker, and only the marker. On `line` that is the underline and the
|
|
19
|
+
* active tab's icon; the boxed track's marker is a card surface and a hairline, which
|
|
20
|
+
* SEGMENT keeps deliberately flat, so a tone there would be a colour with nothing to
|
|
21
|
+
* paint. The label stays `--foreground` in both: it is read, not signalled.
|
|
22
|
+
*/
|
|
23
|
+
declare function TabsList({ className, variant, tone, children, ...props }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants> & {
|
|
24
|
+
tone?: Tone;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
9
26
|
declare function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props): import("react").JSX.Element;
|
|
10
27
|
declare function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props): import("react").JSX.Element;
|
|
28
|
+
/** One tab, whole: what it is called, what marks it, and what it shows. */
|
|
29
|
+
export type TabItem = {
|
|
30
|
+
/** Stable across a relabel — it is what `defaultValue` and `onValueChange` speak. */
|
|
31
|
+
value: string;
|
|
32
|
+
label: ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* An element — `<Icons.Mic />`, `<PriceChip />` — sized and inked by the trigger.
|
|
35
|
+
*
|
|
36
|
+
* An element and not a component, which this briefly also took. `TabGroup` is a client
|
|
37
|
+
* component, so a component reference handed to it from a server page is a function
|
|
38
|
+
* crossing the RSC boundary, which React refuses at render; an element is already
|
|
39
|
+
* rendered and crosses fine. One accepted shape also spares the slot a branch, and it
|
|
40
|
+
* is the shape `Card`'s `icon` has always taken.
|
|
41
|
+
*/
|
|
42
|
+
icon?: ReactNode;
|
|
43
|
+
content: ReactNode;
|
|
44
|
+
};
|
|
11
45
|
/**
|
|
12
|
-
* The declarative shorthand:
|
|
13
|
-
* `
|
|
14
|
-
* the
|
|
46
|
+
* The declarative shorthand: the tabs as data. `TabGroup` is to `Tabs` what
|
|
47
|
+
* `DisclosureGroup` is to `Accordion`, and it is the shape to reach for — an app that
|
|
48
|
+
* rebuilds it over the primitives ends up re-adding the icon, the change handler and the
|
|
49
|
+
* stable value by hand.
|
|
15
50
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* below the adapter is the same component the product surfaces use, so a tab
|
|
20
|
-
* strip in the docs and one on a dashboard behave identically.
|
|
51
|
+
* Everything below the adapter is the same component the product surfaces use, so a tab
|
|
52
|
+
* strip in the docs and one on a dashboard behave identically. The positional
|
|
53
|
+
* `items`-plus-children shape lives in the MDX map, the only thing that speaks it.
|
|
21
54
|
*/
|
|
22
|
-
export declare function TabGroup({
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** `value` names the panel at the call site; it is not used for matching. */
|
|
28
|
-
export declare function Tab({ children }: {
|
|
55
|
+
export declare function TabGroup({ tabs, defaultValue, value, onValueChange, variant, tone, iconPosition, className, }: {
|
|
56
|
+
tabs: readonly TabItem[];
|
|
57
|
+
/** Defaults to the first tab, since a picker with nothing picked is not a state. */
|
|
58
|
+
defaultValue?: string;
|
|
59
|
+
/** Pass with `onValueChange` to drive it from outside. */
|
|
29
60
|
value?: string;
|
|
30
|
-
|
|
61
|
+
onValueChange?: (value: string) => void;
|
|
62
|
+
variant?: VariantProps<typeof tabsListVariants>["variant"];
|
|
63
|
+
tone?: Tone;
|
|
64
|
+
iconPosition?: "inline-start" | "inline-end";
|
|
65
|
+
className?: string;
|
|
31
66
|
}): import("react").JSX.Element;
|
|
32
67
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
package/dist/blocks/tabs.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { cn } from "../cn.js";
|
|
6
|
+
import { toneClass } from "../tone.js";
|
|
6
7
|
import { SEGMENT } from "./segment.js";
|
|
7
8
|
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
8
9
|
return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, className: cn(
|
|
@@ -12,58 +13,106 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
|
12
13
|
// flex container and laid its panels out beside the tab strip.
|
|
13
14
|
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className), ...props }));
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Surfaces come from SEGMENT, so this and marketing/segmented-control cannot drift: a
|
|
18
|
+
* reader who meets the picker on a docs page and again on the usage dashboard should not
|
|
19
|
+
* have to learn it twice. Layout stays local, since only this one has orientation to serve.
|
|
20
|
+
*
|
|
21
|
+
* Each variant states its own box, rather than sharing a base tuned for the boxed track
|
|
22
|
+
* that `line` then had to undo at the call site.
|
|
23
|
+
*/
|
|
24
|
+
const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center text-muted-foreground", {
|
|
19
25
|
variants: {
|
|
20
26
|
variant: {
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
/** The boxed track: one fixed-height rail, segments splitting it evenly. */
|
|
28
|
+
default: cn(SEGMENT.track, "h-8 justify-center"),
|
|
29
|
+
/**
|
|
30
|
+
* A strip of labels over a rule. Free to wrap, so no fixed height — and `pb-2` is
|
|
31
|
+
* the marker's own room (6px offset plus its 2px), so the list's box contains
|
|
32
|
+
* everything the list draws and the gaps below measure from the right edge. The
|
|
33
|
+
* row gap clears the same 8px, or a wrapped row wears the rule above it.
|
|
34
|
+
*/
|
|
35
|
+
line: "flex-wrap justify-start gap-x-1 gap-y-3 rounded-none border-0 bg-transparent pb-2",
|
|
23
36
|
},
|
|
24
37
|
},
|
|
25
38
|
defaultVariants: {
|
|
26
39
|
variant: "default",
|
|
27
40
|
},
|
|
28
41
|
});
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The marker: one element, positioned by Base UI from `--active-tab-left/top/width/height`
|
|
44
|
+
* on the list, dressed by the variant it is in. It outlives the selection, so it carries
|
|
45
|
+
* the answer from the old tab to the new instead of being destroyed and rebuilt.
|
|
46
|
+
*
|
|
47
|
+
* `data-activation-direction` is `none` before anything is picked, and that is the one case
|
|
48
|
+
* that must not animate — without the guard every strip on the page slides in from its left
|
|
49
|
+
* edge on hydration.
|
|
50
|
+
*/
|
|
51
|
+
const tabsIndicatorVariants = cva("pointer-events-none absolute left-0 top-0 w-(--active-tab-width) transition-[translate,width] duration-200 ease-out data-[activation-direction=none]:transition-none motion-reduce:transition-none", {
|
|
52
|
+
variants: {
|
|
53
|
+
variant: {
|
|
54
|
+
/** The pill, on the page plane. The rail below it is what carries the depth. */
|
|
55
|
+
default: cn(SEGMENT.activeSurface, "h-(--active-tab-height) translate-x-(--active-tab-left) translate-y-(--active-tab-top)"),
|
|
56
|
+
/**
|
|
57
|
+
* A rule under the label, clear of the descenders, in the list's tone. The 6px
|
|
58
|
+
* offset plus its own 2px is the `pb-2` the line list reserves — the two are one
|
|
59
|
+
* measurement, and changing either alone puts the rule back outside its box.
|
|
60
|
+
*/
|
|
61
|
+
line: "h-0.5 translate-x-(--active-tab-left) translate-y-[calc(var(--active-tab-top)+var(--active-tab-height)+6px)] rounded-full bg-(--tone-hue)",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: { variant: "default" },
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* `tone` inks the marker, and only the marker. On `line` that is the underline and the
|
|
68
|
+
* active tab's icon; the boxed track's marker is a card surface and a hairline, which
|
|
69
|
+
* SEGMENT keeps deliberately flat, so a tone there would be a colour with nothing to
|
|
70
|
+
* paint. The label stays `--foreground` in both: it is read, not signalled.
|
|
71
|
+
*/
|
|
72
|
+
function TabsList({ className, variant = "default", tone = "primary", children, ...props }) {
|
|
73
|
+
return (_jsxs(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn("relative", toneClass(tone), tabsListVariants({ variant }), className), ...props, children: [_jsx(TabsPrimitive.Indicator, { renderBeforeHydration: true, className: tabsIndicatorVariants({ variant }) }), children] }));
|
|
31
74
|
}
|
|
32
75
|
function TabsTrigger({ className, ...props }) {
|
|
33
|
-
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn(SEGMENT.item,
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
"
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// The
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
"
|
|
76
|
+
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn(SEGMENT.item,
|
|
77
|
+
// Ink only — the surface and the underline belong to the indicator. The radius is
|
|
78
|
+
// for the hover wash, and matches the marker that wash previews.
|
|
79
|
+
"rounded-sm text-muted-foreground hover:text-foreground data-active:text-foreground", "px-1.5 py-0.5 text-sm whitespace-nowrap disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
80
|
+
// An icon sits inside the label's gap, so the padding on that side comes off.
|
|
81
|
+
// `TabGroup` writes the `data-icon` these two read.
|
|
82
|
+
"has-data-[icon=inline-start]:pl-1 has-data-[icon=inline-end]:pr-1",
|
|
83
|
+
// Filling the track is the boxed variant's business; a line tab is as wide as its
|
|
84
|
+
// label. The variant is read off the list's `data-variant` rather than taken as a
|
|
85
|
+
// prop, so a caller states it once on `TabsList` and not on every trigger.
|
|
86
|
+
// Selectors are spelled out, never interpolated: Tailwind's scanner cannot see an
|
|
87
|
+
// interpolated class and would compile nothing.
|
|
88
|
+
"group-data-[variant=default]/tabs-list:h-full group-data-[variant=default]/tabs-list:flex-1 group-data-[variant=default]/tabs-list:justify-center",
|
|
89
|
+
// Hover moves toward the marker's surface, so it previews the selection.
|
|
90
|
+
"group-data-[variant=default]/tabs-list:not-data-active:hover:bg-card/60",
|
|
91
|
+
// The active icon takes the ink the marker is drawn in.
|
|
92
|
+
"group-data-[variant=line]/tabs-list:data-active:[&_[data-icon]]:text-(color:--tone-hue)", className), ...props }));
|
|
46
93
|
}
|
|
47
94
|
function TabsContent({ className, ...props }) {
|
|
48
95
|
return (_jsx(TabsPrimitive.Panel, { "data-slot": "tabs-content", className: cn("flex-1 text-sm outline-none", className), ...props }));
|
|
49
96
|
}
|
|
97
|
+
/** `data-icon` is the hook the trigger's padding and tone selectors read. */
|
|
98
|
+
function TabIconSlot({ icon, position, }) {
|
|
99
|
+
return (_jsx("span", { "data-icon": position, className: "flex items-center transition-colors [&_svg]:size-4 [&_svg]:shrink-0", children: icon }));
|
|
100
|
+
}
|
|
50
101
|
/**
|
|
51
|
-
* The declarative shorthand:
|
|
52
|
-
* `
|
|
53
|
-
* the
|
|
102
|
+
* The declarative shorthand: the tabs as data. `TabGroup` is to `Tabs` what
|
|
103
|
+
* `DisclosureGroup` is to `Accordion`, and it is the shape to reach for — an app that
|
|
104
|
+
* rebuilds it over the primitives ends up re-adding the icon, the change handler and the
|
|
105
|
+
* stable value by hand.
|
|
54
106
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* below the adapter is the same component the product surfaces use, so a tab
|
|
59
|
-
* strip in the docs and one on a dashboard behave identically.
|
|
107
|
+
* Everything below the adapter is the same component the product surfaces use, so a tab
|
|
108
|
+
* strip in the docs and one on a dashboard behave identically. The positional
|
|
109
|
+
* `items`-plus-children shape lives in the MDX map, the only thing that speaks it.
|
|
60
110
|
*/
|
|
61
|
-
export function TabGroup({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return _jsx(_Fragment, { children: children });
|
|
111
|
+
export function TabGroup({ tabs, defaultValue, value, onValueChange, variant, tone, iconPosition = "inline-start", className, }) {
|
|
112
|
+
return (
|
|
113
|
+
// The handler is adapted rather than wrapped when absent: an arrow declared
|
|
114
|
+
// unconditionally is a function crossing the server boundary on every page that
|
|
115
|
+
// renders tabs without one.
|
|
116
|
+
_jsxs(Tabs, { defaultValue: defaultValue ?? tabs[0]?.value, value: value, onValueChange: onValueChange && ((next) => onValueChange(String(next))), className: cn("my-6", className), children: [_jsx(TabsList, { variant: variant, tone: tone, children: tabs.map(({ value: tabValue, label, icon }) => (_jsxs(TabsTrigger, { value: tabValue, children: [icon && iconPosition === "inline-start" && (_jsx(TabIconSlot, { icon: icon, position: "inline-start" })), label, icon && iconPosition === "inline-end" && (_jsx(TabIconSlot, { icon: icon, position: "inline-end" }))] }, tabValue))) }), tabs.map(({ value: tabValue, content }) => (_jsx(TabsContent, { value: tabValue, className: "pt-2 text-muted-foreground", children: content }, tabValue)))] }));
|
|
68
117
|
}
|
|
69
118
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
package/dist/cjs/eslint.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.themeOverrideRules = themeOverrideRules;
|
|
|
10
10
|
exports.surfaceAsInkRules = surfaceAsInkRules;
|
|
11
11
|
exports.renamedTokenRules = renamedTokenRules;
|
|
12
12
|
exports.typographyRules = typographyRules;
|
|
13
|
+
exports.designRules = designRules;
|
|
13
14
|
exports.designConfig = designConfig;
|
|
14
15
|
/** A className written as a plain string, or as a chunk of a template literal. */
|
|
15
16
|
const classString = (pattern) => [
|
|
@@ -55,10 +56,13 @@ function surfaceAsInkRules() {
|
|
|
55
56
|
}
|
|
56
57
|
/**
|
|
57
58
|
* `-foreground` means the label printed on a fill; `-ink` means the hue as
|
|
58
|
-
* words. `warn-foreground`
|
|
59
|
+
* words. `warn-foreground` and the eight categorical `-foreground` tokens were
|
|
60
|
+
* always inks, under the other name. The old spellings still resolve, so nothing
|
|
61
|
+
* breaks on the day of the rename; this is what stops them surviving it.
|
|
59
62
|
*/
|
|
63
|
+
const RENAMED_INKS = "warn|terracotta|ochre|moss|fern|sage|stone|fig|cocoa";
|
|
60
64
|
function renamedTokenRules() {
|
|
61
|
-
return rule(
|
|
65
|
+
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`.");
|
|
62
66
|
}
|
|
63
67
|
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, } = {}) {
|
|
64
68
|
return [
|
|
@@ -114,6 +118,15 @@ function typographyRules({ weights = false, ramp = "text-3xs 10 / text-2xs 11 /
|
|
|
114
118
|
: []),
|
|
115
119
|
];
|
|
116
120
|
}
|
|
121
|
+
function designRules({ accents, typography = true, ...type } = {}) {
|
|
122
|
+
return [
|
|
123
|
+
...colourRules({ accents }),
|
|
124
|
+
...(typography ? typographyRules(type) : []),
|
|
125
|
+
...themeOverrideRules(),
|
|
126
|
+
...surfaceAsInkRules(),
|
|
127
|
+
...renamedTokenRules(),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
117
130
|
/**
|
|
118
131
|
* Every rule in one flat-config entry, ready to spread into eslint.config.js:
|
|
119
132
|
*
|
|
@@ -126,7 +139,7 @@ function typographyRules({ weights = false, ramp = "text-3xs 10 / text-2xs 11 /
|
|
|
126
139
|
* half the set by accident. If you need a second scope, call this again with a
|
|
127
140
|
* different `files` and no overlap.
|
|
128
141
|
*/
|
|
129
|
-
function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"],
|
|
142
|
+
function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"], ...options } = {}) {
|
|
130
143
|
return [
|
|
131
144
|
{
|
|
132
145
|
name: "@supertype.ai/foundations/design",
|
|
@@ -134,11 +147,7 @@ function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"], accents, weights, ramp
|
|
|
134
147
|
rules: {
|
|
135
148
|
"no-restricted-syntax": [
|
|
136
149
|
"error",
|
|
137
|
-
...
|
|
138
|
-
...typographyRules({ weights, ramp, pairing, axis }),
|
|
139
|
-
...themeOverrideRules(),
|
|
140
|
-
...surfaceAsInkRules(),
|
|
141
|
-
...renamedTokenRules(),
|
|
150
|
+
...designRules(options),
|
|
142
151
|
],
|
|
143
152
|
},
|
|
144
153
|
},
|
package/dist/contrast.d.ts
CHANGED
|
@@ -36,6 +36,35 @@ export declare function checkLegibility(css: string, { minimum, inks, surfaces,
|
|
|
36
36
|
surfaces?: string[] | undefined;
|
|
37
37
|
themes?: Theme[] | undefined;
|
|
38
38
|
}): LegibilityFailure[];
|
|
39
|
+
/** What a token is: a surface or mark, a label printed on it, a hue used as words. */
|
|
40
|
+
export interface TokenCuts {
|
|
41
|
+
/** The role itself — a surface, or a mark held to 3:1 against the page. */
|
|
42
|
+
fill: string;
|
|
43
|
+
/**
|
|
44
|
+
* The label printed *on* that fill. The only pair measured one against the
|
|
45
|
+
* other rather than against the page, which is why it is the only pair a
|
|
46
|
+
* swatch may print one on top of the other.
|
|
47
|
+
*/
|
|
48
|
+
onFill?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The same hue used as words, held to 4.5:1 against the page and never printed
|
|
51
|
+
* on the fill — there it measures about 1.2:1. Always `-ink`; the categorical
|
|
52
|
+
* hues shipped under `-foreground` until that was corrected, and the old names
|
|
53
|
+
* survive only as aliases the ESLint rules flag.
|
|
54
|
+
*/
|
|
55
|
+
asInk?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The cuts a token ships, read off the same three sets `checkSignals` measures.
|
|
59
|
+
*
|
|
60
|
+
* Exported because the alternative is every consumer keeping its own idea of
|
|
61
|
+
* which tokens are pairs — the docs site did, and got the categorical hues
|
|
62
|
+
* wrong, rendering `--ochre` as a lone square while its ink, the colour the
|
|
63
|
+
* marker highlight is painted with, appeared nowhere. A palette checked against
|
|
64
|
+
* one taxonomy and documented from another will drift, and the drift shows up as
|
|
65
|
+
* a page that is quietly wrong rather than a build that fails.
|
|
66
|
+
*/
|
|
67
|
+
export declare function tokenCuts(token: string): TokenCuts;
|
|
39
68
|
/**
|
|
40
69
|
* The three bars a palette owes, run over the same engine as `checkLegibility`.
|
|
41
70
|
* Without this the numbers in a theme's comments are claims, not measurements.
|
package/dist/contrast.js
CHANGED
|
@@ -214,14 +214,14 @@ const INKS_TINTED = [
|
|
|
214
214
|
"--info-ink",
|
|
215
215
|
"--danger",
|
|
216
216
|
"--secondary-ink",
|
|
217
|
-
"--terracotta-
|
|
218
|
-
"--ochre-
|
|
219
|
-
"--moss-
|
|
220
|
-
"--fern-
|
|
221
|
-
"--sage-
|
|
222
|
-
"--stone-
|
|
223
|
-
"--fig-
|
|
224
|
-
"--cocoa-
|
|
217
|
+
"--terracotta-ink",
|
|
218
|
+
"--ochre-ink",
|
|
219
|
+
"--moss-ink",
|
|
220
|
+
"--fern-ink",
|
|
221
|
+
"--sage-ink",
|
|
222
|
+
"--stone-ink",
|
|
223
|
+
"--fig-ink",
|
|
224
|
+
"--cocoa-ink",
|
|
225
225
|
];
|
|
226
226
|
/**
|
|
227
227
|
* shadcn's shape: `-foreground` is the label printed on the fill, so the pair is
|
|
@@ -236,6 +236,24 @@ const ON_FILL = [
|
|
|
236
236
|
["--popover", "--popover-foreground"],
|
|
237
237
|
["--sidebar", "--sidebar-foreground"],
|
|
238
238
|
];
|
|
239
|
+
/**
|
|
240
|
+
* The cuts a token ships, read off the same three sets `checkSignals` measures.
|
|
241
|
+
*
|
|
242
|
+
* Exported because the alternative is every consumer keeping its own idea of
|
|
243
|
+
* which tokens are pairs — the docs site did, and got the categorical hues
|
|
244
|
+
* wrong, rendering `--ochre` as a lone square while its ink, the colour the
|
|
245
|
+
* marker highlight is painted with, appeared nowhere. A palette checked against
|
|
246
|
+
* one taxonomy and documented from another will drift, and the drift shows up as
|
|
247
|
+
* a page that is quietly wrong rather than a build that fails.
|
|
248
|
+
*/
|
|
249
|
+
export function tokenCuts(token) {
|
|
250
|
+
const fill = token.startsWith("--") ? token : `--${token}`;
|
|
251
|
+
return {
|
|
252
|
+
fill,
|
|
253
|
+
onFill: ON_FILL.find(([surface]) => surface === fill)?.[1],
|
|
254
|
+
asInk: INKS_TINTED.find((ink) => ink === `${fill}-ink`),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
239
257
|
/**
|
|
240
258
|
* The three bars a palette owes, run over the same engine as `checkLegibility`.
|
|
241
259
|
* Without this the numbers in a theme's comments are claims, not measurements.
|
package/dist/eslint.d.ts
CHANGED
|
@@ -24,10 +24,6 @@ export declare function themeOverrideRules(): RestrictedSyntax[];
|
|
|
24
24
|
* shipped at 17 sites. `text-background` is absent: inverse ink is a real role.
|
|
25
25
|
*/
|
|
26
26
|
export declare function surfaceAsInkRules(): RestrictedSyntax[];
|
|
27
|
-
/**
|
|
28
|
-
* `-foreground` means the label printed on a fill; `-ink` means the hue as
|
|
29
|
-
* words. `warn-foreground` was always the ink, under the other name.
|
|
30
|
-
*/
|
|
31
27
|
export declare function renamedTokenRules(): RestrictedSyntax[];
|
|
32
28
|
export interface TypographyOptions {
|
|
33
29
|
/** Three-weight ramp. Off for editorial, where 700 is a register not a shout. */
|
|
@@ -49,13 +45,35 @@ export interface TypographyOptions {
|
|
|
49
45
|
axis?: boolean;
|
|
50
46
|
}
|
|
51
47
|
export declare function typographyRules({ weights, ramp, pairing, axis, }?: TypographyOptions): RestrictedSyntax[];
|
|
48
|
+
/**
|
|
49
|
+
* Every design rule, as one list.
|
|
50
|
+
*
|
|
51
|
+
* The five builders below it are still exported, and spreading them by hand is
|
|
52
|
+
* what both consumers were doing — one of them into a flat config, the other
|
|
53
|
+
* into a legacy `.eslintrc`, and *both* of them had quietly left out
|
|
54
|
+
* `renamedTokenRules`, so neither would have flagged a deprecated token name.
|
|
55
|
+
* That is not a mistake either author made; it is what a five-name API costs
|
|
56
|
+
* every time somebody wires it up. Spread this instead, and a rule added here
|
|
57
|
+
* arrives in both apps on their next bump.
|
|
58
|
+
*/
|
|
59
|
+
export interface DesignRuleOptions extends ColourOptions, TypographyOptions {
|
|
60
|
+
/**
|
|
61
|
+
* Off for a surface that sets its own type ramp — a marketing page under
|
|
62
|
+
* `.editorial`, a mockup drawing the product at reduced scale. Everything
|
|
63
|
+
* about colour still applies: a deprecated token name is wrong on every
|
|
64
|
+
* surface, which is why this is a flag rather than an invitation to pick
|
|
65
|
+
* three of the five builders by hand.
|
|
66
|
+
*/
|
|
67
|
+
typography?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export declare function designRules({ accents, typography, ...type }?: DesignRuleOptions): RestrictedSyntax[];
|
|
52
70
|
/** A flat-config entry, described structurally so the package needs no ESLint dependency. */
|
|
53
71
|
export interface FlatConfigEntry {
|
|
54
72
|
name: string;
|
|
55
73
|
files: string[];
|
|
56
74
|
rules: Record<string, unknown>;
|
|
57
75
|
}
|
|
58
|
-
export interface DesignConfigOptions extends
|
|
76
|
+
export interface DesignConfigOptions extends DesignRuleOptions {
|
|
59
77
|
/** What the rules apply to. Narrow it to exclude generated or vendored code. */
|
|
60
78
|
files?: string[];
|
|
61
79
|
}
|
|
@@ -71,4 +89,4 @@ export interface DesignConfigOptions extends ColourOptions, TypographyOptions {
|
|
|
71
89
|
* half the set by accident. If you need a second scope, call this again with a
|
|
72
90
|
* different `files` and no overlap.
|
|
73
91
|
*/
|
|
74
|
-
export declare function designConfig({ files,
|
|
92
|
+
export declare function designConfig({ files, ...options }?: DesignConfigOptions): FlatConfigEntry[];
|
package/dist/eslint.js
CHANGED
|
@@ -47,10 +47,13 @@ export function surfaceAsInkRules() {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* `-foreground` means the label printed on a fill; `-ink` means the hue as
|
|
50
|
-
* words. `warn-foreground`
|
|
50
|
+
* words. `warn-foreground` and the eight categorical `-foreground` tokens were
|
|
51
|
+
* always inks, under the other name. The old spellings still resolve, so nothing
|
|
52
|
+
* breaks on the day of the rename; this is what stops them surviving it.
|
|
51
53
|
*/
|
|
54
|
+
const RENAMED_INKS = "warn|terracotta|ochre|moss|fern|sage|stone|fig|cocoa";
|
|
52
55
|
export function renamedTokenRules() {
|
|
53
|
-
return rule(
|
|
56
|
+
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`.");
|
|
54
57
|
}
|
|
55
58
|
export 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, } = {}) {
|
|
56
59
|
return [
|
|
@@ -106,6 +109,15 @@ export function typographyRules({ weights = false, ramp = "text-3xs 10 / text-2x
|
|
|
106
109
|
: []),
|
|
107
110
|
];
|
|
108
111
|
}
|
|
112
|
+
export function designRules({ accents, typography = true, ...type } = {}) {
|
|
113
|
+
return [
|
|
114
|
+
...colourRules({ accents }),
|
|
115
|
+
...(typography ? typographyRules(type) : []),
|
|
116
|
+
...themeOverrideRules(),
|
|
117
|
+
...surfaceAsInkRules(),
|
|
118
|
+
...renamedTokenRules(),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
109
121
|
/**
|
|
110
122
|
* Every rule in one flat-config entry, ready to spread into eslint.config.js:
|
|
111
123
|
*
|
|
@@ -118,7 +130,7 @@ export function typographyRules({ weights = false, ramp = "text-3xs 10 / text-2x
|
|
|
118
130
|
* half the set by accident. If you need a second scope, call this again with a
|
|
119
131
|
* different `files` and no overlap.
|
|
120
132
|
*/
|
|
121
|
-
export function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"],
|
|
133
|
+
export function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"], ...options } = {}) {
|
|
122
134
|
return [
|
|
123
135
|
{
|
|
124
136
|
name: "@supertype.ai/foundations/design",
|
|
@@ -126,11 +138,7 @@ export function designConfig({ files = ["**/*.{ts,tsx,js,jsx}"], accents, weight
|
|
|
126
138
|
rules: {
|
|
127
139
|
"no-restricted-syntax": [
|
|
128
140
|
"error",
|
|
129
|
-
...
|
|
130
|
-
...typographyRules({ weights, ramp, pairing, axis }),
|
|
131
|
-
...themeOverrideRules(),
|
|
132
|
-
...surfaceAsInkRules(),
|
|
133
|
-
...renamedTokenRules(),
|
|
141
|
+
...designRules(options),
|
|
134
142
|
],
|
|
135
143
|
},
|
|
136
144
|
},
|
package/dist/essay/essay.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../cn.js";
|
|
3
|
-
import {
|
|
3
|
+
import { headingFace, TypographyEyebrow, TypographyH1, TypographyH2, TypographyH3, } from "../typography/header.js";
|
|
4
4
|
import { TypographyCaption, TypographyMuted, TypographyProse, } from "../typography/paragraph.js";
|
|
5
|
-
import { EssayColumns } from "./layout.js";
|
|
5
|
+
import { EssayAside, EssayBody, EssayColumns, PostMetaRow } from "./layout.js";
|
|
6
6
|
import { TableOfContents } from "./contents.js";
|
|
7
7
|
/** Pass-through: keeps the className the shell relies on for layout. */
|
|
8
8
|
const PlainReveal = ({ children, className, }) => _jsx("div", { className: className, children: children });
|
|
@@ -31,19 +31,27 @@ const anchorIds = (sections) => {
|
|
|
31
31
|
export function createEssay({ Reveal = PlainReveal, Glow = NoGlow, } = {}) {
|
|
32
32
|
/** Left aligned: the eye has to reach the first line of prose either way. */
|
|
33
33
|
function EssayHeader({ eyebrow, title, lede, byline, }) {
|
|
34
|
-
return (_jsxs("header", { className: "relative overflow-hidden pb-12 pt-16 sm:pt-24", children: [_jsx(Glow, { className: "-top-40 left-1/2 -translate-x-1/2", intensity: 0.16 }), _jsx(EssayColumns, { children: _jsxs(Reveal, { eager: true, className: "flex flex-col gap-6", children: [_jsx(TypographyEyebrow, { children: eyebrow }), _jsx(TypographyH1, { variant: "display", className: "text-balance", children: title }), lede && (_jsx(TypographyMuted, { className: "text-pretty text-xl leading-relaxed", children: lede })), byline &&
|
|
34
|
+
return (_jsxs("header", { className: "relative overflow-hidden pb-12 pt-16 sm:pt-24", children: [_jsx(Glow, { className: "-top-40 left-1/2 -translate-x-1/2", intensity: 0.16 }), _jsx(EssayColumns, { children: _jsxs(Reveal, { eager: true, className: "flex flex-col gap-6", children: [_jsx(TypographyEyebrow, { children: eyebrow }), _jsx(TypographyH1, { variant: "display", className: "text-balance", children: title }), lede && (_jsx(TypographyMuted, { className: "text-pretty text-xl leading-relaxed", children: lede })), byline && _jsx(PostMetaRow, { children: byline })] }) })] }));
|
|
35
35
|
}
|
|
36
36
|
/** The reading column, with the sticky index sitting in its left margin. */
|
|
37
37
|
function EssayLayout({ index, children, }) {
|
|
38
|
-
return (_jsx(EssayColumns, { className: "pb-16 sm:pb-24", aside: _jsx(
|
|
38
|
+
return (_jsx(EssayColumns, { className: "pb-16 sm:pb-24", aside: _jsx(EssayAside, { children: _jsx(TableOfContents, { sections: index }) }), children: _jsx(EssayBody, { className: "flex flex-col gap-16", children: children }) }));
|
|
39
39
|
}
|
|
40
40
|
/** The heading carries the anchor, offset so it lands under the sticky nav. */
|
|
41
41
|
function EssaySection({ id, heading, children, }) {
|
|
42
42
|
return (_jsx("section", { id: id, className: "scroll-mt-24", children: _jsxs(Reveal, { className: "flex flex-col gap-5", children: [_jsx(TypographyH2, { className: "text-balance", children: heading }), children] }) }));
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* One per essay: a page with three of them has decided nothing.
|
|
46
|
+
*
|
|
47
|
+
* The face and a rung, not the whole `headingClass()` ramp — see `headingFace` in
|
|
48
|
+
* typography/header.tsx for why a non-heading takes one and not the other. `text-pretty`
|
|
49
|
+
* over `text-balance`: this is prose set large, so it fills the measure rather than being
|
|
50
|
+
* set in even lines like a headline. The rung is the section heading's, so one pull quote
|
|
51
|
+
* stands level with them and retunes with them under `.editorial`.
|
|
52
|
+
*/
|
|
45
53
|
function EssayPullQuote({ children }) {
|
|
46
|
-
return (_jsx(Reveal, { children: _jsx("blockquote", { className: cn(
|
|
54
|
+
return (_jsx(Reveal, { children: _jsx("blockquote", { className: cn(headingFace, "text-h2 leading-snug text-foreground text-pretty", "border-l-2 border-primary/40 py-1 pl-6"), children: children }) }));
|
|
47
55
|
}
|
|
48
56
|
/** Generic on purpose: a page hands it anything; this decides only the fit. */
|
|
49
57
|
function EssayFigure({ children, caption, }) {
|
package/dist/essay/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export { TableOfContents } from "./contents.js";
|
|
|
4
4
|
export { createEssay, EssayHeader, EssayLayout, EssaySection, EssayPullQuote, EssayFigure, EssayMovements, EssayDocument, type EssayDecorations, type EssayIndexEntry, type EssayDocSection, type EssayMovement, } from "./essay.js";
|
|
5
5
|
export { useReadingProgress, useScrollSpy } from "./scroll.js";
|
|
6
6
|
export { ReadingProgressBar, ReadingRail } from "./reading.js";
|
|
7
|
-
export { EssayColumns, MetaDot, PostMetaRow, PostDate, formatPostDate, type PostDateFormat, ReadTime, TagPills, } from "./layout.js";
|
|
7
|
+
export { EssayColumns, EssayAside, EssayBody, ReadingLayout, MetaDot, PostMetaRow, PostDate, formatPostDate, type PostDateFormat, ReadTime, TagPills, } from "./layout.js";
|
package/dist/essay/index.js
CHANGED
|
@@ -6,4 +6,4 @@ export { createEssay,
|
|
|
6
6
|
EssayHeader, EssayLayout, EssaySection, EssayPullQuote, EssayFigure, EssayMovements, EssayDocument, } from "./essay.js";
|
|
7
7
|
export { useReadingProgress, useScrollSpy } from "./scroll.js";
|
|
8
8
|
export { ReadingProgressBar, ReadingRail } from "./reading.js";
|
|
9
|
-
export { EssayColumns, MetaDot, PostMetaRow, PostDate, formatPostDate, ReadTime, TagPills, } from "./layout.js";
|
|
9
|
+
export { EssayColumns, EssayAside, EssayBody, ReadingLayout, MetaDot, PostMetaRow, PostDate, formatPostDate, ReadTime, TagPills, } from "./layout.js";
|