@supertype.ai/foundations 0.1.28 → 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 -2
- 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/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/README.md
CHANGED
|
@@ -65,7 +65,7 @@ untagged git dependency re-resolves to a different commit on any fresh install.
|
|
|
65
65
|
|
|
66
66
|
```jsonc
|
|
67
67
|
// package.json
|
|
68
|
-
"@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.1.
|
|
68
|
+
"@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.1.29"
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
</details>
|
|
@@ -233,7 +233,7 @@ package.
|
|
|
233
233
|
| import | contains | docs |
|
|
234
234
|
| --------------------------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------- |
|
|
235
235
|
| `@supertype.ai/foundations` | all typography primitives, `cn` | [Typography](docs/typography.md) |
|
|
236
|
-
| `@supertype.ai/foundations/blocks` | `Card`, `Callout`, `Steps`, `
|
|
236
|
+
| `@supertype.ai/foundations/blocks` | `Button`, `Badge`, `Card`, `Callout`, `Steps`, `TabGroup`, `Accordion`, `SEGMENT` | [Blocks](docs/blocks.md) |
|
|
237
237
|
| `@supertype.ai/foundations/mdx` | `proseMdxComponents` — the MDX element map | [In MDX](docs/blocks.md#in-mdx) |
|
|
238
238
|
| `@supertype.ai/foundations/essay` | the long-form shell, TOC, reading rail, post meta | [Essay](docs/essay.md) |
|
|
239
239
|
| `@supertype.ai/foundations/seo` | `createSeo(...)` — metadata + JSON-LD | [Tooling](docs/tooling.md#seo-and-og-images) |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ComponentProps, ReactElement } from "react";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
declare const badge: (props?: ({
|
|
4
|
+
tone?: "muted" | "primary" | "secondary" | "brand" | "success" | "warn" | "destructive" | null | undefined;
|
|
5
|
+
size?: "sm" | "xs" | null | undefined;
|
|
6
|
+
pill?: boolean | null | undefined;
|
|
7
|
+
variant?: "solid" | "soft" | "outline" | "ghost" | null | undefined;
|
|
8
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
9
|
+
export type BadgeLook = VariantProps<typeof badge>;
|
|
10
|
+
export declare function badgeVariants(props?: Parameters<typeof badge>[0]): string;
|
|
11
|
+
/**
|
|
12
|
+
* A `span` unless `render` says otherwise — cloned rather than run through a
|
|
13
|
+
* `useRender` hook, which is what viably's badge did. A hook would make every
|
|
14
|
+
* badge in the tree a client component to serve the one call site that renders
|
|
15
|
+
* an anchor, and a badge is a label: it should cost nothing on the server. This
|
|
16
|
+
* is the same mechanism `Button` uses for the same reason.
|
|
17
|
+
*
|
|
18
|
+
* The `[a]:hover` rules above light up on their own when an anchor is the parent
|
|
19
|
+
* or the rendered element.
|
|
20
|
+
*/
|
|
21
|
+
export declare function Badge({ className, variant, tone, size, pill, render, ...props }: ComponentProps<"span"> & BadgeLook & {
|
|
22
|
+
render?: ReactElement;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../cn.js";
|
|
4
|
+
import { renderAs } from "./render-as.js";
|
|
5
|
+
import { FOCUS_RING } from "./focus.js";
|
|
6
|
+
import { TONE, TONE_SURFACE, impliedTone } from "../tone.js";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// A label that is not a control. Same two axes as Button, and for the same
|
|
9
|
+
// reason: the two apps had each grown their own list, and the lists disagreed
|
|
10
|
+
// with each other and with the rest of the package.
|
|
11
|
+
//
|
|
12
|
+
// ssite's read `default | secondary | destructive | outline | success | warning
|
|
13
|
+
// | supertype | tint`. Two of those are the package's tones under invented
|
|
14
|
+
// spellings — `warning` for `warn`, `supertype` for `brand` — which is precisely
|
|
15
|
+
// the second vocabulary a design system exists to prevent. `secondary` was
|
|
16
|
+
// `bg-muted/80 text-foreground` against a `default` of `bg-muted
|
|
17
|
+
// text-muted-foreground`: a distinction no reader could name, let alone use.
|
|
18
|
+
//
|
|
19
|
+
// viably's read `default | secondary | destructive | outline | ghost | link`,
|
|
20
|
+
// which is a copy of the button list it was cargo-culted from — including the
|
|
21
|
+
// `link` variant, which no badge has ever used, because a badge is not a link.
|
|
22
|
+
//
|
|
23
|
+
// So: `variant` for how much ink, `tone` for what it means, both spelled exactly
|
|
24
|
+
// as Button spells them. `link` is absent because it was never real; everything
|
|
25
|
+
// else about the vocabulary is the same list, so knowing one component's axes is
|
|
26
|
+
// knowing this one's.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const badge = cva(cn("inline-flex w-fit shrink-0 items-center justify-center gap-1", "overflow-hidden border border-transparent font-medium whitespace-nowrap", cn("transition focus-visible:border-ring", FOCUS_RING), "aria-invalid:border-destructive aria-invalid:ring-destructive/20", "[&>svg]:pointer-events-none [&>svg]:size-3!", TONE_SURFACE), {
|
|
29
|
+
// Same cascade order as Button: `pill` beats `size` on radius.
|
|
30
|
+
variants: {
|
|
31
|
+
tone: TONE,
|
|
32
|
+
/**
|
|
33
|
+
* Two rungs, because a badge has two jobs. `sm` is the label riding beside
|
|
34
|
+
* a title; `xs` is the figure riding beside a toolbar control — a filter
|
|
35
|
+
* tally, an unread count.
|
|
36
|
+
*
|
|
37
|
+
* `leading-none` restates the default on purpose: `text-3xs` carries a
|
|
38
|
+
* line-height of its own, which lands after the base class and wins.
|
|
39
|
+
* Stating it per size is what keeps `!leading-none` out of call sites.
|
|
40
|
+
*/
|
|
41
|
+
size: {
|
|
42
|
+
xs: "h-4 min-w-4 rounded px-1 text-3xs leading-none",
|
|
43
|
+
sm: "h-5 rounded-md px-2 py-0.5 text-xs leading-none",
|
|
44
|
+
},
|
|
45
|
+
pill: { true: "rounded-full", false: "" },
|
|
46
|
+
variant: {
|
|
47
|
+
solid: "bg-(--tone-fill) text-(color:--tone-ink) [a]:hover:bg-(--tone-fill-hover)",
|
|
48
|
+
soft: "bg-(--tone-wash) text-(color:--tone-hue) [a]:hover:bg-(--tone-wash-hover)",
|
|
49
|
+
outline: "border-(color:--tone-line) text-(color:--tone-hue) [a]:hover:bg-(--tone-wash)",
|
|
50
|
+
ghost: "text-(color:--tone-hue) hover:bg-(--tone-wash)",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: { variant: "solid", size: "sm", pill: false },
|
|
54
|
+
});
|
|
55
|
+
export function badgeVariants(props = {}) {
|
|
56
|
+
return badge({ tone: props?.tone ?? impliedTone(props?.variant), ...props });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A `span` unless `render` says otherwise — cloned rather than run through a
|
|
60
|
+
* `useRender` hook, which is what viably's badge did. A hook would make every
|
|
61
|
+
* badge in the tree a client component to serve the one call site that renders
|
|
62
|
+
* an anchor, and a badge is a label: it should cost nothing on the server. This
|
|
63
|
+
* is the same mechanism `Button` uses for the same reason.
|
|
64
|
+
*
|
|
65
|
+
* The `[a]:hover` rules above light up on their own when an anchor is the parent
|
|
66
|
+
* or the rendered element.
|
|
67
|
+
*/
|
|
68
|
+
export function Badge({ className, variant, tone, size, pill, render, ...props }) {
|
|
69
|
+
const resolved = tone ?? impliedTone(variant);
|
|
70
|
+
const classes = cn(badge({ variant, tone: resolved, size, pill, className }));
|
|
71
|
+
const marks = {
|
|
72
|
+
"data-slot": "badge",
|
|
73
|
+
"data-variant": variant ?? "solid",
|
|
74
|
+
"data-tone": resolved,
|
|
75
|
+
};
|
|
76
|
+
const as = renderAs(render, classes, { ...marks, ...props });
|
|
77
|
+
if (as)
|
|
78
|
+
return as;
|
|
79
|
+
return _jsx("span", { ...marks, className: classes, ...props });
|
|
80
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
declare const button: (props?: ({
|
|
4
|
+
tone?: "muted" | "primary" | "secondary" | "brand" | "success" | "warn" | "destructive" | null | undefined;
|
|
5
|
+
size?: "sm" | "xs" | "md" | "lg" | "xl" | null | undefined;
|
|
6
|
+
icon?: boolean | null | undefined;
|
|
7
|
+
pill?: boolean | null | undefined;
|
|
8
|
+
variant?: "solid" | "link" | "soft" | "outline" | "ghost" | null | undefined;
|
|
9
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
10
|
+
export type ButtonLook = VariantProps<typeof button>;
|
|
11
|
+
/**
|
|
12
|
+
* The class list, for the handful of places that style someone else's element
|
|
13
|
+
* and cannot render a `Button` — a router `Link` inside a `not-found`, a
|
|
14
|
+
* calendar day cell. Takes the same props, including the implied tone.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buttonVariants(props?: Parameters<typeof button>[0]): string;
|
|
17
|
+
/**
|
|
18
|
+
* A non-`<button>` render element bypasses the primitive on purpose: Base UI
|
|
19
|
+
* always stamps `type="button"` or `role="button"`, and the latter drops an
|
|
20
|
+
* anchor out of screen-reader link navigation. Cloning gives it the classes and
|
|
21
|
+
* nothing else.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Button({ className, variant, tone, size, icon, pill, render, nativeButton, ...props }: ButtonPrimitive.Props & ButtonLook): import("react").JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { isValidElement } from "react";
|
|
3
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { cn } from "../cn.js";
|
|
6
|
+
import { renderAs } from "./render-as.js";
|
|
7
|
+
import { FOCUS_RING } from "./focus.js";
|
|
8
|
+
import { TONE, TONE_SURFACE, impliedTone } from "../tone.js";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// The control both apps were re-declaring. viably and ssite each carried their
|
|
11
|
+
// own `cva` with its own variant list — `default | secondary | accent |
|
|
12
|
+
// destructive | ghost | outline | link` in one, the same names minus two plus a
|
|
13
|
+
// `rose` in the other — and the two had already drifted on radius, on height,
|
|
14
|
+
// and on what `destructive` even means (a solid red fill in ssite, a tinted wash
|
|
15
|
+
// in viably).
|
|
16
|
+
//
|
|
17
|
+
// The fix is not a longer shared list. Those names answer two questions at once:
|
|
18
|
+
//
|
|
19
|
+
// variant — how much ink the button spends. Filled, washed, hairline, bare.
|
|
20
|
+
// tone — what the ink means. See ../tone.ts; Callout and TypographyLink
|
|
21
|
+
// take the same seven, because a component does not get to invent a
|
|
22
|
+
// name for a colour the package has already named. Its default is
|
|
23
|
+
// the one the variant implies — see `impliedTone`.
|
|
24
|
+
//
|
|
25
|
+
// `destructive` is a tone. `ghost` is a variant. A list holding both can only
|
|
26
|
+
// express the pairs someone thought to add, which is why neither app could write
|
|
27
|
+
// a quiet destructive button without a className.
|
|
28
|
+
//
|
|
29
|
+
// Everything else is a modifier, and both are boolean because both have exactly
|
|
30
|
+
// two states: `icon` squares the box, `pill` rounds it off. They compose — a
|
|
31
|
+
// round icon button is `icon pill` — which is why they are not one `shape` enum.
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
const button = cva(cn("inline-flex shrink-0 cursor-pointer items-center justify-center", "border border-transparent bg-clip-padding font-medium whitespace-nowrap", "transition select-none",
|
|
34
|
+
// The border joins the ring where a control has one; the ring itself is shared.
|
|
35
|
+
cn(FOCUS_RING, "focus-visible:border-ring"), "active:not-aria-[haspopup]:translate-y-px", "disabled:pointer-events-none disabled:opacity-50", "aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", TONE_SURFACE), {
|
|
36
|
+
// Key order is cascade order: `cva` emits these as declared and `cn` resolves
|
|
37
|
+
// a conflict in favour of the last one written. `pill` therefore beats
|
|
38
|
+
// `size` on radius, and `variant` beats `size` on the box — which is what
|
|
39
|
+
// lets `link` shed the height and padding of whatever size it was given.
|
|
40
|
+
// Reordering this object is a visual change.
|
|
41
|
+
variants: {
|
|
42
|
+
tone: TONE,
|
|
43
|
+
/**
|
|
44
|
+
* One ladder, 24px to 40px on a 4px step. `md` is the product default —
|
|
45
|
+
* dense rows of controls beside a table — and `lg`/`xl` the marketing
|
|
46
|
+
* rungs. `--radius-lg` is 10px and theme.css already labels it "buttons,
|
|
47
|
+
* default control"; the two rungs below borrow `md`, because a 10px radius
|
|
48
|
+
* on a 24px box reads as a lozenge.
|
|
49
|
+
*/
|
|
50
|
+
size: {
|
|
51
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
|
|
52
|
+
sm: "h-7 gap-1 rounded-md px-2.5 text-[0.8rem] [&_svg:not([class*='size-'])]:size-3.5",
|
|
53
|
+
md: "h-8 gap-1.5 rounded-lg px-3 text-sm",
|
|
54
|
+
lg: "h-9 gap-2 rounded-lg px-4 text-sm",
|
|
55
|
+
xl: "h-10 gap-2 rounded-lg px-6 text-sm",
|
|
56
|
+
},
|
|
57
|
+
/** A square box for a lone glyph, on whichever rung you are already on. No second ladder of `icon-sm` names to keep aligned with the first. */
|
|
58
|
+
icon: { true: "px-0", false: "" },
|
|
59
|
+
/** Full-round corners. Marketing surfaces; also every filter chip. */
|
|
60
|
+
pill: { true: "rounded-full", false: "" },
|
|
61
|
+
variant: {
|
|
62
|
+
solid: "bg-(--tone-fill) text-(color:--tone-ink) hover:bg-(--tone-fill-hover)",
|
|
63
|
+
soft: "bg-(--tone-wash) text-(color:--tone-hue) hover:bg-(--tone-wash-hover)",
|
|
64
|
+
outline: "border-(color:--tone-line) bg-background text-(color:--tone-hue) hover:bg-(--tone-wash)",
|
|
65
|
+
ghost: "text-(color:--tone-hue) hover:bg-(--tone-wash)",
|
|
66
|
+
// No box of its own: a button that reads as a link has to sit on the
|
|
67
|
+
// text baseline, not on a 32px control's centre line.
|
|
68
|
+
link: "h-auto gap-1 rounded-none px-0 py-0 text-(color:--tone-hue) underline-offset-4 hover:underline",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
compoundVariants: [
|
|
72
|
+
{ icon: true, size: "xs", class: "size-6" },
|
|
73
|
+
{ icon: true, size: "sm", class: "size-7" },
|
|
74
|
+
{ icon: true, size: "md", class: "size-8" },
|
|
75
|
+
{ icon: true, size: "lg", class: "size-9" },
|
|
76
|
+
{ icon: true, size: "xl", class: "size-10" },
|
|
77
|
+
],
|
|
78
|
+
defaultVariants: {
|
|
79
|
+
variant: "solid",
|
|
80
|
+
size: "md",
|
|
81
|
+
icon: false,
|
|
82
|
+
pill: false,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* The class list, for the handful of places that style someone else's element
|
|
87
|
+
* and cannot render a `Button` — a router `Link` inside a `not-found`, a
|
|
88
|
+
* calendar day cell. Takes the same props, including the implied tone.
|
|
89
|
+
*/
|
|
90
|
+
export function buttonVariants(props = {}) {
|
|
91
|
+
return button({ tone: props?.tone ?? impliedTone(props?.variant), ...props });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A non-`<button>` render element bypasses the primitive on purpose: Base UI
|
|
95
|
+
* always stamps `type="button"` or `role="button"`, and the latter drops an
|
|
96
|
+
* anchor out of screen-reader link navigation. Cloning gives it the classes and
|
|
97
|
+
* nothing else.
|
|
98
|
+
*/
|
|
99
|
+
export function Button({ className, variant, tone, size, icon, pill, render, nativeButton, ...props }) {
|
|
100
|
+
const resolved = tone ?? impliedTone(variant);
|
|
101
|
+
const classes = cn(button({ variant, tone: resolved, size, icon, pill, className }));
|
|
102
|
+
// The resolved axes, stamped: a child can style off its parent's tone, and a
|
|
103
|
+
// test can assert the ramp without asserting a class string.
|
|
104
|
+
const marks = {
|
|
105
|
+
"data-slot": "button",
|
|
106
|
+
"data-variant": variant ?? "solid",
|
|
107
|
+
"data-tone": resolved,
|
|
108
|
+
};
|
|
109
|
+
// `render.type !== "button"`: a plain <button/> still goes through the primitive, which
|
|
110
|
+
// is what supplies the native semantics.
|
|
111
|
+
const as = isValidElement(render) && render.type === "button"
|
|
112
|
+
? null
|
|
113
|
+
: renderAs(render, classes, { ...marks, ...props });
|
|
114
|
+
if (as)
|
|
115
|
+
return as;
|
|
116
|
+
return (_jsx(ButtonPrimitive, { ...marks, className: classes, render: render, nativeButton: nativeButton ?? true, ...props }));
|
|
117
|
+
}
|
package/dist/blocks/callout.d.ts
CHANGED
|
@@ -1,41 +1,5 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from "react";
|
|
2
|
-
|
|
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
|
-
*/
|
|
2
|
+
import { type Tone } from "../tone.js";
|
|
39
3
|
export declare function Callout({ icon: Icon, title, tone, density, bodyClassName, action, children, className, }: {
|
|
40
4
|
/** Injected, so the package needs no icon set. Optional: a notice whose title already reads as a label
|
|
41
5
|
* ("Replied into Norman's thread") gains nothing from a glyph beside it. */
|
|
@@ -43,7 +7,7 @@ export declare function Callout({ icon: Icon, title, tone, density, bodyClassNam
|
|
|
43
7
|
className?: string;
|
|
44
8
|
}>;
|
|
45
9
|
title?: ReactNode;
|
|
46
|
-
tone?:
|
|
10
|
+
tone?: Tone;
|
|
47
11
|
density?: "compact" | "editorial";
|
|
48
12
|
/** For the one body that is not prose — a raw delivery error, which needs mono and its own
|
|
49
13
|
* line breaks preserved. */
|
|
@@ -54,4 +18,3 @@ export declare function Callout({ icon: Icon, title, tone, density, bodyClassNam
|
|
|
54
18
|
children: ReactNode;
|
|
55
19
|
className?: string;
|
|
56
20
|
}): import("react").JSX.Element;
|
|
57
|
-
export {};
|
package/dist/blocks/callout.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../cn.js";
|
|
3
|
+
import { toneClass } from "../tone.js";
|
|
3
4
|
import { TypographyCaption, TypographyLabel, TypographyMuted, TypographySmall, } from "../typography/paragraph.js";
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
// An inline notice: a titled paragraph that explains something the surface it sits in cannot say
|
|
@@ -15,47 +16,21 @@ import { TypographyCaption, TypographyLabel, TypographyMuted, TypographySmall, }
|
|
|
15
16
|
// that just happened; these are quiet, permanent explanations sitting inside a panel, and they
|
|
16
17
|
// must not announce themselves to a screen reader every time a sheet opens.
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
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
19
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
20
|
+
* No tone table of its own: the same seven `Button` and `TypographyLink` take.
|
|
21
|
+
* The four hand-tuned rows this file used to carry are gone, `accent` among them
|
|
22
|
+
* — it was `--primary` under another name, which is why a "tip" callout and a
|
|
23
|
+
* "primary" one were indistinguishable. `muted` keeps its name and its values.
|
|
24
|
+
* See ../tone.ts.
|
|
25
|
+
*
|
|
26
|
+
* A callout is a panel, so it tints with `--tone-veil` (5%) where a control uses
|
|
27
|
+
* `--tone-wash` (10%). That is the only thing this file knows about colour.
|
|
54
28
|
*/
|
|
29
|
+
const BOX = "border-(color:--tone-line) bg-(--tone-veil)";
|
|
55
30
|
export function Callout({ icon: Icon, title, tone = "muted", density = "compact", bodyClassName, action, children, className, }) {
|
|
56
|
-
const
|
|
31
|
+
const toned = toneClass(tone);
|
|
57
32
|
if (density === "editorial") {
|
|
58
|
-
return (
|
|
33
|
+
return (_jsxs("div", { className: cn("relative overflow-hidden rounded-lg border py-3.5 pl-5 pr-4", toned, BOX, className), children: [_jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 left-0 w-[3px] bg-(--tone-line)" }), _jsxs("div", { className: "flex items-start gap-2.5", children: [Icon && (_jsx(Icon, { className: "mt-0.5 size-4 shrink-0 text-(color:--tone-hue)" })), _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [title && (_jsx(TypographyLabel, { className: "text-(color:--tone-hue)", 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
34
|
}
|
|
60
|
-
return (_jsxs("div", { className: cn("rounded-md border p-3",
|
|
35
|
+
return (_jsxs("div", { className: cn("rounded-md border p-3", toned, BOX, className), children: [title && (_jsxs(TypographySmall, { className: "flex items-center gap-1.5 font-medium text-(color:--tone-hue)", children: [Icon && _jsx(Icon, { className: "size-3.5 shrink-0" }), 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
36
|
}
|
package/dist/blocks/card.js
CHANGED
|
@@ -1,5 +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
|
+
import { FOCUS_RING } from "./focus.js";
|
|
4
|
+
import { toneClass } from "../tone.js";
|
|
3
5
|
import { Link } from "next-view-transitions";
|
|
4
6
|
/** Two columns from `sm` up: a pair reads as a set rather than two panels. */
|
|
5
7
|
export function Cards({ className, children, ...props }) {
|
|
@@ -13,6 +15,17 @@ export function Cards({ className, children, ...props }) {
|
|
|
13
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 " +
|
|
14
16
|
"has-[>img:first-child]:pt-0 " +
|
|
15
17
|
"*:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl";
|
|
18
|
+
/**
|
|
19
|
+
* What a card does when it is a link, which is the only time it does anything: two pixels
|
|
20
|
+
* up, a shadow under it, the ring firming from `--border` to a cut of the page's own ink.
|
|
21
|
+
* Following a link is not a colour, so nothing here is one — `--elevation-raised` is the
|
|
22
|
+
* token for a layer leaving the page plane, and that is the whole gesture. The lift is
|
|
23
|
+
* `motion-safe:` and the shadow is not, so reduced motion keeps the affordance.
|
|
24
|
+
*
|
|
25
|
+
* `toneClass` is declared here so the icon below can take `--tone-hue` rather than naming
|
|
26
|
+
* a token, the way every other tinted role in the package reads it.
|
|
27
|
+
*/
|
|
28
|
+
const CARD_LINK_CLASS = cn(toneClass("primary"), FOCUS_RING, "group/card no-underline transition duration-200 ease-out", "hover:shadow-raised hover:ring-foreground/15 motion-safe:hover:-translate-y-0.5");
|
|
16
29
|
export function CardHeader({ className, ...props }) {
|
|
17
30
|
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
31
|
}
|
|
@@ -40,7 +53,14 @@ export function CardContent({ className, ...props }) {
|
|
|
40
53
|
* the router's Link.
|
|
41
54
|
*/
|
|
42
55
|
export function Card({ href, className, external, title, description, icon, children, ...rest }) {
|
|
43
|
-
const header = title || description || icon ? (_jsxs(CardHeader, { children: [icon
|
|
56
|
+
const header = title || description || icon ? (_jsxs(CardHeader, { children: [icon || title ? (
|
|
57
|
+
// The icon sits on the title's line and is its mark; stacked, it read as a
|
|
58
|
+
// decoration the title happened to follow. `gap-2` is a gap between two
|
|
59
|
+
// objects, not the header's `gap-1` between two lines.
|
|
60
|
+
_jsxs("div", { className: "flex items-center gap-2", children: [icon ? (
|
|
61
|
+
// Sized here, not at the call site, so two cards cannot disagree about how
|
|
62
|
+
// big an icon is. On a link card it takes the tone as the card lifts.
|
|
63
|
+
_jsx("span", { className: "shrink-0 text-muted-foreground transition-colors group-hover/card:text-(color:--tone-hue) [&_svg]:size-4 [&_svg]:shrink-0", children: icon })) : null, title ? _jsx(CardTitle, { children: title }) : null] })) : null, description ? _jsx(CardDescription, { children: description }) : null] })) : null;
|
|
44
64
|
// Bare children compose; children under a shorthand header are body copy.
|
|
45
65
|
const body = header ? (_jsxs(_Fragment, { children: [header, children ? _jsx(CardContent, { children: children }) : null] })) : (children);
|
|
46
66
|
const shared = { "data-slot": "card" };
|
|
@@ -48,7 +68,7 @@ export function Card({ href, className, external, title, description, icon, chil
|
|
|
48
68
|
return (_jsx("div", { className: cn(CARD_CLASS, className), ...shared, ...rest, children: body }));
|
|
49
69
|
}
|
|
50
70
|
const leavesApp = external ?? /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
51
|
-
const classes = cn(CARD_CLASS,
|
|
71
|
+
const classes = cn(CARD_CLASS, CARD_LINK_CLASS, className);
|
|
52
72
|
if (leavesApp) {
|
|
53
73
|
return (_jsx("a", { href: href, className: classes, target: "_blank", rel: "noopener noreferrer", ...shared, ...rest, children: body }));
|
|
54
74
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The focus ring, stated once.
|
|
3
|
+
*
|
|
4
|
+
* It was five copies — Button, Badge, Accordion, `SEGMENT.item`, and Card, which
|
|
5
|
+
* added the fifth while its own comment observed that the other four existed.
|
|
6
|
+
* Retuning the width or the ink was a find-and-replace across five files with no
|
|
7
|
+
* compiler help if one was missed.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FOCUS_RING = "outline-none focus-visible:ring-3 focus-visible:ring-ring/50";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The focus ring, stated once.
|
|
3
|
+
*
|
|
4
|
+
* It was five copies — Button, Badge, Accordion, `SEGMENT.item`, and Card, which
|
|
5
|
+
* added the fifth while its own comment observed that the other four existed.
|
|
6
|
+
* Retuning the width or the ink was a find-and-replace across five files with no
|
|
7
|
+
* compiler help if one was missed.
|
|
8
|
+
*/
|
|
9
|
+
export const FOCUS_RING = "outline-none focus-visible:ring-3 focus-visible:ring-ring/50";
|
package/dist/blocks/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { Cards, Card, CardHeader, CardTitle, CardDescription, CardContent, } fro
|
|
|
2
2
|
export { Disclosure, DisclosureGroup } from "./accordion.js";
|
|
3
3
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "./interactive-accordion.js";
|
|
4
4
|
export { Callout } from "./callout.js";
|
|
5
|
-
export {
|
|
5
|
+
export { Button, buttonVariants, type ButtonLook } from "./button.js";
|
|
6
|
+
export { Badge, badgeVariants, type BadgeLook } from "./badge.js";
|
|
7
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, TabGroup, type TabItem, } from "./tabs.js";
|
|
6
8
|
export { SEGMENT } from "./segment.js";
|
|
7
9
|
export { Steps, Step } from "./steps.js";
|
package/dist/blocks/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export { Cards, Card, CardHeader, CardTitle, CardDescription, CardContent, } fro
|
|
|
2
2
|
export { Disclosure, DisclosureGroup } from "./accordion.js";
|
|
3
3
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent, } from "./interactive-accordion.js";
|
|
4
4
|
export { Callout } from "./callout.js";
|
|
5
|
-
export {
|
|
5
|
+
export { Button, buttonVariants } from "./button.js";
|
|
6
|
+
export { Badge, badgeVariants } from "./badge.js";
|
|
7
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, TabGroup, } from "./tabs.js";
|
|
6
8
|
export { SEGMENT } from "./segment.js";
|
|
7
9
|
export { Steps, Step } from "./steps.js";
|
|
@@ -8,6 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
10
10
|
import { cn } from "../cn.js";
|
|
11
|
+
import { FOCUS_RING } from "./focus.js";
|
|
11
12
|
function Accordion({ className, ...props }) {
|
|
12
13
|
return (_jsx(AccordionPrimitive.Root, { "data-slot": "accordion", className: cn("flex w-full flex-col", className), ...props }));
|
|
13
14
|
}
|
|
@@ -15,7 +16,7 @@ function AccordionItem({ className, ...props }) {
|
|
|
15
16
|
return (_jsx(AccordionPrimitive.Item, { "data-slot": "accordion-item", className: cn("not-last:border-b", className), ...props }));
|
|
16
17
|
}
|
|
17
18
|
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
|
|
19
|
+
return (_jsx(AccordionPrimitive.Header, { className: "flex", children: _jsxs(AccordionPrimitive.Trigger, { "data-slot": "accordion-trigger", className: cn(FOCUS_RING, "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 hover:underline focus-visible:border-ring 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
|
}
|
|
20
21
|
function AccordionContent({ className, children, ...props }) {
|
|
21
22
|
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 }) }));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* `render={<a href="…" />}` — the component's classes and data marks put onto an element
|
|
4
|
+
* the caller supplies, so a badge or a button can BE a link rather than wrap one. A
|
|
5
|
+
* screen reader announces the element, and the element is the anchor.
|
|
6
|
+
*
|
|
7
|
+
* Shared because Button and Badge both do it, identically, for the same reason; both
|
|
8
|
+
* files' comments used to say so while carrying their own copy. Returns `null` when
|
|
9
|
+
* `render` is not an element, which is the caller's signal to render its own tag.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderAs(render: unknown, classes: string, props: Record<string, unknown>): ReactElement | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cloneElement, isValidElement } from "react";
|
|
2
|
+
import { cn } from "../cn.js";
|
|
3
|
+
/**
|
|
4
|
+
* `render={<a href="…" />}` — the component's classes and data marks put onto an element
|
|
5
|
+
* the caller supplies, so a badge or a button can BE a link rather than wrap one. A
|
|
6
|
+
* screen reader announces the element, and the element is the anchor.
|
|
7
|
+
*
|
|
8
|
+
* Shared because Button and Badge both do it, identically, for the same reason; both
|
|
9
|
+
* files' comments used to say so while carrying their own copy. Returns `null` when
|
|
10
|
+
* `render` is not an element, which is the caller's signal to render its own tag.
|
|
11
|
+
*/
|
|
12
|
+
export function renderAs(render, classes, props) {
|
|
13
|
+
// `unknown`, because Base UI's `render` is an element OR a render function; only the
|
|
14
|
+
// element half is ours to clone, and `isValidElement` is the narrowing.
|
|
15
|
+
if (!isValidElement(render))
|
|
16
|
+
return null;
|
|
17
|
+
const { className } = render.props;
|
|
18
|
+
return cloneElement(render, {
|
|
19
|
+
...props,
|
|
20
|
+
className: cn(classes, className),
|
|
21
|
+
});
|
|
22
|
+
}
|
package/dist/blocks/segment.d.ts
CHANGED
|
@@ -9,29 +9,37 @@
|
|
|
9
9
|
* class strings, which is exactly the kind of duplication that drifts the first time
|
|
10
10
|
* someone retunes the palette.
|
|
11
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
12
|
* The active segment is deliberately flat — a card surface and a hairline, no shadow.
|
|
18
13
|
* Elevation in this system means "this layer left the page plane" (see --elevation-* in
|
|
19
14
|
* theme.css), and a segment sitting inside its own track has not.
|
|
15
|
+
*
|
|
16
|
+
* The other half of that: the segment stays on the page plane because the RAIL drops below
|
|
17
|
+
* it. The well is the shadow and the hairline, not a fill — the track is `bg-background`
|
|
18
|
+
* pressed in. A `--muted` rail cannot work, because `--muted` sits below `--card` in light
|
|
19
|
+
* and above it in dark, so a muted fill reads correct in one theme and inverted in the
|
|
20
|
+
* other, and `dark:` is what the package's own ESLint rule exists to stop.
|
|
20
21
|
*/
|
|
21
22
|
export declare const SEGMENT: {
|
|
22
|
-
/**
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The rail a set of segments sits in. Surface only; each component owns its layout.
|
|
25
|
+
*
|
|
26
|
+
* `rounded-md` over `activeSurface`'s `rounded-sm` is the 2px of `p-0.5`: concentric
|
|
27
|
+
* radii, so the segment's corner runs parallel to the rail's rather than across it.
|
|
28
|
+
*/
|
|
29
|
+
readonly track: "rounded-md border border-border bg-background p-0.5 shadow-recessed";
|
|
24
30
|
/** Affordances every segment shares, whatever its shape or engine. */
|
|
25
|
-
readonly item: "relative inline-flex items-center gap-1.5 font-medium outline-none
|
|
31
|
+
readonly item: "relative inline-flex items-center gap-1.5 font-medium transition-colors outline-none focus-visible:ring-3 focus-visible:ring-ring/50";
|
|
26
32
|
/** Selected: the ink lifts to full strength. */
|
|
27
33
|
readonly active: "text-foreground";
|
|
28
34
|
/**
|
|
29
35
|
* Unselected: quiet, but lighting its own surface on hover so the whole strip reads as
|
|
30
|
-
* reachable rather than only the segment already chosen.
|
|
36
|
+
* reachable rather than only the segment already chosen. It moves toward `activeSurface`
|
|
37
|
+
* and not toward the rail, so a hover previews being picked.
|
|
38
|
+
*/
|
|
39
|
+
readonly idle: "text-muted-foreground hover:bg-card/60 hover:text-foreground";
|
|
40
|
+
/**
|
|
41
|
+
* The flat surface marking the selection. One string, worn by both engines: the marketing
|
|
42
|
+
* picker slides it with `motion`, `Tabs` hands it to the element Base UI positions.
|
|
31
43
|
*/
|
|
32
|
-
readonly
|
|
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";
|
|
44
|
+
readonly activeSurface: "rounded-sm bg-card ring-1 ring-border";
|
|
37
45
|
};
|