@tangle-network/ui 11.34.0 → 11.36.0

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +4 -3
  4. package/dist/index.js +2 -1
  5. package/dist/lib/control-presentation.d.ts +34 -1
  6. package/dist/lib/control-presentation.js +1 -1
  7. package/dist/lib/focus.d.ts +2 -2
  8. package/dist/lib/focus.js +3 -2
  9. package/dist/lib/motion.d.ts +4 -0
  10. package/dist/lib/motion.js +7 -0
  11. package/dist/lib/surface-motion.js +84 -0
  12. package/dist/presentation.d.ts +88 -0
  13. package/dist/presentation.js +183 -0
  14. package/dist/primitives/alert-dialog.js +1 -1
  15. package/dist/primitives/alert.d.ts +1 -2
  16. package/dist/primitives/button.d.ts +2 -5
  17. package/dist/primitives/button.js +3 -30
  18. package/dist/primitives/card.js +3 -7
  19. package/dist/primitives/date-picker.d.ts +1 -1
  20. package/dist/primitives/dialog.js +29 -16
  21. package/dist/primitives/empty-state.js +6 -5
  22. package/dist/primitives/heading.d.ts +1 -0
  23. package/dist/primitives/heading.js +3 -2
  24. package/dist/primitives/help-text.js +2 -1
  25. package/dist/primitives/input.d.ts +1 -4
  26. package/dist/primitives/input.js +3 -24
  27. package/dist/primitives/label.js +1 -2
  28. package/dist/primitives/metric-strip.js +7 -12
  29. package/dist/primitives/page-header.d.ts +1 -0
  30. package/dist/primitives/page-header.js +7 -6
  31. package/dist/primitives/page-shell.d.ts +8 -2
  32. package/dist/primitives/page-shell.js +21 -2
  33. package/dist/primitives/pagination.js +1 -1
  34. package/dist/primitives/segmented-control.d.ts +6 -3
  35. package/dist/primitives/segmented-control.js +105 -56
  36. package/dist/primitives/sheet.js +29 -19
  37. package/dist/primitives/skeleton.js +1 -1
  38. package/dist/primitives/status-pill.d.ts +3 -1
  39. package/dist/primitives/status-pill.js +12 -3
  40. package/dist/primitives/table.js +10 -9
  41. package/dist/primitives/tabs.js +3 -12
  42. package/dist/primitives/tooltip.js +11 -3
  43. package/dist/primitives.d.ts +3 -2
  44. package/dist/primitives.js +2 -1
  45. package/dist/utils.d.ts +1 -1
  46. package/package.json +15 -5
  47. package/src/lib/control-presentation.ts +2 -4
  48. package/src/lib/focus.ts +3 -2
  49. package/src/lib/motion.ts +7 -0
  50. package/src/lib/surface-motion.tsx +78 -0
  51. package/src/presentation.ts +205 -0
  52. package/src/primitives/alert.tsx +1 -2
  53. package/src/primitives/button.tsx +4 -40
  54. package/src/primitives/card.tsx +5 -10
  55. package/src/primitives/checkbox-separator-sheet-tooltip.test.tsx +2 -1
  56. package/src/primitives/control-presentation.test.tsx +2 -7
  57. package/src/primitives/dialog.tsx +19 -18
  58. package/src/primitives/empty-state.tsx +6 -5
  59. package/src/primitives/heading.tsx +4 -2
  60. package/src/primitives/help-text.tsx +3 -2
  61. package/src/primitives/input.tsx +3 -31
  62. package/src/primitives/label.tsx +2 -4
  63. package/src/primitives/metric-strip.tsx +8 -17
  64. package/src/primitives/page-header.tsx +8 -5
  65. package/src/primitives/page-shell.tsx +13 -2
  66. package/src/primitives/segmented-control.stories.tsx +89 -0
  67. package/src/primitives/segmented-control.test.tsx +20 -7
  68. package/src/primitives/segmented-control.tsx +90 -29
  69. package/src/primitives/sheet.tsx +16 -11
  70. package/src/primitives/skeleton.tsx +1 -1
  71. package/src/primitives/status-pill.test.tsx +1 -0
  72. package/src/primitives/status-pill.tsx +10 -4
  73. package/src/primitives/table.tsx +10 -9
  74. package/src/primitives/tabs.tsx +5 -17
  75. package/src/primitives/tooltip.tsx +6 -5
  76. package/src/styles/compat.css +12 -0
  77. package/src/styles/presentation.css +30 -0
  78. package/src/primitives/page-shell.test.tsx +0 -24
