@supertype.ai/foundations 0.1.24
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/LICENSE +21 -0
- package/README.md +369 -0
- package/bin/foundations.mjs +713 -0
- package/dist/blocks/accordion.d.ts +23 -0
- package/dist/blocks/accordion.js +59 -0
- package/dist/blocks/callout.d.ts +57 -0
- package/dist/blocks/callout.js +61 -0
- package/dist/blocks/card.d.ts +34 -0
- package/dist/blocks/card.js +56 -0
- package/dist/blocks/index.d.ts +7 -0
- package/dist/blocks/index.js +7 -0
- package/dist/blocks/interactive-accordion.d.ts +13 -0
- package/dist/blocks/interactive-accordion.js +27 -0
- package/dist/blocks/segment.d.ts +37 -0
- package/dist/blocks/segment.js +37 -0
- package/dist/blocks/steps.d.ts +10 -0
- package/dist/blocks/steps.js +13 -0
- package/dist/blocks/tabs.d.ts +32 -0
- package/dist/blocks/tabs.js +69 -0
- package/dist/cjs/eslint.js +146 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cn.d.ts +2 -0
- package/dist/cn.js +5 -0
- package/dist/contrast.d.ts +47 -0
- package/dist/contrast.js +255 -0
- package/dist/eslint.d.ts +74 -0
- package/dist/eslint.js +138 -0
- package/dist/essay/contents.d.ts +10 -0
- package/dist/essay/contents.js +17 -0
- package/dist/essay/essay.d.ts +125 -0
- package/dist/essay/essay.js +92 -0
- package/dist/essay/index.d.ts +7 -0
- package/dist/essay/index.js +9 -0
- package/dist/essay/layout.d.ts +72 -0
- package/dist/essay/layout.js +77 -0
- package/dist/essay/rail.d.ts +15 -0
- package/dist/essay/rail.js +26 -0
- package/dist/essay/reading.d.ts +17 -0
- package/dist/essay/reading.js +31 -0
- package/dist/essay/scroll.d.ts +8 -0
- package/dist/essay/scroll.js +78 -0
- package/dist/essay/toc.d.ts +23 -0
- package/dist/essay/toc.js +50 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +33 -0
- package/dist/injection.d.ts +8 -0
- package/dist/injection.js +1 -0
- package/dist/mdx.d.ts +47 -0
- package/dist/mdx.js +68 -0
- package/dist/og.d.ts +18 -0
- package/dist/og.js +50 -0
- package/dist/rehype.d.ts +18 -0
- package/dist/rehype.js +41 -0
- package/dist/seo.d.ts +174 -0
- package/dist/seo.js +152 -0
- package/dist/typography/as.d.ts +15 -0
- package/dist/typography/as.js +8 -0
- package/dist/typography/header.d.ts +44 -0
- package/dist/typography/header.js +119 -0
- package/dist/typography/highlight.d.ts +33 -0
- package/dist/typography/highlight.js +98 -0
- package/dist/typography/index.d.ts +4 -0
- package/dist/typography/index.js +3 -0
- package/dist/typography/paragraph.d.ts +157 -0
- package/dist/typography/paragraph.js +229 -0
- package/llms.txt +125 -0
- package/package.json +140 -0
- package/src/prose.css +12 -0
- package/src/shiki.css +23 -0
- package/src/theme.css +272 -0
- package/src/tokens.css +43 -0
- package/src/type.css +73 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A disclosure group: `<details>`/`<summary>`, no JS, correct before hydration.
|
|
4
|
+
*
|
|
5
|
+
* Named for what it is rather than `Accordion`, the interactive Base UI
|
|
6
|
+
* component next door. The two are not variants of each other — this one is a
|
|
7
|
+
* server component an MDX author gets for free, that one animates and manages
|
|
8
|
+
* state. Sharing a name is how a call site ends up with the wrong one.
|
|
9
|
+
*/
|
|
10
|
+
export declare function DisclosureGroup({ className, children, type, defaultValue, name, ...props }: Omit<ComponentProps<"div">, "defaultValue"> & {
|
|
11
|
+
/** `single` closes siblings when one opens. Defaults to `multiple`. */
|
|
12
|
+
type?: "single" | "multiple";
|
|
13
|
+
/** Title(s) open on first render. */
|
|
14
|
+
defaultValue?: string | string[];
|
|
15
|
+
/** Explicit group name; one is derived from `type` when omitted. */
|
|
16
|
+
name?: string;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
type DisclosureProps = Omit<ComponentProps<"details">, "title"> & {
|
|
19
|
+
title: ReactNode;
|
|
20
|
+
name?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function Disclosure({ title, children, className, ...props }: DisclosureProps): import("react").JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, cloneElement, isValidElement } from "react";
|
|
3
|
+
import { cn } from "../cn.js";
|
|
4
|
+
/**
|
|
5
|
+
* A disclosure group: `<details>`/`<summary>`, no JS, correct before hydration.
|
|
6
|
+
*
|
|
7
|
+
* Named for what it is rather than `Accordion`, the interactive Base UI
|
|
8
|
+
* component next door. The two are not variants of each other — this one is a
|
|
9
|
+
* server component an MDX author gets for free, that one animates and manages
|
|
10
|
+
* state. Sharing a name is how a call site ends up with the wrong one.
|
|
11
|
+
*/
|
|
12
|
+
export function DisclosureGroup({ className, children, type = "multiple", defaultValue, name, ...props }) {
|
|
13
|
+
// Single-open comes from the shared `name` attribute, which browsers implement
|
|
14
|
+
// natively and which degrades to all-open where they do not — a fine failure
|
|
15
|
+
// for a disclosure group, and far cheaper than shipping state for it.
|
|
16
|
+
const open = defaultValue
|
|
17
|
+
? new Set(Array.isArray(defaultValue) ? defaultValue : [defaultValue])
|
|
18
|
+
: null;
|
|
19
|
+
const groupName = type === "single" ? (name ?? deriveGroupName(children)) : undefined;
|
|
20
|
+
// Cloned rather than passed through context: a Provider would have to be a
|
|
21
|
+
// client component, and this whole block exists to stay off that boundary.
|
|
22
|
+
const items = Children.map(children, (child) => {
|
|
23
|
+
if (!isValidElement(child))
|
|
24
|
+
return child;
|
|
25
|
+
// defaultValue matches on the title, so it can only match a string one. A
|
|
26
|
+
// JSX title stringifies to "[object Object]" and would match nothing while
|
|
27
|
+
// looking like it should.
|
|
28
|
+
const title = child.props.title;
|
|
29
|
+
const defaultOpen = open && typeof title === "string" ? open.has(title) : undefined;
|
|
30
|
+
return cloneElement(child, {
|
|
31
|
+
name: child.props.name ?? groupName,
|
|
32
|
+
open: child.props.open ?? defaultOpen,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
return (_jsx("div", { className: cn("my-6 divide-y divide-border overflow-hidden rounded-xl border border-border", className), ...props, children: items }));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `<details name>` must match across siblings, server and client, and builds — a
|
|
39
|
+
* module counter fails all three, and `useId` is a hook in a server component.
|
|
40
|
+
* Hashing the titles is deterministic; identical groups on one page would merge.
|
|
41
|
+
*/
|
|
42
|
+
function deriveGroupName(children) {
|
|
43
|
+
const titles = [];
|
|
44
|
+
Children.forEach(children, (child) => {
|
|
45
|
+
if (isValidElement(child) && typeof child.props.title === "string") {
|
|
46
|
+
titles.push(child.props.title);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// djb2. Short, stable, and the collision domain here is one page.
|
|
50
|
+
let hash = 5381;
|
|
51
|
+
const source = titles.join("|");
|
|
52
|
+
for (let i = 0; i < source.length; i++) {
|
|
53
|
+
hash = ((hash << 5) + hash + source.charCodeAt(i)) | 0;
|
|
54
|
+
}
|
|
55
|
+
return `accordion-${(hash >>> 0).toString(36)}`;
|
|
56
|
+
}
|
|
57
|
+
export function Disclosure({ title, children, className, ...props }) {
|
|
58
|
+
return (_jsxs("details", { className: cn("group bg-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 })] }));
|
|
59
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
|
+
declare const TONE: {
|
|
3
|
+
/** The default: an explanation, not a problem. */
|
|
4
|
+
readonly muted: {
|
|
5
|
+
readonly box: "border-border bg-muted/40";
|
|
6
|
+
readonly title: "text-foreground";
|
|
7
|
+
readonly icon: "text-muted-foreground";
|
|
8
|
+
readonly rail: "bg-border";
|
|
9
|
+
};
|
|
10
|
+
/** Something failed and the reader needs to see that it did. */
|
|
11
|
+
readonly destructive: {
|
|
12
|
+
readonly box: "border-destructive/40 bg-destructive/5";
|
|
13
|
+
readonly title: "text-destructive";
|
|
14
|
+
readonly icon: "text-destructive";
|
|
15
|
+
readonly rail: "bg-destructive/60";
|
|
16
|
+
};
|
|
17
|
+
/** A prerequisite or a footgun: the reader can still proceed, but not blindly. */
|
|
18
|
+
readonly warn: {
|
|
19
|
+
readonly box: "border-warn/25 bg-warn/5";
|
|
20
|
+
readonly title: "text-warn-ink";
|
|
21
|
+
readonly icon: "text-warn";
|
|
22
|
+
readonly rail: "bg-warn/60";
|
|
23
|
+
};
|
|
24
|
+
/** A detail that rewards the reader rather than warning them. */
|
|
25
|
+
readonly accent: {
|
|
26
|
+
readonly box: "border-primary/25 bg-primary/5";
|
|
27
|
+
readonly title: "text-foreground";
|
|
28
|
+
readonly icon: "text-primary";
|
|
29
|
+
readonly rail: "bg-primary/60";
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Two densities, because this notice serves two ramps. `compact` is the product
|
|
34
|
+
* default (12px title over 12px body, no rail) that the relay sheet and the contact
|
|
35
|
+
* record already render. `editorial` is the docs form: body copy at reading size,
|
|
36
|
+
* and a 3px accent rail carrying the tone so the surface itself can stay quiet.
|
|
37
|
+
* Splitting on a prop rather than forking the component is the point of the file.
|
|
38
|
+
*/
|
|
39
|
+
export declare function Callout({ icon: Icon, title, tone, density, bodyClassName, action, children, className, }: {
|
|
40
|
+
/** Injected, so the package needs no icon set. Optional: a notice whose title already reads as a label
|
|
41
|
+
* ("Replied into Norman's thread") gains nothing from a glyph beside it. */
|
|
42
|
+
icon?: ComponentType<{
|
|
43
|
+
className?: string;
|
|
44
|
+
}>;
|
|
45
|
+
title?: ReactNode;
|
|
46
|
+
tone?: keyof typeof TONE;
|
|
47
|
+
density?: "compact" | "editorial";
|
|
48
|
+
/** For the one body that is not prose — a raw delivery error, which needs mono and its own
|
|
49
|
+
* line breaks preserved. */
|
|
50
|
+
bodyClassName?: string;
|
|
51
|
+
/** A link or buttons under the body. The only interactive slot: a notice that explains
|
|
52
|
+
* something usually also knows the one place to go and do something about it. */
|
|
53
|
+
action?: ReactNode;
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
className?: string;
|
|
56
|
+
}): import("react").JSX.Element;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../cn.js";
|
|
3
|
+
import { TypographyCaption, TypographyLabel, TypographyMuted, TypographySmall, } from "../typography/paragraph.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// An inline notice: a titled paragraph that explains something the surface it sits in cannot say
|
|
6
|
+
// on its own — why a row was dropped, why a payload is hidden, why a timeline is incomplete.
|
|
7
|
+
//
|
|
8
|
+
// Extracted because there were six of these hand-rolled across the relay detail sheet and the
|
|
9
|
+
// contact record, each re-declaring the same border, the same muted fill, the same `text-xs
|
|
10
|
+
// font-medium` title and the same `text-xs text-muted-foreground` body. Three of the six had
|
|
11
|
+
// already drifted on the radius. Hand-rolled type styles are also exactly what the project's own
|
|
12
|
+
// guidance forbids, and six copies is how a rule like that gets broken without anyone deciding to.
|
|
13
|
+
//
|
|
14
|
+
// Deliberately not a shadcn Alert. Alert is a page-level, role="alert" affordance for something
|
|
15
|
+
// that just happened; these are quiet, permanent explanations sitting inside a panel, and they
|
|
16
|
+
// must not announce themselves to a screen reader every time a sheet opens.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const TONE = {
|
|
19
|
+
/** The default: an explanation, not a problem. */
|
|
20
|
+
muted: {
|
|
21
|
+
box: "border-border bg-muted/40",
|
|
22
|
+
title: "text-foreground",
|
|
23
|
+
icon: "text-muted-foreground",
|
|
24
|
+
rail: "bg-border",
|
|
25
|
+
},
|
|
26
|
+
/** Something failed and the reader needs to see that it did. */
|
|
27
|
+
destructive: {
|
|
28
|
+
box: "border-destructive/40 bg-destructive/5",
|
|
29
|
+
title: "text-destructive",
|
|
30
|
+
icon: "text-destructive",
|
|
31
|
+
rail: "bg-destructive/60",
|
|
32
|
+
},
|
|
33
|
+
/** A prerequisite or a footgun: the reader can still proceed, but not blindly. */
|
|
34
|
+
warn: {
|
|
35
|
+
box: "border-warn/25 bg-warn/5",
|
|
36
|
+
title: "text-warn-ink",
|
|
37
|
+
icon: "text-warn",
|
|
38
|
+
rail: "bg-warn/60",
|
|
39
|
+
},
|
|
40
|
+
/** A detail that rewards the reader rather than warning them. */
|
|
41
|
+
accent: {
|
|
42
|
+
box: "border-primary/25 bg-primary/5",
|
|
43
|
+
title: "text-foreground",
|
|
44
|
+
icon: "text-primary",
|
|
45
|
+
rail: "bg-primary/60",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Two densities, because this notice serves two ramps. `compact` is the product
|
|
50
|
+
* default (12px title over 12px body, no rail) that the relay sheet and the contact
|
|
51
|
+
* record already render. `editorial` is the docs form: body copy at reading size,
|
|
52
|
+
* and a 3px accent rail carrying the tone so the surface itself can stay quiet.
|
|
53
|
+
* Splitting on a prop rather than forking the component is the point of the file.
|
|
54
|
+
*/
|
|
55
|
+
export function Callout({ icon: Icon, title, tone = "muted", density = "compact", bodyClassName, action, children, className, }) {
|
|
56
|
+
const t = TONE[tone];
|
|
57
|
+
if (density === "editorial") {
|
|
58
|
+
return (_jsx("div", { className: cn("relative overflow-hidden rounded-lg border py-3.5 pl-5 pr-4", t.box, className), children: _jsxs("div", { className: "flex items-start gap-2.5", children: [Icon && _jsx(Icon, { className: cn("mt-0.5 size-4 shrink-0", t.icon) }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [title && (_jsx(TypographyLabel, { className: t.title, children: title })), _jsx(TypographyMuted, { className: cn("leading-relaxed", bodyClassName), children: children }), action && (_jsx("div", { className: "mt-1 flex items-center gap-1", children: action }))] })] }) }));
|
|
59
|
+
}
|
|
60
|
+
return (_jsxs("div", { className: cn("rounded-md border p-3", t.box, className), children: [title && (_jsxs(TypographySmall, { className: cn("flex items-center gap-1.5 font-medium", t.title), children: [Icon && _jsx(Icon, { className: cn("size-3.5 shrink-0", t.icon) }), title] })), _jsx(TypographyCaption, { className: cn("mt-1 block leading-relaxed", bodyClassName), children: children }), action && _jsx("div", { className: "mt-2 flex items-center gap-1", children: action })] }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
/** Two columns from `sm` up: a pair reads as a set rather than two panels. */
|
|
3
|
+
export declare function Cards({ className, children, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
4
|
+
export declare function CardHeader({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
5
|
+
/**
|
|
6
|
+
* No `font-heading`, and no rung off the heading ladder. That role is the
|
|
7
|
+
* editorial display face — `.editorial` hands it to the serif and drops the
|
|
8
|
+
* weight to 400 — and a card is chrome, not prose: dropped into a docs page it
|
|
9
|
+
* wore a serif title over a sans description and lost the weight that separated
|
|
10
|
+
* the two. Rank inside a card is weight and size, the way a callout title does
|
|
11
|
+
* it. The face is whatever the card inherits.
|
|
12
|
+
*/
|
|
13
|
+
export declare function CardTitle({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
14
|
+
export declare function CardDescription({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
15
|
+
export declare function CardContent({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
16
|
+
type CardShorthand = {
|
|
17
|
+
/** Shorthand header: 52 MDX files use it, and no compiler checks those. */
|
|
18
|
+
title?: ReactNode;
|
|
19
|
+
description?: ReactNode;
|
|
20
|
+
icon?: ReactNode;
|
|
21
|
+
/** Override the scheme sniff: an absolute URL home, or a relative one away. */
|
|
22
|
+
external?: boolean;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Takes either shape: `title`/`href` fills the header, or compose the slots
|
|
26
|
+
* directly. Unrecognised props pass through — MDX authors reach for the whole
|
|
27
|
+
* HTML surface. An href with a scheme leaves the app; the rest route through
|
|
28
|
+
* the router's Link.
|
|
29
|
+
*/
|
|
30
|
+
export declare function Card({ href, className, external, title, description, icon, children, ...rest }: CardShorthand & {
|
|
31
|
+
href?: string;
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
} & Omit<ComponentProps<"a">, keyof CardShorthand | "href" | "children">): import("react").JSX.Element;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../cn.js";
|
|
3
|
+
import { Link } from "next-view-transitions";
|
|
4
|
+
/** Two columns from `sm` up: a pair reads as a set rather than two panels. */
|
|
5
|
+
export function Cards({ className, children, ...props }) {
|
|
6
|
+
return (_jsx("div", { className: cn("my-6 grid gap-4 sm:grid-cols-2", className), ...props, children: children }));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `ring-1` not `border`: a ring draws outside the box, so a card sits flush in a
|
|
10
|
+
* grid and `overflow-hidden` clips a bleed image cleanly. Padding is vertical
|
|
11
|
+
* only — the horizontal inset belongs to the slots, so bands can run edge to edge.
|
|
12
|
+
*/
|
|
13
|
+
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 " +
|
|
14
|
+
"has-[>img:first-child]:pt-0 " +
|
|
15
|
+
"*:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl";
|
|
16
|
+
export function CardHeader({ className, ...props }) {
|
|
17
|
+
return (_jsx("div", { "data-slot": "card-header", className: cn("grid auto-rows-min items-start gap-1 px-4 [.border-b]:pb-4", className), ...props }));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* No `font-heading`, and no rung off the heading ladder. That role is the
|
|
21
|
+
* editorial display face — `.editorial` hands it to the serif and drops the
|
|
22
|
+
* weight to 400 — and a card is chrome, not prose: dropped into a docs page it
|
|
23
|
+
* wore a serif title over a sans description and lost the weight that separated
|
|
24
|
+
* the two. Rank inside a card is weight and size, the way a callout title does
|
|
25
|
+
* it. The face is whatever the card inherits.
|
|
26
|
+
*/
|
|
27
|
+
export function CardTitle({ className, ...props }) {
|
|
28
|
+
return (_jsx("div", { "data-slot": "card-title", className: cn("text-base leading-snug font-medium", className), ...props }));
|
|
29
|
+
}
|
|
30
|
+
export function CardDescription({ className, ...props }) {
|
|
31
|
+
return (_jsx("div", { "data-slot": "card-description", className: cn("text-sm text-muted-foreground", className), ...props }));
|
|
32
|
+
}
|
|
33
|
+
export function CardContent({ className, ...props }) {
|
|
34
|
+
return (_jsx("div", { "data-slot": "card-content", className: cn("px-4", className), ...props }));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Takes either shape: `title`/`href` fills the header, or compose the slots
|
|
38
|
+
* directly. Unrecognised props pass through — MDX authors reach for the whole
|
|
39
|
+
* HTML surface. An href with a scheme leaves the app; the rest route through
|
|
40
|
+
* the router's Link.
|
|
41
|
+
*/
|
|
42
|
+
export function Card({ href, className, external, title, description, icon, children, ...rest }) {
|
|
43
|
+
const header = title || description || icon ? (_jsxs(CardHeader, { children: [icon ? _jsx("div", { className: "mb-1 text-muted-foreground", children: icon }) : null, title ? _jsx(CardTitle, { children: title }) : null, description ? _jsx(CardDescription, { children: description }) : null] })) : null;
|
|
44
|
+
// Bare children compose; children under a shorthand header are body copy.
|
|
45
|
+
const body = header ? (_jsxs(_Fragment, { children: [header, children ? _jsx(CardContent, { children: children }) : null] })) : (children);
|
|
46
|
+
const shared = { "data-slot": "card" };
|
|
47
|
+
if (!href) {
|
|
48
|
+
return (_jsx("div", { className: cn(CARD_CLASS, className), ...shared, ...rest, children: body }));
|
|
49
|
+
}
|
|
50
|
+
const leavesApp = external ?? /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
51
|
+
const classes = cn(CARD_CLASS, "no-underline transition-colors hover:bg-accent", className);
|
|
52
|
+
if (leavesApp) {
|
|
53
|
+
return (_jsx("a", { href: href, className: classes, target: "_blank", rel: "noopener noreferrer", ...shared, ...rest, children: body }));
|
|
54
|
+
}
|
|
55
|
+
return (_jsx(Link, { href: href, className: classes, ...shared, ...rest, children: body }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Cards, Card, CardHeader, CardTitle, CardDescription, CardContent, } from "./card.js";
|
|
2
|
+
export { Disclosure, DisclosureGroup } from "./accordion.js";
|
|
3
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "./interactive-accordion.js";
|
|
4
|
+
export { Callout } from "./callout.js";
|
|
5
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, TabGroup, Tab, } from "./tabs.js";
|
|
6
|
+
export { SEGMENT } from "./segment.js";
|
|
7
|
+
export { Steps, Step } from "./steps.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Cards, Card, CardHeader, CardTitle, CardDescription, CardContent, } from "./card.js";
|
|
2
|
+
export { Disclosure, DisclosureGroup } from "./accordion.js";
|
|
3
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "./interactive-accordion.js";
|
|
4
|
+
export { Callout } from "./callout.js";
|
|
5
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, TabGroup, Tab, } from "./tabs.js";
|
|
6
|
+
export { SEGMENT } from "./segment.js";
|
|
7
|
+
export { Steps, Step } from "./steps.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The interactive accordion: Base UI, animated, client-side.
|
|
3
|
+
*
|
|
4
|
+
* `Disclosure`/`DisclosureGroup` next door is the `<details>` version an MDX
|
|
5
|
+
* author gets with no JS. Neither is a variant of the other, so they no longer
|
|
6
|
+
* share a name.
|
|
7
|
+
*/
|
|
8
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
9
|
+
declare function Accordion({ className, ...props }: AccordionPrimitive.Root.Props): import("react").JSX.Element;
|
|
10
|
+
declare function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props): import("react").JSX.Element;
|
|
11
|
+
declare function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props): import("react").JSX.Element;
|
|
12
|
+
declare function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props): import("react").JSX.Element;
|
|
13
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The interactive accordion: Base UI, animated, client-side.
|
|
4
|
+
*
|
|
5
|
+
* `Disclosure`/`DisclosureGroup` next door is the `<details>` version an MDX
|
|
6
|
+
* author gets with no JS. Neither is a variant of the other, so they no longer
|
|
7
|
+
* share a name.
|
|
8
|
+
*/
|
|
9
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
10
|
+
import { cn } from "../cn.js";
|
|
11
|
+
function Accordion({ className, ...props }) {
|
|
12
|
+
return (_jsx(AccordionPrimitive.Root, { "data-slot": "accordion", className: cn("flex w-full flex-col", className), ...props }));
|
|
13
|
+
}
|
|
14
|
+
function AccordionItem({ className, ...props }) {
|
|
15
|
+
return (_jsx(AccordionPrimitive.Item, { "data-slot": "accordion-item", className: cn("not-last:border-b", className), ...props }));
|
|
16
|
+
}
|
|
17
|
+
function AccordionTrigger({ className, children, ...props }) {
|
|
18
|
+
return (_jsx(AccordionPrimitive.Header, { className: "flex", children: _jsxs(AccordionPrimitive.Trigger, { "data-slot": "accordion-trigger", className: cn("group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground", className), ...props, children: [children, _jsx(Chevron, { className: "group-aria-expanded/accordion-trigger:hidden" }), _jsx(Chevron, { up: true, className: "hidden group-aria-expanded/accordion-trigger:inline" })] }) }));
|
|
19
|
+
}
|
|
20
|
+
function AccordionContent({ className, children, ...props }) {
|
|
21
|
+
return (_jsx(AccordionPrimitive.Panel, { "data-slot": "accordion-content", className: "overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up", ...props, children: _jsx("div", { className: cn("h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4", className), children: children }) }));
|
|
22
|
+
}
|
|
23
|
+
/** Inline, not an icon import — one glyph is not a dependency. */
|
|
24
|
+
function Chevron({ up, className }) {
|
|
25
|
+
return (_jsx("svg", { "data-slot": "accordion-trigger-icon", "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: cn("pointer-events-none shrink-0", className), children: _jsx("path", { d: up ? "m18 15-6-6-6 6" : "m6 9 6 6 6-6" }) }));
|
|
26
|
+
}
|
|
27
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segmented picker, as one set of surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Two components draw this control and neither can absorb the other: `Tabs` is Base UI's
|
|
5
|
+
* compositional Tabs, driven by a `value` and marking its own trigger with `data-active`,
|
|
6
|
+
* while a consumer's marketing picker is a data-driven `items`/`index` control that paints
|
|
7
|
+
* the active state onto a separate absolutely-positioned pill so `motion` can slide it.
|
|
8
|
+
* Same design, two mechanisms. Before this file they were two hand-synced copies of the same
|
|
9
|
+
* class strings, which is exactly the kind of duplication that drifts the first time
|
|
10
|
+
* someone retunes the palette.
|
|
11
|
+
*
|
|
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
|
+
* The active segment is deliberately flat — a card surface and a hairline, no shadow.
|
|
18
|
+
* Elevation in this system means "this layer left the page plane" (see --elevation-* in
|
|
19
|
+
* theme.css), and a segment sitting inside its own track has not.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SEGMENT: {
|
|
22
|
+
/** The rail a set of segments sits in. Surface only; each component owns its layout. */
|
|
23
|
+
readonly track: "rounded-lg border border-border bg-muted/40 p-0.5";
|
|
24
|
+
/** Affordances every segment shares, whatever its shape or engine. */
|
|
25
|
+
readonly item: "relative inline-flex items-center gap-1.5 font-medium outline-none transition-colors focus-visible:ring-3 focus-visible:ring-ring/50";
|
|
26
|
+
/** Selected: the ink lifts to full strength. */
|
|
27
|
+
readonly active: "text-foreground";
|
|
28
|
+
/**
|
|
29
|
+
* Unselected: quiet, but lighting its own surface on hover so the whole strip reads as
|
|
30
|
+
* reachable rather than only the segment already chosen.
|
|
31
|
+
*/
|
|
32
|
+
readonly idle: "text-muted-foreground hover:bg-muted/60 hover:text-foreground";
|
|
33
|
+
/** The flat surface marking the selection. */
|
|
34
|
+
readonly activeSurface: "bg-card ring-1 ring-border";
|
|
35
|
+
/** `activeSurface`, for an engine that marks its own trigger with `data-active`. */
|
|
36
|
+
readonly dataActiveSurface: "data-active:bg-card data-active:text-foreground data-active:ring-1 data-active:ring-border";
|
|
37
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segmented picker, as one set of surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Two components draw this control and neither can absorb the other: `Tabs` is Base UI's
|
|
5
|
+
* compositional Tabs, driven by a `value` and marking its own trigger with `data-active`,
|
|
6
|
+
* while a consumer's marketing picker is a data-driven `items`/`index` control that paints
|
|
7
|
+
* the active state onto a separate absolutely-positioned pill so `motion` can slide it.
|
|
8
|
+
* Same design, two mechanisms. Before this file they were two hand-synced copies of the same
|
|
9
|
+
* class strings, which is exactly the kind of duplication that drifts the first time
|
|
10
|
+
* someone retunes the palette.
|
|
11
|
+
*
|
|
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
|
+
* The active segment is deliberately flat — a card surface and a hairline, no shadow.
|
|
18
|
+
* Elevation in this system means "this layer left the page plane" (see --elevation-* in
|
|
19
|
+
* theme.css), and a segment sitting inside its own track has not.
|
|
20
|
+
*/
|
|
21
|
+
export const SEGMENT = {
|
|
22
|
+
/** The rail a set of segments sits in. Surface only; each component owns its layout. */
|
|
23
|
+
track: "rounded-lg border border-border bg-muted/40 p-0.5",
|
|
24
|
+
/** Affordances every segment shares, whatever its shape or engine. */
|
|
25
|
+
item: "relative inline-flex items-center gap-1.5 font-medium outline-none transition-colors focus-visible:ring-3 focus-visible:ring-ring/50",
|
|
26
|
+
/** Selected: the ink lifts to full strength. */
|
|
27
|
+
active: "text-foreground",
|
|
28
|
+
/**
|
|
29
|
+
* Unselected: quiet, but lighting its own surface on hover so the whole strip reads as
|
|
30
|
+
* reachable rather than only the segment already chosen.
|
|
31
|
+
*/
|
|
32
|
+
idle: "text-muted-foreground hover:bg-muted/60 hover:text-foreground",
|
|
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",
|
|
37
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Numbers are a CSS counter, not markup: reordering renumbers itself, and the
|
|
4
|
+
* digits stay out of the accessibility tree and out of copied text.
|
|
5
|
+
*/
|
|
6
|
+
export declare function Steps({ className, children, ...props }: ComponentProps<"div">): import("react").JSX.Element;
|
|
7
|
+
/** One step. `title` is its heading; children are the body. */
|
|
8
|
+
export declare function Step({ title, className, children, ...props }: Omit<ComponentProps<"div">, "title"> & {
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../cn.js";
|
|
3
|
+
/**
|
|
4
|
+
* Numbers are a CSS counter, not markup: reordering renumbers itself, and the
|
|
5
|
+
* digits stay out of the accessibility tree and out of copied text.
|
|
6
|
+
*/
|
|
7
|
+
export function Steps({ className, children, ...props }) {
|
|
8
|
+
return (_jsx("div", { className: cn("my-6 [counter-reset:prose-step]", className), ...props, children: children }));
|
|
9
|
+
}
|
|
10
|
+
/** One step. `title` is its heading; children are the body. */
|
|
11
|
+
export function Step({ title, className, children, ...props }) {
|
|
12
|
+
return (_jsxs("div", { className: cn("relative border-l border-border pb-6 pl-10 last:border-transparent last:pb-0", "[counter-increment:prose-step]", "before:absolute before:left-0 before:top-0 before:-translate-x-1/2", "before:flex before:h-7 before:w-7 before:items-center before:justify-center", "before:rounded-full before:bg-muted before:text-xs before:font-semibold", "before:text-foreground before:[content:counter(prose-step)]", className), ...props, children: [title ? (_jsx("div", { className: "mb-1 font-semibold text-foreground", children: title })) : null, _jsx("div", { className: "text-sm text-muted-foreground", children: children })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { type VariantProps } from "class-variance-authority";
|
|
4
|
+
declare function Tabs({ className, orientation, ...props }: TabsPrimitive.Root.Props): import("react").JSX.Element;
|
|
5
|
+
declare const tabsListVariants: (props?: ({
|
|
6
|
+
variant?: "line" | "default" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
declare function TabsList({ className, variant, ...props }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>): import("react").JSX.Element;
|
|
9
|
+
declare function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props): import("react").JSX.Element;
|
|
10
|
+
declare function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props): import("react").JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* The declarative shorthand: `items` plus a `<Tab>` per panel. `TabGroup` is to
|
|
13
|
+
* `Tabs` what `DisclosureGroup` is to `Accordion` — the shape you reach for when
|
|
14
|
+
* the tabs are data, and what an MDX author writes as `<Tabs>`.
|
|
15
|
+
*
|
|
16
|
+
* A caller passing data has no value to bind, so children pair with `items`
|
|
17
|
+
* **by position** — `value` on a `<Tab>` is for readability and is not matched,
|
|
18
|
+
* since matching would silently drop a panel on an edited label. Everything
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
export declare function TabGroup({ items, children, className, }: {
|
|
23
|
+
items: string[];
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
27
|
+
/** `value` names the panel at the call site; it is not used for matching. */
|
|
28
|
+
export declare function Tab({ children }: {
|
|
29
|
+
value?: string;
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
}): import("react").JSX.Element;
|
|
32
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { cn } from "../cn.js";
|
|
6
|
+
import { SEGMENT } from "./segment.js";
|
|
7
|
+
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
8
|
+
return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, className: cn(
|
|
9
|
+
// Base UI writes the orientation as `data-orientation="horizontal|vertical"`, so
|
|
10
|
+
// the variant has to read that attribute's value. A bare `data-horizontal:` compiles
|
|
11
|
+
// to `[data-horizontal]`, which nothing ever sets: the root silently stayed a row
|
|
12
|
+
// flex container and laid its panels out beside the tab strip.
|
|
13
|
+
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className), ...props }));
|
|
14
|
+
}
|
|
15
|
+
// Surfaces come from SEGMENT, so this and marketing/segmented-control cannot drift: a
|
|
16
|
+
// reader who meets the picker on a docs page and again on the usage dashboard should not
|
|
17
|
+
// have to learn it twice. Layout stays local, since only this one has orientation to serve.
|
|
18
|
+
const tabsListVariants = cva("group/tabs-list inline-flex h-8 w-fit items-center justify-center text-muted-foreground data-[variant=line]:rounded-none", {
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: SEGMENT.track,
|
|
22
|
+
line: "gap-1 border-0 bg-transparent",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "default",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
function TabsList({ className, variant = "default", ...props }) {
|
|
30
|
+
return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }), className), ...props }));
|
|
31
|
+
}
|
|
32
|
+
function TabsTrigger({ className, ...props }) {
|
|
33
|
+
return (_jsx(TabsPrimitive.Tab, { "data-slot": "tabs-trigger", className: cn(SEGMENT.item, SEGMENT.dataActiveSurface, "rounded-md text-muted-foreground hover:text-foreground",
|
|
34
|
+
// A trigger fills its share of the track and may carry an icon, neither of which a
|
|
35
|
+
// standalone picker button has to do.
|
|
36
|
+
"h-[calc(100%-1px)] flex-1 justify-center px-1.5 py-0.5 text-sm whitespace-nowrap disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
37
|
+
// The idle hover, scoped to the boxed variant. Spelled out rather than
|
|
38
|
+
// interpolated: an interpolated class is invisible to Tailwind's scanner and would
|
|
39
|
+
// compile to nothing at all.
|
|
40
|
+
"group-data-[variant=default]/tabs-list:not-data-active:hover:bg-muted/60",
|
|
41
|
+
// The line variant wears no surface at all; the underline below is its whole signal.
|
|
42
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:ring-0",
|
|
43
|
+
// That underline carries the accent rather than plain ink, so it reads as the
|
|
44
|
+
// brand's marker and not as a bold rule.
|
|
45
|
+
"after:absolute after:inset-x-0 after:bottom-[-5px] after:h-0.5 after:bg-primary after:opacity-0 after:transition-opacity group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className), ...props }));
|
|
46
|
+
}
|
|
47
|
+
function TabsContent({ className, ...props }) {
|
|
48
|
+
return (_jsx(TabsPrimitive.Panel, { "data-slot": "tabs-content", className: cn("flex-1 text-sm outline-none", className), ...props }));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The declarative shorthand: `items` plus a `<Tab>` per panel. `TabGroup` is to
|
|
52
|
+
* `Tabs` what `DisclosureGroup` is to `Accordion` — the shape you reach for when
|
|
53
|
+
* the tabs are data, and what an MDX author writes as `<Tabs>`.
|
|
54
|
+
*
|
|
55
|
+
* A caller passing data has no value to bind, so children pair with `items`
|
|
56
|
+
* **by position** — `value` on a `<Tab>` is for readability and is not matched,
|
|
57
|
+
* since matching would silently drop a panel on an edited label. Everything
|
|
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.
|
|
60
|
+
*/
|
|
61
|
+
export function TabGroup({ items, children, className, }) {
|
|
62
|
+
const panels = Array.isArray(children) ? children : [children];
|
|
63
|
+
return (_jsxs(Tabs, { defaultValue: 0, className: cn("my-6", className), children: [_jsx(TabsList, { children: items.map((label, i) => (_jsx(TabsTrigger, { value: i, children: label }, label))) }), panels.map((panel, i) => (_jsx(TabsContent, { value: i, className: "pt-2 text-muted-foreground", children: panel }, i)))] }));
|
|
64
|
+
}
|
|
65
|
+
/** `value` names the panel at the call site; it is not used for matching. */
|
|
66
|
+
export function Tab({ children }) {
|
|
67
|
+
return _jsx(_Fragment, { children: children });
|
|
68
|
+
}
|
|
69
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|