@supertype.ai/foundations 0.1.31 → 0.1.32

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 CHANGED
@@ -66,7 +66,7 @@ untagged git dependency resolves to a different commit on a fresh install.
66
66
 
67
67
  ```jsonc
68
68
  // package.json
69
- "@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.1.31"
69
+ "@supertype.ai/foundations": "https://github.com/supertypeai/foundations.git#v0.1.32"
70
70
  ```
71
71
 
72
72
  </details>
@@ -62,7 +62,7 @@ cn(FOCUS_RING, "focus-visible:border-ring"), "active:not-aria-[haspopup]:transla
62
62
  variant: {
63
63
  solid: `bg-(--tone-fill) text-(color:--tone-ink) hover:bg-(--tone-fill-hover) ${INK_ON_FILL}`,
64
64
  soft: "bg-(--tone-wash) text-(color:--tone-hue) hover:bg-(--tone-wash-hover)",
65
- outline: "border-(color:--tone-line) bg-background text-(color:--tone-hue) hover:bg-(--tone-wash)",
65
+ outline: "border-(color:--tone-line) text-(color:--tone-hue) hover:bg-(--tone-wash)",
66
66
  ghost: "text-(color:--tone-hue) hover:bg-(--tone-wash)",
67
67
  // No box of its own: a button that reads as a link has to sit on the
68
68
  // text baseline, not on a 32px control's centre line.
@@ -12,6 +12,10 @@ import { TypographyCaption, TypographyLabel, TypographyMuted, TypographySmall, }
12
12
  // already drifted on the radius. Hand-rolled type styles are also exactly what the project's own
13
13
  // guidance forbids, and six copies is how a rule like that gets broken without anyone deciding to.
14
14
  //
15
+ // The body is a slot, so it renders as a div in both densities. A caller passes a list, a
16
+ // pair of paragraphs or a mono block of delivery errors, none of which may sit inside a <p>:
17
+ // the parser closes it early and React reports a hydration error on a callout that looks fine.
18
+ //
15
19
  // Deliberately not a shadcn Alert. Alert is a page-level, role="alert" affordance for something
16
20
  // that just happened; these are quiet, permanent explanations sitting inside a panel, and they
17
21
  // must not announce themselves to a screen reader every time a sheet opens.
@@ -30,7 +34,7 @@ const BOX = "border-(color:--tone-line) bg-(--tone-veil)";
30
34
  export function Callout({ icon: Icon, title, tone = "muted", density = "compact", bodyClassName, action, children, className, }) {
31
35
  const toned = toneClass(tone);
32
36
  if (density === "editorial") {
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 }))] })] })] }));
37
+ 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, { as: "div", className: cn("leading-relaxed", bodyClassName), children: children }), action && (_jsx("div", { className: "mt-1 flex items-center gap-1", children: action }))] })] })] }));
34
38
  }
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 })] }));
39
+ 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, { as: "div", className: cn("mt-1 leading-relaxed", bodyClassName), children: children }), action && _jsx("div", { className: "mt-2 flex items-center gap-1", children: action })] }));
36
40
  }
@@ -11,12 +11,19 @@ declare const pVariants: (props?: ({
11
11
  tone?: "muted" | "default" | null | undefined;
12
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
13
13
  export type ParagraphVariants = VariantProps<typeof pVariants>;
14
- export declare function TypographyP({ className, variant, tone, children, ...props }: ComponentProps<"p"> & ParagraphVariants): import("react").JSX.Element;
14
+ /**
15
+ * `as` is here for the reason the caption has it, one step further on: a
16
+ * component that hands its body to a caller cannot know whether what arrives is
17
+ * a sentence or a list, and a paragraph may hold neither a list nor a div. The
18
+ * HTML parser closes the `<p>` early and React reports a hydration error, so
19
+ * every wrapper of that shape (`Callout` was the one) renders `as="div"`.
20
+ */
21
+ export declare function TypographyP({ className, variant, tone, as, children, ...props }: WithAs<ParagraphVariants>): import("react").JSX.Element;
15
22
  /** A preset's props: its base's, minus the axes it has decided. `keyof Pins` reads
16
23
  * the exclusion off the pinned object the preset also spreads, so the two cannot
17
24
  * drift — `<TypographyMuted tone="default">` used to compile and un-mute it. */
18
25
  type Preset<Base, Pins> = Omit<Base, keyof Pins>;
19
- type ParagraphProps = ComponentProps<"p"> & ParagraphVariants;
26
+ type ParagraphProps = WithAs<ParagraphVariants>;
20
27
  /** The UI rung in the secondary ink. */
21
28
  declare const MUTED: {
22
29
  readonly tone: "muted";
@@ -25,8 +25,15 @@ const pVariants = cva("", {
25
25
  },
26
26
  defaultVariants: { variant: "ui", tone: "default" },
27
27
  });
28
- export function TypographyP({ className, variant, tone, children, ...props }) {
29
- return (_jsx("p", { className: cn(pVariants({ variant, tone }), className), ...props, children: children }));
28
+ /**
29
+ * `as` is here for the reason the caption has it, one step further on: a
30
+ * component that hands its body to a caller cannot know whether what arrives is
31
+ * a sentence or a list, and a paragraph may hold neither a list nor a div. The
32
+ * HTML parser closes the `<p>` early and React reports a hydration error, so
33
+ * every wrapper of that shape (`Callout` was the one) renders `as="div"`.
34
+ */
35
+ export function TypographyP({ className, variant, tone, as = "p", children, ...props }) {
36
+ return (_jsx(TextAs, { as: as, className: cn(pVariants({ variant, tone }), className), ...props, children: children }));
30
37
  }
31
38
  /** The UI rung in the secondary ink. */
32
39
  const MUTED = { tone: "muted" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supertype.ai/foundations",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public",
package/src/type.css CHANGED
@@ -71,3 +71,33 @@
71
71
  .editorial.font-heading {
72
72
  font-weight: var(--heading-weight);
73
73
  }
74
+
75
+ /* A glyph set beside words, sized and seated so it belongs to the line.
76
+
77
+ Two numbers, both read off the font rather than chosen. Lucide draws its ink
78
+ across 22 of its 24 grid units, so a 0.8em box puts that ink at 1.02 times the
79
+ cap height of whatever rung it sits beside.
80
+
81
+ The seat corrects a bias the browser introduces. Centring aligns a glyph on the
82
+ middle of the line box, and the line box is built from the font's ascent and
83
+ descent rounded to whole pixels, which lands its middle below the middle of the
84
+ capitals: 0.31px low at the 11px rung, 0.46px at the 10px, 0.50px at the 13px.
85
+ So a centred glyph is always seated slightly low, by an amount that changes with
86
+ the rung, which is why hand nudges get written one place at a time and never
87
+ generalise. Lifting by 0.035em takes it back onto the cap band across the ramp.
88
+
89
+ A box pinned in pixels cannot hold the first number either, because the band it
90
+ has to match moves with the rung. Twelve pixels is 1.39 times the cap height at
91
+ the 11px rung and 1.53 at the 10px one, so the glyph climbs over the ascenders
92
+ and hangs well under the baseline while every letter beside it rests on it.
93
+
94
+ For a glyph inline with words. A glyph that stands alone, in a button, a nav
95
+ rail or an avatar slot, has no band to answer to and keeps a fixed size.
96
+
97
+ The 22-of-24 figure is lucide's grid. An app on a different icon set wants a
98
+ different box for the same ratio. */
99
+ @utility icon-inline {
100
+ width: 0.8em;
101
+ height: 0.8em;
102
+ translate: 0 -0.035em;
103
+ }