@@ -1,40 +1,11 @@
1
1
  import { ixControl, ixControlSoft, ixSelected } from "../lib/interaction.js";
2
2
  import { focusRing } from "../lib/focus.js";
3
3
  import { cn } from "../lib/utils.js";
4
- import { controlHeight, controlMotion, controlRadius, controlSizes, controlSquare, controlText } from "../lib/control-presentation.js";
4
+ import { buttonVariants } from "../presentation.js";
5
5
  import { Slot } from "@radix-ui/react-slot";
6
- import { cva } from "class-variance-authority";
7
6
  import * as React from "react";
8
7
  import { jsx, jsxs } from "react/jsx-runtime";
9
8
  //#region src/primitives/button.tsx
10
- const buttonVariants = cva(`inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-[color,background-color,border-color,box-shadow,transform,scale] ${controlMotion} ${focusRing} disabled:pointer-events-none disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`, {
11
- variants: {
12
- variant: {
13
- default: `bg-primary text-primary-foreground hover:bg-primary/90 active:scale-[0.97] ${ixControl}`,
14
- destructive: `bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.97] ${ixControl}`,
15
- outline: `border border-border bg-card hover:bg-muted active:scale-[0.97] text-foreground ${ixControl}`,
16
- secondary: `bg-muted border border-border text-foreground hover:bg-muted/80 active:scale-[0.97] ${ixControl}`,
17
- ghost: `hover:text-foreground text-muted-foreground border border-transparent ${ixControlSoft}`,
18
- link: `text-[var(--accent-text)] underline-offset-4 hover:underline ${ixControlSoft}`,
19
- sandbox: `bg-[var(--btn-primary-bg)] text-[var(--btn-primary-text)] border border-[var(--border-accent)] hover:bg-[var(--btn-primary-hover)] active:scale-[0.97] ${ixControl}`
20
- },
21
- size: {
22
- default: `${controlHeight.md} ${controlText.md} ${controlRadius.md} px-4 py-2`,
23
- md: `${controlHeight.md} ${controlText.md} ${controlRadius.md} px-4 py-2`,
24
- sm: `${controlHeight.sm} ${controlText.sm} ${controlRadius.sm} px-3`,
25
- lg: `${controlHeight.lg} ${controlText.lg} ${controlRadius.lg} px-7`,
26
- xl: "h-13 rounded-xl px-9 text-base",
27
- icon: `${controlSquare.md} ${controlText.md} ${controlRadius.md}`,
28
- "icon-sm": `${controlSquare.sm} ${controlText.sm} ${controlRadius.sm}`,
29
- "icon-lg": `${controlSquare.lg} ${controlText.lg} ${controlRadius.lg}`,
30
- ...controlSizes
31
- }
32
- },
33
- defaultVariants: {
34
- variant: "default",
35
- size: "default"
36
- }
37
- });
38
9
  const bareButton = `${focusRing} ${ixControlSoft} disabled:pointer-events-none disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50`;
39
10
  /** One Button render's classes: variant and size, the toggle states, then the caller's. */
40
11
  function buttonClassName(variant, size, pressed, className) {
@@ -72,6 +43,7 @@ const Button = React.forwardRef(({ className, variant, size, asChild = false, lo
72
43
  return /* @__PURE__ */ jsx(Slot, {
73
44
  className: buttonClassName(variant, size, props["aria-pressed"], className),
74
45
  ref,
46
+ "data-variant": size === "display" ? variant ?? "default" : void 0,
75
47
  ...props,
76
48
  ...isDisabled ? disabledHandlers(props) : {},
77
49
  children: child ? React.cloneElement(child, {
@@ -91,6 +63,7 @@ const Button = React.forwardRef(({ className, variant, size, asChild = false, lo
91
63
  return /* @__PURE__ */ jsxs("button", {
92
64
  className: buttonClassName(variant, size, props["aria-pressed"], className),
93
65
  ref,
66
+ "data-variant": size === "display" ? variant ?? "default" : void 0,
94
67
  ...props,
95
68
  disabled: disabled || loading,
96
69
  "aria-busy": loading || props["aria-busy"],
@@ -1,5 +1,6 @@
1
1
  import "../lib/interaction.js";
2
2
  import { cn } from "../lib/utils.js";
3
+ import { cardPresentation } from "../presentation.js";
3
4
  import { Heading } from "./heading.js";
4
5
  import * as React from "react";
5
6
  import { jsx } from "react/jsx-runtime";
@@ -15,12 +16,7 @@ import { jsx } from "react/jsx-runtime";
15
16
  const Card = React.forwardRef(({ className, variant = "default", hover = false, interactive = false, ...props }, ref) => {
16
17
  return /* @__PURE__ */ jsx("div", {
17
18
  ref,
18
- className: cn("rounded-[var(--radius-lg)] border text-card-foreground transition-[border-color,box-shadow,background-color]", "duration-[var(--transition-default)]", {
19
- default: "bg-card border-border shadow-[var(--edge-highlight),var(--shadow-card)]",
20
- elevated: "bg-muted/50 border-border shadow-[var(--shadow-card)]",
21
- glass: "bg-card/80 backdrop-blur-xl border-border shadow-[var(--shadow-card)]",
22
- sandbox: "bg-muted/50 border-primary/20 shadow-[var(--shadow-accent)]"
23
- }[variant], (hover || interactive) && "hover:border-[var(--ix-hover-border)] hover:shadow-[var(--ix-hover)] focus:not-focus-visible:border-[var(--ix-hover-border)] focus:not-focus-visible:shadow-[var(--ix-after)] active:border-[hsl(var(--hsl-ring))] active:shadow-[var(--ix-press)] focus-visible:shadow-none", className),
19
+ className: cn(cardPresentation.root, cardPresentation.variants[variant], (hover || interactive) && "hover:border-[var(--ix-hover-border)] hover:shadow-[var(--ix-hover)] focus:not-focus-visible:border-[var(--ix-hover-border)] focus:not-focus-visible:shadow-[var(--ix-after)] active:border-[hsl(var(--hsl-ring))] active:shadow-[var(--ix-press)] focus-visible:shadow-none", className),
24
20
  "data-slot": "card",
25
21
  ...props
26
22
  });
@@ -48,7 +44,7 @@ const CardDescription = React.forwardRef(({ className, ...props }, ref) => /* @_
48
44
  CardDescription.displayName = "CardDescription";
49
45
  const CardContent = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
50
46
  ref,
51
- className: cn("p-4 [&:not(:first-child)]:pt-0", className),
47
+ className: cn(cardPresentation.padding, "[&:not(:first-child)]:pt-0", className),
52
48
  ...props
53
49
  }));
54
50
  CardContent.displayName = "CardContent";
@@ -1,5 +1,5 @@
1
- import { CalendarDate, CalendarProps } from "./calendar.js";
2
1
  import { ControlSize } from "../lib/control-presentation.js";
2
+ import { CalendarDate, CalendarProps } from "./calendar.js";
3
3
  import * as React from "react";
4
4
  //#region src/primitives/date-picker.d.ts
5
5
  export interface DatePickerProps extends Pick<CalendarProps, "min" | "max" | "isDateDisabled" | "today" | "weekStartsOn" | "locale" | "defaultMonth"> {
@@ -1,5 +1,7 @@
1
1
  import { focusRing } from "../lib/focus.js";
2
2
  import { cn } from "../lib/utils.js";
3
+ import { dialogPresentation } from "../presentation.js";
4
+ import { SurfaceMotion } from "../lib/surface-motion.js";
3
5
  import * as React from "react";
4
6
  import { jsx, jsxs } from "react/jsx-runtime";
5
7
  import * as DialogPrimitive from "@radix-ui/react-dialog";
@@ -9,49 +11,60 @@ const Dialog = DialogPrimitive.Root;
9
11
  const DialogTrigger = DialogPrimitive.Trigger;
10
12
  const DialogPortal = DialogPrimitive.Portal;
11
13
  const DialogClose = DialogPrimitive.Close;
12
- const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
14
+ const DialogOverlay = React.forwardRef(({ className, asChild, children, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
13
15
  ref,
14
- className: cn("fixed inset-0 z-50 bg-black/40", "data-[state=closed]:animate-out data-[state=open]:animate-in", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className),
15
- ...props
16
+ className: cn(dialogPresentation.overlay, dialogPresentation.transition, className),
17
+ ...props,
18
+ asChild: true,
19
+ children: /* @__PURE__ */ jsx(SurfaceMotion, {
20
+ kind: "overlay",
21
+ asChild,
22
+ children
23
+ })
16
24
  }));
17
25
  DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
18
- const DialogContent = React.forwardRef(({ className, children, variant = "default", hideCloseButton = false, ...props }, ref) => {
19
- return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
26
+ const DialogContent = React.forwardRef(({ className, children, variant = "default", hideCloseButton = false, asChild, ...props }, ref) => {
27
+ return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsx(DialogPrimitive.Content, {
20
28
  ref,
21
- className: cn("fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%]", "gap-4 rounded-lg border bg-card p-6 shadow-lg duration-200", "data-[state=closed]:animate-out data-[state=open]:animate-in", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]", "data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]", {
29
+ className: cn(dialogPresentation.position, dialogPresentation.surface, dialogPresentation.transition, {
22
30
  default: "border-border",
23
31
  sandbox: "border-[var(--border-accent)] shadow-[var(--shadow-accent)]"
24
32
  }[variant], className),
25
33
  ...props,
26
- children: [children, !hideCloseButton && /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
27
- className: cn("absolute top-4 right-4 rounded-sm opacity-70 transition-[opacity,box-shadow] hover:opacity-100 focus-visible:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", focusRing),
28
- children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
29
- className: "sr-only",
30
- children: "Close"
34
+ asChild: true,
35
+ children: /* @__PURE__ */ jsxs(SurfaceMotion, {
36
+ kind: "dialog",
37
+ asChild,
38
+ children: [children, !hideCloseButton && /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
39
+ className: cn(dialogPresentation.close, focusRing),
40
+ children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
41
+ className: "sr-only",
42
+ children: "Close"
43
+ })]
31
44
  })]
32
- })]
45
+ })
33
46
  })] });
34
47
  });
35
48
  DialogContent.displayName = DialogPrimitive.Content.displayName;
36
49
  const DialogHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
37
- className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className),
50
+ className: cn(dialogPresentation.header, className),
38
51
  ...props
39
52
  });
40
53
  DialogHeader.displayName = "DialogHeader";
41
54
  const DialogFooter = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
42
- className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className),
55
+ className: cn(dialogPresentation.footer, className),
43
56
  ...props
44
57
  });
45
58
  DialogFooter.displayName = "DialogFooter";
46
59
  const DialogTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Title, {
47
60
  ref,
48
- className: cn("font-semibold text-lg leading-none tracking-tight", className),
61
+ className: cn(dialogPresentation.title, className),
49
62
  ...props
50
63
  }));
51
64
  DialogTitle.displayName = DialogPrimitive.Title.displayName;
52
65
  const DialogDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, {
53
66
  ref,
54
- className: cn("text-muted-foreground text-sm", className),
67
+ className: cn(dialogPresentation.description, className),
55
68
  ...props
56
69
  }));
57
70
  DialogDescription.displayName = DialogPrimitive.Description.displayName;
@@ -1,27 +1,28 @@
1
1
  import { cn } from "../lib/utils.js";
2
+ import { emptyStatePresentation } from "../presentation.js";
2
3
  import * as React from "react";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/primitives/empty-state.tsx
5
6
  const EmptyState = React.forwardRef(({ className, icon, title, description, action, titleAs: Title = "h3", ...props }, ref) => {
6
7
  return /* @__PURE__ */ jsxs("div", {
7
8
  ref,
8
- className: cn("flex flex-col items-center justify-center px-4 py-16 text-center", className),
9
+ className: cn(emptyStatePresentation.root, className),
9
10
  ...props,
10
11
  children: [
11
12
  icon && /* @__PURE__ */ jsx("div", {
12
- className: "mb-4 rounded-full bg-muted p-4 text-muted-foreground",
13
+ className: emptyStatePresentation.icon,
13
14
  children: icon
14
15
  }),
15
16
  /* @__PURE__ */ jsx(Title, {
16
- className: "font-semibold text-lg",
17
+ className: emptyStatePresentation.title,
17
18
  children: title
18
19
  }),
19
20
  description && /* @__PURE__ */ jsx("p", {
20
- className: "mt-2 max-w-sm text-muted-foreground text-sm",
21
+ className: emptyStatePresentation.description,
21
22
  children: description
22
23
  }),
23
24
  action && /* @__PURE__ */ jsx("div", {
24
- className: "mt-6",
25
+ className: emptyStatePresentation.action,
25
26
  children: action
26
27
  })
27
28
  ]
@@ -5,6 +5,7 @@ export type HeadingVariant = "display" | "hero" | "page" | "section" | "subsecti
5
5
  export interface HeadingProps extends Omit<React.HTMLAttributes<HTMLElement>, "role"> {
6
6
  /** Visual treatment, independent of the rendered element. Defaults to section. */
7
7
  variant?: HeadingVariant;
8
+ presentation?: "operational" | "expressive";
8
9
  /** Semantic element; changing it does not change the visual variant. */
9
10
  as?: React.ElementType;
10
11
  /**
@@ -22,13 +22,14 @@ const DEFAULT_TAG = {
22
22
  * One renderer for page, section and card titles. Use at most one default
23
23
  * display/page h1 per page; choose `as` according to the document hierarchy.
24
24
  */
25
- const Heading = React.forwardRef(({ variant, role, as, className, ...props }, ref) => {
25
+ const Heading = React.forwardRef(({ variant, role, as, className, presentation, ...props }, ref) => {
26
26
  const legacyVariant = role && Object.prototype.hasOwnProperty.call(VARIANT_CLASS, role) ? role : void 0;
27
27
  const visualVariant = variant ?? legacyVariant ?? "section";
28
28
  const Tag = as ?? DEFAULT_TAG[visualVariant];
29
29
  return /* @__PURE__ */ jsx(Tag, {
30
30
  ref,
31
- className: cn("[overflow-wrap:anywhere]", VARIANT_CLASS[visualVariant], className),
31
+ className: cn("[overflow-wrap:anywhere]", presentation ? presentation === "expressive" ? "tangle-display" : "tangle-heading" : VARIANT_CLASS[visualVariant], className),
32
+ "data-variant": presentation ? visualVariant : void 0,
32
33
  role: legacyVariant ? void 0 : role,
33
34
  ...props
34
35
  });
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../lib/utils.js";
2
+ import { helpPresentation } from "../presentation.js";
2
3
  import * as React from "react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region src/primitives/help-text.tsx
@@ -10,7 +11,7 @@ import { jsx } from "react/jsx-runtime";
10
11
  const HelpText = React.forwardRef(({ className, tone = "hint", ...props }, ref) => /* @__PURE__ */ jsx("p", {
11
12
  ref,
12
13
  "data-tone": tone,
13
- className: cn("text-[length:var(--font-size-help,0.75rem)] leading-4", tone === "error" ? "font-medium text-[var(--surface-danger-text)]" : "text-[var(--text-dim)]", className),
14
+ className: cn(helpPresentation.root, helpPresentation[tone], className),
14
15
  ...props
15
16
  }));
16
17
  HelpText.displayName = "HelpText";
@@ -1,10 +1,7 @@
1
+ import { inputVariants } from "../presentation.js";
1
2
  import { VariantProps } from "class-variance-authority";
2
3
  import * as React from "react";
3
4
  //#region src/primitives/input.d.ts
4
- declare const inputVariants: (props?: ({
5
- variant?: "default" | "error" | "sandbox" | null | undefined;
6
- size?: "compact" | "default" | "lg" | "md" | "sm" | "touch" | null | undefined;
7
- } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
5
  export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">, VariantProps<typeof inputVariants> {
9
6
  label?: string;
10
7
  error?: string;
@@ -1,34 +1,13 @@
1
1
  "use client";
2
- import { focusFieldInvalid } from "../lib/focus.js";
2
+ import "../lib/focus.js";
3
3
  import { cn } from "../lib/utils.js";
4
- import { controlRadius, controlSizes, controlText, fieldPresentation, fieldSizes, fieldTouchText } from "../lib/control-presentation.js";
4
+ import { controlRadius, controlText, fieldPresentation, fieldTouchText } from "../lib/control-presentation.js";
5
+ import { inputVariants } from "../presentation.js";
5
6
  import { HelpText } from "./help-text.js";
6
7
  import { Label } from "./label.js";
7
- import { cva } from "class-variance-authority";
8
8
  import * as React from "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/primitives/input.tsx
11
- const inputVariants = cva(cn("flex w-full border py-1 placeholder:text-[var(--text-dim)] disabled:cursor-not-allowed disabled:opacity-50 file:border-0 file:bg-transparent file:font-medium", fieldPresentation, fieldTouchText), {
12
- variants: {
13
- variant: {
14
- default: "",
15
- sandbox: "",
16
- error: focusFieldInvalid
17
- },
18
- size: {
19
- default: fieldSizes.md,
20
- md: fieldSizes.md,
21
- sm: fieldSizes.sm,
22
- lg: fieldSizes.lg,
23
- compact: cn(fieldSizes.md, controlSizes.compact),
24
- touch: cn(fieldSizes.md, controlSizes.touch)
25
- }
26
- },
27
- defaultVariants: {
28
- variant: "default",
29
- size: "default"
30
- }
31
- });
32
11
  function descriptionIds(...ids) {
33
12
  return [...new Set(ids.join(" ").split(/\s+/).filter(Boolean))].join(" ") || void 0;
34
13
  }
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/utils.js";
3
- import { cva } from "class-variance-authority";
3
+ import { labelVariants } from "../presentation.js";
4
4
  import * as React from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import * as LabelPrimitive from "@radix-ui/react-label";
7
7
  //#region src/primitives/label.tsx
8
- const labelVariants = cva("font-medium text-[length:var(--font-size-label,0.875rem)] leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
9
8
  const Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(LabelPrimitive.Root, {
10
9
  ref,
11
10
  className: cn(labelVariants(), className),
@@ -1,17 +1,12 @@
1
1
  import { cn } from "../lib/utils.js";
2
+ import { metricLayouts, metricPresentation } from "../presentation.js";
2
3
  import { StatusPill } from "./status-pill.js";
3
4
  import * as React from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
  //#region src/primitives/metric-strip.tsx
6
- const layouts = {
7
- 3: "grid-cols-2 sm:grid-cols-3 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:[&>div:not(:nth-child(3n+1))]:border-l",
8
- 4: "grid-cols-2 sm:grid-cols-4 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:[&>div:not(:nth-child(4n+1))]:border-l",
9
- 5: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:max-lg:[&>div:not(:nth-child(3n+1))]:border-l lg:[&>div:not(:nth-child(5n+1))]:border-l",
10
- 6: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:max-lg:[&>div:not(:nth-child(3n+1))]:border-l lg:[&>div:not(:nth-child(6n+1))]:border-l"
11
- };
12
7
  const MetricStrip = React.forwardRef(({ className, children, columns = 4, ...props }, ref) => /* @__PURE__ */ jsx("dl", {
13
8
  ref,
14
- className: cn("grid min-w-0 rounded-[var(--radius-lg)] border border-border bg-card shadow-[var(--shadow-card)]", layouts[columns], className),
9
+ className: cn(metricPresentation.strip, metricLayouts[columns], className),
15
10
  ...props,
16
11
  children
17
12
  }));
@@ -19,11 +14,11 @@ MetricStrip.displayName = "MetricStrip";
19
14
  /** A described term; the strip, not this item, decides where separators belong. */
20
15
  const Metric = React.forwardRef(({ className, label, value, hint, attention, icon, ...props }, ref) => /* @__PURE__ */ jsxs("div", {
21
16
  ref,
22
- className: cn("min-w-0 border-border p-4 sm:p-5", className),
17
+ className: cn(metricPresentation.item, className),
23
18
  ...props,
24
19
  children: [
25
20
  /* @__PURE__ */ jsxs("dt", {
26
- className: "flex min-w-0 flex-wrap items-center gap-2 text-muted-foreground text-sm",
21
+ className: metricPresentation.label,
27
22
  children: [
28
23
  icon != null && icon !== false && /* @__PURE__ */ jsx("span", {
29
24
  "aria-hidden": "true",
@@ -31,7 +26,7 @@ const Metric = React.forwardRef(({ className, label, value, hint, attention, ico
31
26
  children: icon
32
27
  }),
33
28
  /* @__PURE__ */ jsx("span", {
34
- className: "min-w-0 whitespace-normal [overflow-wrap:anywhere]",
29
+ className: metricPresentation.labelText,
35
30
  children: label
36
31
  }),
37
32
  attention && /* @__PURE__ */ jsx(StatusPill, {
@@ -41,12 +36,12 @@ const Metric = React.forwardRef(({ className, label, value, hint, attention, ico
41
36
  ]
42
37
  }),
43
38
  /* @__PURE__ */ jsx("dd", {
44
- className: cn("mt-1 whitespace-normal [overflow-wrap:anywhere] font-semibold text-xl tabular-nums tracking-tight lg:text-2xl", attention?.tone === "danger" && "text-[var(--surface-danger-text)]"),
39
+ className: cn(metricPresentation.value, attention?.tone === "danger" && "text-[var(--surface-danger-text)]"),
45
40
  title: typeof value === "string" ? value : void 0,
46
41
  children: value
47
42
  }),
48
43
  hint !== void 0 && hint !== null && hint !== false && hint !== "" && /* @__PURE__ */ jsx("dd", {
49
- className: "mt-0.5 whitespace-normal [overflow-wrap:anywhere] text-[var(--text-dim)] text-xs",
44
+ className: metricPresentation.hint,
50
45
  title: typeof hint === "string" ? hint : void 0,
51
46
  children: hint
52
47
  })
@@ -7,6 +7,7 @@ import * as React from "react";
7
7
  */
8
8
  export interface PageHeaderProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
9
9
  title: React.ReactNode;
10
+ presentation?: "operational" | "expressive";
10
11
  description?: React.ReactNode;
11
12
  /** Canonical action slot. If supplied (even null), takes precedence over action. */
12
13
  actions?: React.ReactNode;
@@ -4,14 +4,14 @@ import * as React from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region src/primitives/page-header.tsx
6
6
  const hasContent = (node) => Boolean(node) || node === 0;
7
- const PageHeader = React.forwardRef(({ className, title, description, actions, action, eyebrow, meta, titleId, titleAs, level = 1, ...props }, ref) => {
7
+ const PageHeader = React.forwardRef(({ className, presentation, title, description, actions, action, eyebrow, meta, titleId, titleAs, level = 1, ...props }, ref) => {
8
8
  const resolvedActions = actions === void 0 ? action : actions;
9
9
  return /* @__PURE__ */ jsxs("header", {
10
10
  ref,
11
- className: cn("mb-6 min-w-0", className),
11
+ className: cn(presentation === "expressive" ? "tangle-intro" : presentation === "operational" ? "tangle-page-header" : "mb-6 min-w-0", className),
12
12
  ...props,
13
13
  children: [/* @__PURE__ */ jsxs("div", {
14
- className: "flex min-w-0 flex-wrap items-start justify-between gap-x-6 gap-y-3",
14
+ className: presentation ? "tangle-page-header-main" : "flex min-w-0 flex-wrap items-start justify-between gap-x-6 gap-y-3",
15
15
  children: [/* @__PURE__ */ jsxs("div", {
16
16
  className: "min-w-0 flex-[1_1_16rem]",
17
17
  children: [
@@ -22,18 +22,19 @@ const PageHeader = React.forwardRef(({ className, title, description, actions, a
22
22
  }),
23
23
  /* @__PURE__ */ jsx(Heading, {
24
24
  id: titleId,
25
- variant: level === 1 ? "page" : "section",
25
+ variant: presentation === "expressive" ? "display" : level === 1 ? "page" : "section",
26
+ presentation,
26
27
  as: titleAs ?? (level === 1 ? "h1" : "h2"),
27
28
  className: "text-balance",
28
29
  children: title
29
30
  }),
30
31
  hasContent(description) && /* @__PURE__ */ jsx("p", {
31
- className: "mt-1 max-w-prose text-muted-foreground text-sm [overflow-wrap:anywhere]",
32
+ className: presentation === "expressive" ? "tangle-intro-description" : presentation ? "tangle-page-description" : "mt-1 max-w-prose text-muted-foreground text-sm [overflow-wrap:anywhere]",
32
33
  children: description
33
34
  })
34
35
  ]
35
36
  }), hasContent(resolvedActions) && /* @__PURE__ */ jsx("div", {
36
- className: "flex min-w-0 max-w-full flex-wrap items-center gap-2 [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full",
37
+ className: presentation === "expressive" ? "tangle-intro-actions" : "flex min-w-0 max-w-full flex-wrap items-center gap-2 [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full",
37
38
  children: resolvedActions
38
39
  })]
39
40
  }), hasContent(meta) && /* @__PURE__ */ jsx("div", {
@@ -1,7 +1,13 @@
1
1
  import { ReactNode } from "react";
2
2
  //#region src/primitives/page-shell.d.ts
3
3
  export interface PageShellProps {
4
- children: ReactNode;
4
+ children?: ReactNode;
5
+ /** Optional reading layout. Content, routing and navigation interaction stay caller-owned. */
6
+ navigation?: ReactNode;
7
+ article?: ReactNode;
8
+ outline?: ReactNode;
9
+ navigationLabel?: string;
10
+ outlineLabel?: string;
5
11
  className?: string;
6
12
  }
7
13
  /**
@@ -10,5 +16,5 @@ export interface PageShellProps {
10
16
  * The caller owns landmarks, routing, auth, navigation and data state. Use
11
17
  * className to override the bound/gutters, or omit it for a workbench layout.
12
18
  */
13
- export declare function PageShell({ children, className }: PageShellProps): import("react").JSX.Element;
19
+ export declare function PageShell({ children, className, navigation, article, outline, navigationLabel, outlineLabel }: PageShellProps): import("react").JSX.Element;
14
20
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { cn } from "../lib/utils.js";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  //#region src/primitives/page-shell.tsx
4
4
  /**
5
5
  * Ported from sandbox-ui/src/primitives/page-shell.tsx. An optional bounded
@@ -7,7 +7,26 @@ import { jsx } from "react/jsx-runtime";
7
7
  * The caller owns landmarks, routing, auth, navigation and data state. Use
8
8
  * className to override the bound/gutters, or omit it for a workbench layout.
9
9
  */
10
- function PageShell({ children, className }) {
10
+ function PageShell({ children, className, navigation, article, outline, navigationLabel = "Sections", outlineLabel = "On this page" }) {
11
+ if (navigation || article || outline) return /* @__PURE__ */ jsxs("div", {
12
+ className: cn("tangle-reading-shell", className),
13
+ children: [
14
+ navigation && /* @__PURE__ */ jsx("nav", {
15
+ "aria-label": navigationLabel,
16
+ className: "tangle-reading-nav",
17
+ children: navigation
18
+ }),
19
+ /* @__PURE__ */ jsx("article", {
20
+ className: "tangle-reading-article",
21
+ children: article ?? children
22
+ }),
23
+ outline && /* @__PURE__ */ jsx("aside", {
24
+ "aria-label": outlineLabel,
25
+ className: "tangle-reading-outline",
26
+ children: outline
27
+ })
28
+ ]
29
+ });
11
30
  return /* @__PURE__ */ jsx("div", {
12
31
  className: cn("mx-auto min-w-0 w-full max-w-6xl space-y-8 px-6 py-8 lg:px-8", className),
13
32
  children
@@ -1,5 +1,5 @@
1
1
  import { cn } from "../lib/utils.js";
2
- import { buttonVariants } from "./button.js";
2
+ import { buttonVariants } from "../presentation.js";
3
3
  import "react";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { ChevronLeft, ChevronRight } from "lucide-react";
@@ -24,9 +24,12 @@ import * as React from "react";
24
24
  * - Every label keeps one weight. Selection is colour plus surface or
25
25
  * underline, so changing the value never reflows the row.
26
26
  *
27
- * The tabs variant scrolls sideways when its options outgrow the row: the
28
- * selected tab is kept in view, and a faded edge shows that more tabs sit
29
- * past it.
27
+ * When the options outgrow the row they scroll sideways (the tabs variant
28
+ * always can; the row variant can when a host gives it `flex-nowrap
29
+ * overflow-x-auto`). A chevron button pinned to each edge with options past it
30
+ * shows that the strip scrolls and pages it on click or tap; touch and
31
+ * trackpad scroll it natively, and arrow keys move the selection, which is
32
+ * kept fully in view clear of the chevrons.
30
33
  */
31
34
  export interface SegmentedControlOption<T extends string = string> {
32
35
  value: T;