@tangle-network/ui 11.35.0 → 11.37.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.
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/dist/code-presentation.d.ts +2 -0
- package/dist/code-presentation.js +2 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6 -3
- package/dist/lib/code-presentation.d.ts +92 -0
- package/dist/lib/code-presentation.js +437 -0
- package/dist/lib/control-presentation.d.ts +34 -1
- package/dist/lib/control-presentation.js +1 -1
- package/dist/lib/focus.d.ts +2 -2
- package/dist/lib/focus.js +3 -2
- package/dist/lib/motion.d.ts +4 -0
- package/dist/lib/motion.js +7 -0
- package/dist/lib/surface-motion.js +105 -0
- package/dist/markdown/api-schema.d.ts +9 -0
- package/dist/markdown/api-schema.js +20 -0
- package/dist/markdown/code-block.d.ts +15 -7
- package/dist/markdown/code-block.js +120 -268
- package/dist/markdown.d.ts +4 -2
- package/dist/markdown.js +4 -2
- package/dist/presentation.d.ts +88 -0
- package/dist/presentation.js +183 -0
- package/dist/primitives/alert-dialog.js +1 -1
- package/dist/primitives/alert.d.ts +1 -2
- package/dist/primitives/button.d.ts +2 -5
- package/dist/primitives/button.js +5 -31
- package/dist/primitives/card.js +3 -7
- package/dist/primitives/code-block.d.ts +1 -1
- package/dist/primitives/date-picker.d.ts +1 -1
- package/dist/primitives/dialog.js +29 -16
- package/dist/primitives/empty-state.js +6 -5
- package/dist/primitives/heading.d.ts +1 -0
- package/dist/primitives/heading.js +3 -2
- package/dist/primitives/help-text.js +2 -1
- package/dist/primitives/input.d.ts +1 -4
- package/dist/primitives/input.js +3 -24
- package/dist/primitives/label.js +1 -2
- package/dist/primitives/metric-strip.js +7 -12
- package/dist/primitives/page-header.d.ts +1 -0
- package/dist/primitives/page-header.js +7 -6
- package/dist/primitives/page-shell.d.ts +8 -2
- package/dist/primitives/page-shell.js +21 -2
- package/dist/primitives/pagination.js +1 -1
- package/dist/primitives/popover.js +11 -3
- package/dist/primitives/sheet.js +29 -19
- package/dist/primitives/skeleton.js +1 -1
- package/dist/primitives/status-pill.d.ts +3 -1
- package/dist/primitives/status-pill.js +12 -3
- package/dist/primitives/table.js +10 -9
- package/dist/primitives/tabs.js +3 -12
- package/dist/primitives/tooltip.js +11 -3
- package/dist/primitives.d.ts +3 -2
- package/dist/primitives.js +2 -1
- package/dist/utils.d.ts +1 -1
- package/package.json +20 -5
- package/src/code-presentation.ts +2 -0
- package/src/lib/code-presentation.ts +257 -0
- package/src/lib/control-presentation.ts +2 -4
- package/src/lib/focus.ts +3 -2
- package/src/lib/motion.ts +7 -0
- package/src/lib/surface-motion.tsx +85 -0
- package/src/markdown/api-presentation.test.tsx +63 -0
- package/src/markdown/api-schema.tsx +9 -0
- package/src/markdown/code-block.tsx +54 -185
- package/src/markdown/index.ts +2 -1
- package/src/presentation.ts +205 -0
- package/src/primitives/alert.tsx +1 -2
- package/src/primitives/button.tsx +6 -41
- package/src/primitives/card.tsx +5 -10
- package/src/primitives/checkbox-separator-sheet-tooltip.test.tsx +2 -1
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/control-presentation.test.tsx +2 -7
- package/src/primitives/dialog.tsx +19 -18
- package/src/primitives/empty-state.tsx +6 -5
- package/src/primitives/heading.tsx +4 -2
- package/src/primitives/help-text.tsx +3 -2
- package/src/primitives/input.tsx +3 -31
- package/src/primitives/label.tsx +2 -4
- package/src/primitives/metric-strip.tsx +8 -17
- package/src/primitives/page-header.tsx +8 -5
- package/src/primitives/page-shell.tsx +13 -2
- package/src/primitives/popover.tsx +6 -8
- package/src/primitives/sheet.tsx +16 -11
- package/src/primitives/skeleton.tsx +1 -1
- package/src/primitives/status-pill.test.tsx +1 -0
- package/src/primitives/status-pill.tsx +10 -4
- package/src/primitives/table.tsx +10 -9
- package/src/primitives/tabs.tsx +5 -17
- package/src/primitives/tooltip.tsx +6 -5
- package/src/styles/compat.css +12 -0
- package/src/styles/presentation.css +38 -0
- package/src/primitives/page-shell.test.tsx +0 -24
package/src/primitives/card.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { ixControl } from "../lib/interaction";
|
|
3
|
+
import { cardPresentation } from "../presentation";
|
|
3
4
|
import { cn } from "../lib/utils";
|
|
4
5
|
import { Heading } from "./heading";
|
|
5
6
|
|
|
@@ -24,20 +25,14 @@ const Card = React.forwardRef<
|
|
|
24
25
|
interactive?: boolean;
|
|
25
26
|
}
|
|
26
27
|
>(({ className, variant = "default", hover = false, interactive = false, ...props }, ref) => {
|
|
27
|
-
|
|
28
|
-
default: "bg-card border-border shadow-[var(--edge-highlight),var(--shadow-card)]",
|
|
29
|
-
elevated: "bg-muted/50 border-border shadow-[var(--shadow-card)]",
|
|
30
|
-
glass: "bg-card/80 backdrop-blur-xl border-border shadow-[var(--shadow-card)]",
|
|
31
|
-
sandbox: "bg-muted/50 border-primary/20 shadow-[var(--shadow-accent)]",
|
|
32
|
-
};
|
|
28
|
+
|
|
33
29
|
|
|
34
30
|
return (
|
|
35
31
|
<div
|
|
36
32
|
ref={ref}
|
|
37
33
|
className={cn(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
variants[variant],
|
|
34
|
+
cardPresentation.root,
|
|
35
|
+
cardPresentation.variants[variant],
|
|
41
36
|
(hover || interactive) && ixControl,
|
|
42
37
|
className,
|
|
43
38
|
)}
|
|
@@ -98,7 +93,7 @@ const CardContent = React.forwardRef<
|
|
|
98
93
|
>(({ className, ...props }, ref) => (
|
|
99
94
|
<div
|
|
100
95
|
ref={ref}
|
|
101
|
-
className={cn("
|
|
96
|
+
className={cn(cardPresentation.padding, "[&:not(:first-child)]:pt-0", className)}
|
|
102
97
|
{...props}
|
|
103
98
|
/>
|
|
104
99
|
));
|
|
@@ -71,7 +71,8 @@ describe("Sheet", () => {
|
|
|
71
71
|
);
|
|
72
72
|
fireEvent.click(screen.getByText("Open chat"));
|
|
73
73
|
const dialog = await screen.findByRole("dialog", { name: "Assistant" });
|
|
74
|
-
expect(dialog
|
|
74
|
+
expect(dialog).toHaveAttribute("data-state", "open");
|
|
75
|
+
expect(dialog.style.opacity).toBe("1");
|
|
75
76
|
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
|
76
77
|
await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
|
|
77
78
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Re-exports the canonical themed CodeBlock from the markdown package.
|
|
2
2
|
// Kept here for backwards compatibility with openui and other importers.
|
|
3
3
|
export { CodeBlock, CopyButton } from "../markdown/code-block";
|
|
4
|
-
export type { CodeBlockProps } from "../markdown/code-block";
|
|
4
|
+
export type { CodeBlockProps, CopyButtonProps } from "../markdown/code-block";
|
|
5
5
|
|
|
6
6
|
import { cn } from "../lib/utils";
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { cleanup, render, screen } from "@testing-library/react";
|
|
3
3
|
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
-
import { controlHeight, controlText,
|
|
4
|
+
import { controlHeight, controlText, fieldPresentation } from "../lib/control-presentation";
|
|
5
5
|
import { HelpText } from "./help-text";
|
|
6
6
|
import { Label } from "./label";
|
|
7
7
|
import { Button, buttonVariants } from "./button";
|
|
@@ -11,7 +11,7 @@ import { Select, SelectTrigger, SelectValue } from "./select";
|
|
|
11
11
|
afterEach(cleanup);
|
|
12
12
|
|
|
13
13
|
describe("shared control presentation", () => {
|
|
14
|
-
it("uses the field well, not the border/track alias, with scoped foreground
|
|
14
|
+
it("uses the field well, not the border/track alias, with scoped foreground", () => {
|
|
15
15
|
// The well is the brand's --well context variable; Toolbar can still pick the fill.
|
|
16
16
|
expect(fieldPresentation).toContain("bg-[var(--field-surface,var(--well))]");
|
|
17
17
|
expect(fieldPresentation.split(" ")).not.toContain("bg-input");
|
|
@@ -20,10 +20,6 @@ describe("shared control presentation", () => {
|
|
|
20
20
|
expect(fieldPresentation.split(" ")).toContain("border-[var(--field-border)]");
|
|
21
21
|
expect(fieldPresentation.split(" ")).not.toContain("border-border");
|
|
22
22
|
expect(fieldPresentation).toContain("text-foreground");
|
|
23
|
-
expect(fieldPresentation).not.toContain("duration-150");
|
|
24
|
-
expect(controlMotion).toContain("duration-[var(--duration-fast)]");
|
|
25
|
-
expect(controlMotion).not.toContain("--transition-fast");
|
|
26
|
-
expect(controlMotion).toContain("motion-reduce:duration-0");
|
|
27
23
|
});
|
|
28
24
|
|
|
29
25
|
it("puts Button, Input, Textarea and SelectTrigger on one height and text scale", () => {
|
|
@@ -116,7 +112,6 @@ describe("shared control presentation", () => {
|
|
|
116
112
|
expect(buttonVariants({ variant: "destructive" })).toContain("text-destructive-foreground");
|
|
117
113
|
expect(buttonVariants({ variant: "sandbox" })).toContain("text-[var(--btn-primary-text)]");
|
|
118
114
|
expect(buttonVariants()).not.toContain("transition-all");
|
|
119
|
-
expect(buttonVariants()).toContain("box-shadow,transform,scale");
|
|
120
115
|
expect(buttonVariants()).toContain("motion-reduce:active:scale-100");
|
|
121
116
|
});
|
|
122
117
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SurfaceMotion } from "../lib/surface-motion";
|
|
2
|
+
import { dialogPresentation } from "../presentation";
|
|
1
3
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
4
|
import { X } from "lucide-react";
|
|
3
5
|
import * as React from "react";
|
|
@@ -15,17 +17,17 @@ const DialogClose = DialogPrimitive.Close;
|
|
|
15
17
|
const DialogOverlay = React.forwardRef<
|
|
16
18
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
17
19
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
18
|
-
>(({ className, ...props }, ref) => (
|
|
20
|
+
>(({ className, asChild, children, ...props }, ref) => (
|
|
19
21
|
<DialogPrimitive.Overlay
|
|
20
22
|
ref={ref}
|
|
21
23
|
className={cn(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
24
|
+
dialogPresentation.overlay,
|
|
25
|
+
dialogPresentation.transition,
|
|
25
26
|
className,
|
|
26
27
|
)}
|
|
27
28
|
{...props}
|
|
28
|
-
|
|
29
|
+
asChild
|
|
30
|
+
><SurfaceMotion kind="overlay" asChild={asChild}>{children}</SurfaceMotion></DialogPrimitive.Overlay>
|
|
29
31
|
));
|
|
30
32
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
31
33
|
|
|
@@ -40,7 +42,7 @@ const DialogContent = React.forwardRef<
|
|
|
40
42
|
*/
|
|
41
43
|
hideCloseButton?: boolean;
|
|
42
44
|
}
|
|
43
|
-
>(({ className, children, variant = "default", hideCloseButton = false, ...props }, ref) => {
|
|
45
|
+
>(({ className, children, variant = "default", hideCloseButton = false, asChild, ...props }, ref) => {
|
|
44
46
|
const variants = {
|
|
45
47
|
default: "border-border",
|
|
46
48
|
sandbox: "border-[var(--border-accent)] shadow-[var(--shadow-accent)]",
|
|
@@ -52,23 +54,21 @@ const DialogContent = React.forwardRef<
|
|
|
52
54
|
<DialogPrimitive.Content
|
|
53
55
|
ref={ref}
|
|
54
56
|
className={cn(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
59
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
60
|
-
"data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]",
|
|
61
|
-
"data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
|
|
57
|
+
dialogPresentation.position,
|
|
58
|
+
dialogPresentation.surface,
|
|
59
|
+
dialogPresentation.transition,
|
|
62
60
|
variants[variant],
|
|
63
61
|
className,
|
|
64
62
|
)}
|
|
65
63
|
{...props}
|
|
64
|
+
asChild
|
|
66
65
|
>
|
|
66
|
+
<SurfaceMotion kind="dialog" asChild={asChild}>
|
|
67
67
|
{children}
|
|
68
68
|
{!hideCloseButton && (
|
|
69
69
|
<DialogPrimitive.Close
|
|
70
70
|
className={cn(
|
|
71
|
-
|
|
71
|
+
dialogPresentation.close,
|
|
72
72
|
focusRing,
|
|
73
73
|
)}
|
|
74
74
|
>
|
|
@@ -76,6 +76,7 @@ const DialogContent = React.forwardRef<
|
|
|
76
76
|
<span className="sr-only">Close</span>
|
|
77
77
|
</DialogPrimitive.Close>
|
|
78
78
|
)}
|
|
79
|
+
</SurfaceMotion>
|
|
79
80
|
</DialogPrimitive.Content>
|
|
80
81
|
</DialogPortal>
|
|
81
82
|
);
|
|
@@ -88,7 +89,7 @@ const DialogHeader = ({
|
|
|
88
89
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
89
90
|
<div
|
|
90
91
|
className={cn(
|
|
91
|
-
|
|
92
|
+
dialogPresentation.header,
|
|
92
93
|
className,
|
|
93
94
|
)}
|
|
94
95
|
{...props}
|
|
@@ -102,7 +103,7 @@ const DialogFooter = ({
|
|
|
102
103
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
103
104
|
<div
|
|
104
105
|
className={cn(
|
|
105
|
-
|
|
106
|
+
dialogPresentation.footer,
|
|
106
107
|
className,
|
|
107
108
|
)}
|
|
108
109
|
{...props}
|
|
@@ -117,7 +118,7 @@ const DialogTitle = React.forwardRef<
|
|
|
117
118
|
<DialogPrimitive.Title
|
|
118
119
|
ref={ref}
|
|
119
120
|
className={cn(
|
|
120
|
-
|
|
121
|
+
dialogPresentation.title,
|
|
121
122
|
className,
|
|
122
123
|
)}
|
|
123
124
|
{...props}
|
|
@@ -131,7 +132,7 @@ const DialogDescription = React.forwardRef<
|
|
|
131
132
|
>(({ className, ...props }, ref) => (
|
|
132
133
|
<DialogPrimitive.Description
|
|
133
134
|
ref={ref}
|
|
134
|
-
className={cn(
|
|
135
|
+
className={cn(dialogPresentation.description, className)}
|
|
135
136
|
{...props}
|
|
136
137
|
/>
|
|
137
138
|
));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { emptyStatePresentation } from "../presentation";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { cn } from "../lib/utils";
|
|
3
4
|
|
|
@@ -17,23 +18,23 @@ const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
|
|
|
17
18
|
<div
|
|
18
19
|
ref={ref}
|
|
19
20
|
className={cn(
|
|
20
|
-
|
|
21
|
+
emptyStatePresentation.root,
|
|
21
22
|
className,
|
|
22
23
|
)}
|
|
23
24
|
{...props}
|
|
24
25
|
>
|
|
25
26
|
{icon && (
|
|
26
|
-
<div className=
|
|
27
|
+
<div className={emptyStatePresentation.icon}>
|
|
27
28
|
{icon}
|
|
28
29
|
</div>
|
|
29
30
|
)}
|
|
30
|
-
<Title className=
|
|
31
|
+
<Title className={emptyStatePresentation.title}>{title}</Title>
|
|
31
32
|
{description && (
|
|
32
|
-
<p className=
|
|
33
|
+
<p className={emptyStatePresentation.description}>
|
|
33
34
|
{description}
|
|
34
35
|
</p>
|
|
35
36
|
)}
|
|
36
|
-
{action && <div className=
|
|
37
|
+
{action && <div className={emptyStatePresentation.action}>{action}</div>}
|
|
37
38
|
</div>
|
|
38
39
|
);
|
|
39
40
|
},
|
|
@@ -38,6 +38,7 @@ export interface HeadingProps
|
|
|
38
38
|
extends Omit<React.HTMLAttributes<HTMLElement>, "role"> {
|
|
39
39
|
/** Visual treatment, independent of the rendered element. Defaults to section. */
|
|
40
40
|
variant?: HeadingVariant;
|
|
41
|
+
presentation?: "operational" | "expressive";
|
|
41
42
|
/** Semantic element; changing it does not change the visual variant. */
|
|
42
43
|
as?: React.ElementType;
|
|
43
44
|
/**
|
|
@@ -53,7 +54,7 @@ export interface HeadingProps
|
|
|
53
54
|
* display/page h1 per page; choose `as` according to the document hierarchy.
|
|
54
55
|
*/
|
|
55
56
|
const Heading = React.forwardRef<HTMLElement, HeadingProps>(
|
|
56
|
-
({ variant, role, as, className, ...props }, ref) => {
|
|
57
|
+
({ variant, role, as, className, presentation, ...props }, ref) => {
|
|
57
58
|
const legacyVariant =
|
|
58
59
|
role && Object.prototype.hasOwnProperty.call(VARIANT_CLASS, role)
|
|
59
60
|
? (role as HeadingVariant)
|
|
@@ -64,7 +65,8 @@ const Heading = React.forwardRef<HTMLElement, HeadingProps>(
|
|
|
64
65
|
return (
|
|
65
66
|
<Tag
|
|
66
67
|
ref={ref}
|
|
67
|
-
className={cn("[overflow-wrap:anywhere]", VARIANT_CLASS[visualVariant], className)}
|
|
68
|
+
className={cn("[overflow-wrap:anywhere]", presentation ? (presentation === "expressive" ? "tangle-display" : "tangle-heading") : VARIANT_CLASS[visualVariant], className)}
|
|
69
|
+
data-variant={presentation ? visualVariant : undefined}
|
|
68
70
|
role={legacyVariant ? undefined : role}
|
|
69
71
|
{...props}
|
|
70
72
|
/>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { helpPresentation } from "../presentation";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { cn } from "../lib/utils";
|
|
3
4
|
|
|
@@ -17,8 +18,8 @@ const HelpText = React.forwardRef<HTMLParagraphElement, HelpTextProps>(
|
|
|
17
18
|
ref={ref}
|
|
18
19
|
data-tone={tone}
|
|
19
20
|
className={cn(
|
|
20
|
-
|
|
21
|
-
tone
|
|
21
|
+
helpPresentation.root,
|
|
22
|
+
helpPresentation[tone],
|
|
22
23
|
className,
|
|
23
24
|
)}
|
|
24
25
|
{...props}
|
package/src/primitives/input.tsx
CHANGED
|
@@ -1,45 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { controlRadius, controlSizes, controlText, fieldPresentation,
|
|
4
|
+
import { controlRadius, controlSizes, controlText, fieldPresentation, fieldTouchText } from "../lib/control-presentation";
|
|
5
5
|
import { focusFieldInvalid } from "../lib/focus";
|
|
6
6
|
import { cn } from "../lib/utils";
|
|
7
7
|
import { HelpText } from "./help-text";
|
|
8
8
|
import { Label } from "./label";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { inputVariants } from "../presentation";
|
|
11
|
+
import { type VariantProps } from "class-variance-authority";
|
|
11
12
|
|
|
12
13
|
// Sizes follow the shared control scale (control-presentation.ts), so an Input
|
|
13
14
|
// and a Button at the same size share a height, text size and corner.
|
|
14
|
-
const inputVariants = cva(
|
|
15
|
-
cn(
|
|
16
|
-
"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",
|
|
17
|
-
fieldPresentation,
|
|
18
|
-
fieldTouchText,
|
|
19
|
-
),
|
|
20
|
-
{
|
|
21
|
-
variants: {
|
|
22
|
-
// `default` and `sandbox` render the same field; both names stay valid.
|
|
23
|
-
variant: {
|
|
24
|
-
default: "",
|
|
25
|
-
sandbox: "",
|
|
26
|
-
error: focusFieldInvalid,
|
|
27
|
-
},
|
|
28
|
-
size: {
|
|
29
|
-
default: fieldSizes.md,
|
|
30
|
-
md: fieldSizes.md,
|
|
31
|
-
sm: fieldSizes.sm,
|
|
32
|
-
lg: fieldSizes.lg,
|
|
33
|
-
compact: cn(fieldSizes.md, controlSizes.compact),
|
|
34
|
-
touch: cn(fieldSizes.md, controlSizes.touch),
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
defaultVariants: {
|
|
38
|
-
variant: "default",
|
|
39
|
-
size: "default",
|
|
40
|
-
},
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
15
|
|
|
44
16
|
// Only rendered descriptions are added; caller IDs and their order are retained.
|
|
45
17
|
function descriptionIds(...ids: Array<string | undefined>) {
|
package/src/primitives/label.tsx
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
4
|
-
import {
|
|
4
|
+
import { labelVariants } from "../presentation";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import { cn } from "../lib/utils";
|
|
7
7
|
|
|
8
8
|
// One label size (`--font-size-label`, 14px) for every field size.
|
|
9
|
-
|
|
10
|
-
"font-medium text-[length:var(--font-size-label,0.875rem)] leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
11
|
-
);
|
|
9
|
+
|
|
12
10
|
|
|
13
11
|
const Label = React.forwardRef<
|
|
14
12
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
@@ -1,17 +1,8 @@
|
|
|
1
|
+
import { metricPresentation, metricLayouts } from "../presentation";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { cn } from "../lib/utils";
|
|
3
4
|
import { StatusPill, type StatusTone } from "./status-pill";
|
|
4
5
|
|
|
5
|
-
// Each finite preset owns its grid AND its row-start rules. Keep the strings
|
|
6
|
-
// literal so host Tailwind consumers scanning the published source emit them.
|
|
7
|
-
// Ranges do not overlap: no drawn border is subsequently suppressed.
|
|
8
|
-
const layouts = {
|
|
9
|
-
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",
|
|
10
|
-
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",
|
|
11
|
-
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",
|
|
12
|
-
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",
|
|
13
|
-
} as const;
|
|
14
|
-
|
|
15
6
|
/** Headline readings on one plane, with native definition-list semantics. */
|
|
16
7
|
export interface MetricStripProps
|
|
17
8
|
extends React.HTMLAttributes<HTMLDListElement> {
|
|
@@ -26,8 +17,8 @@ const MetricStrip = React.forwardRef<HTMLDListElement, MetricStripProps>(
|
|
|
26
17
|
<dl
|
|
27
18
|
ref={ref}
|
|
28
19
|
className={cn(
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
metricPresentation.strip,
|
|
21
|
+
metricLayouts[columns],
|
|
31
22
|
className,
|
|
32
23
|
)}
|
|
33
24
|
{...props}
|
|
@@ -55,10 +46,10 @@ const Metric = React.forwardRef<HTMLDivElement, MetricProps>(
|
|
|
55
46
|
({ className, label, value, hint, attention, icon, ...props }, ref) => (
|
|
56
47
|
<div
|
|
57
48
|
ref={ref}
|
|
58
|
-
className={cn(
|
|
49
|
+
className={cn(metricPresentation.item, className)}
|
|
59
50
|
{...props}
|
|
60
51
|
>
|
|
61
|
-
<dt className=
|
|
52
|
+
<dt className={metricPresentation.label}>
|
|
62
53
|
{icon != null && icon !== false && (
|
|
63
54
|
<span
|
|
64
55
|
aria-hidden="true"
|
|
@@ -67,7 +58,7 @@ const Metric = React.forwardRef<HTMLDivElement, MetricProps>(
|
|
|
67
58
|
{icon}
|
|
68
59
|
</span>
|
|
69
60
|
)}
|
|
70
|
-
<span className=
|
|
61
|
+
<span className={metricPresentation.labelText}>{label}</span>
|
|
71
62
|
{attention && (
|
|
72
63
|
<StatusPill tone={attention.tone}>{attention.label}</StatusPill>
|
|
73
64
|
)}
|
|
@@ -76,7 +67,7 @@ const Metric = React.forwardRef<HTMLDivElement, MetricProps>(
|
|
|
76
67
|
title. Wrap even unbroken values without making the grid wider. */}
|
|
77
68
|
<dd
|
|
78
69
|
className={cn(
|
|
79
|
-
|
|
70
|
+
metricPresentation.value,
|
|
80
71
|
attention?.tone === "danger" && "text-[var(--surface-danger-text)]",
|
|
81
72
|
)}
|
|
82
73
|
title={typeof value === "string" ? value : undefined}
|
|
@@ -85,7 +76,7 @@ const Metric = React.forwardRef<HTMLDivElement, MetricProps>(
|
|
|
85
76
|
</dd>
|
|
86
77
|
{hint !== undefined && hint !== null && hint !== false && hint !== "" && (
|
|
87
78
|
<dd
|
|
88
|
-
className=
|
|
79
|
+
className={metricPresentation.hint}
|
|
89
80
|
title={typeof hint === "string" ? hint : undefined}
|
|
90
81
|
>
|
|
91
82
|
{hint}
|
|
@@ -10,6 +10,7 @@ import { Heading } from "./heading";
|
|
|
10
10
|
export interface PageHeaderProps
|
|
11
11
|
extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
12
12
|
title: React.ReactNode;
|
|
13
|
+
presentation?: "operational" | "expressive";
|
|
13
14
|
description?: React.ReactNode;
|
|
14
15
|
/** Canonical action slot. If supplied (even null), takes precedence over action. */
|
|
15
16
|
actions?: React.ReactNode;
|
|
@@ -31,6 +32,7 @@ const PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(
|
|
|
31
32
|
(
|
|
32
33
|
{
|
|
33
34
|
className,
|
|
35
|
+
presentation,
|
|
34
36
|
title,
|
|
35
37
|
description,
|
|
36
38
|
actions,
|
|
@@ -46,28 +48,29 @@ const PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(
|
|
|
46
48
|
) => {
|
|
47
49
|
const resolvedActions = actions === undefined ? action : actions;
|
|
48
50
|
return (
|
|
49
|
-
<header ref={ref} className={cn("mb-6 min-w-0", className)} {...props}>
|
|
50
|
-
<div className="flex min-w-0 flex-wrap items-start justify-between gap-x-6 gap-y-3">
|
|
51
|
+
<header ref={ref} className={cn(presentation === "expressive" ? "tangle-intro" : presentation === "operational" ? "tangle-page-header" : "mb-6 min-w-0", className)} {...props}>
|
|
52
|
+
<div className={presentation ? "tangle-page-header-main" : "flex min-w-0 flex-wrap items-start justify-between gap-x-6 gap-y-3"}>
|
|
51
53
|
<div className="min-w-0 flex-[1_1_16rem]">
|
|
52
54
|
{hasContent(eyebrow) && (
|
|
53
55
|
<Heading variant="eyebrow" className="mb-1.5">{eyebrow}</Heading>
|
|
54
56
|
)}
|
|
55
57
|
<Heading
|
|
56
58
|
id={titleId}
|
|
57
|
-
variant={level === 1 ? "page" : "section"}
|
|
59
|
+
variant={presentation === "expressive" ? "display" : level === 1 ? "page" : "section"}
|
|
60
|
+
presentation={presentation}
|
|
58
61
|
as={titleAs ?? (level === 1 ? "h1" : "h2")}
|
|
59
62
|
className="text-balance"
|
|
60
63
|
>
|
|
61
64
|
{title}
|
|
62
65
|
</Heading>
|
|
63
66
|
{hasContent(description) && (
|
|
64
|
-
<p className="mt-1 max-w-prose text-muted-foreground text-sm [overflow-wrap:anywhere]">
|
|
67
|
+
<p className={presentation === "expressive" ? "tangle-intro-description" : presentation ? "tangle-page-description" : "mt-1 max-w-prose text-muted-foreground text-sm [overflow-wrap:anywhere]"}>
|
|
65
68
|
{description}
|
|
66
69
|
</p>
|
|
67
70
|
)}
|
|
68
71
|
</div>
|
|
69
72
|
{hasContent(resolvedActions) && (
|
|
70
|
-
<div className="flex min-w-0 max-w-full flex-wrap items-center gap-2 [overflow-wrap:anywhere] [&>*]:min-w-0 [&>*]:max-w-full">
|
|
73
|
+
<div 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"}>
|
|
71
74
|
{resolvedActions}
|
|
72
75
|
</div>
|
|
73
76
|
)}
|
|
@@ -2,7 +2,13 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import { cn } from "../lib/utils";
|
|
3
3
|
|
|
4
4
|
export interface PageShellProps {
|
|
5
|
-
children
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/** Optional reading layout. Content, routing and navigation interaction stay caller-owned. */
|
|
7
|
+
navigation?: ReactNode;
|
|
8
|
+
article?: ReactNode;
|
|
9
|
+
outline?: ReactNode;
|
|
10
|
+
navigationLabel?: string;
|
|
11
|
+
outlineLabel?: string;
|
|
6
12
|
className?: string;
|
|
7
13
|
}
|
|
8
14
|
|
|
@@ -12,7 +18,12 @@ export interface PageShellProps {
|
|
|
12
18
|
* The caller owns landmarks, routing, auth, navigation and data state. Use
|
|
13
19
|
* className to override the bound/gutters, or omit it for a workbench layout.
|
|
14
20
|
*/
|
|
15
|
-
export function PageShell({ children, className }: PageShellProps) {
|
|
21
|
+
export function PageShell({ children, className, navigation, article, outline, navigationLabel = "Sections", outlineLabel = "On this page" }: PageShellProps) {
|
|
22
|
+
if (navigation || article || outline) return <div className={cn("tangle-reading-shell",className)}>
|
|
23
|
+
{navigation && <nav aria-label={navigationLabel} className="tangle-reading-nav">{navigation}</nav>}
|
|
24
|
+
<article className="tangle-reading-article">{article ?? children}</article>
|
|
25
|
+
{outline && <aside aria-label={outlineLabel} className="tangle-reading-outline">{outline}</aside>}
|
|
26
|
+
</div>;
|
|
16
27
|
return (
|
|
17
28
|
<div
|
|
18
29
|
className={cn(
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
4
4
|
import * as React from "react";
|
|
5
|
+
import { SurfaceMotion } from "../lib/surface-motion";
|
|
6
|
+
import { surfaceMotion } from "../lib/motion";
|
|
5
7
|
import { cn } from "../lib/utils";
|
|
6
8
|
|
|
7
9
|
const Popover = PopoverPrimitive.Root;
|
|
@@ -20,7 +22,7 @@ const PopoverClose = PopoverPrimitive.Close;
|
|
|
20
22
|
const PopoverContent = React.forwardRef<
|
|
21
23
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
22
24
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
23
|
-
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
|
25
|
+
>(({ className, align = "center", sideOffset = 4, asChild, children, ...props }, ref) => (
|
|
24
26
|
<PopoverPrimitive.Portal>
|
|
25
27
|
<PopoverPrimitive.Content
|
|
26
28
|
ref={ref}
|
|
@@ -28,16 +30,12 @@ const PopoverContent = React.forwardRef<
|
|
|
28
30
|
sideOffset={sideOffset}
|
|
29
31
|
className={cn(
|
|
30
32
|
"z-50 w-72 max-w-[calc(100vw-2rem)] origin-(--radix-popover-content-transform-origin) rounded-xl border border-border bg-card p-4 text-foreground shadow-[var(--shadow-card)] outline-hidden",
|
|
31
|
-
|
|
32
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
33
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
34
|
-
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
|
|
35
|
-
"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
36
|
-
"motion-reduce:animate-none",
|
|
33
|
+
surfaceMotion,
|
|
37
34
|
className,
|
|
38
35
|
)}
|
|
39
36
|
{...props}
|
|
40
|
-
|
|
37
|
+
asChild
|
|
38
|
+
><SurfaceMotion kind="popover" asChild={asChild}>{children}</SurfaceMotion></PopoverPrimitive.Content>
|
|
41
39
|
</PopoverPrimitive.Portal>
|
|
42
40
|
));
|
|
43
41
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
package/src/primitives/sheet.tsx
CHANGED
|
@@ -4,6 +4,8 @@ import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
5
|
import { X } from "lucide-react";
|
|
6
6
|
import * as React from "react";
|
|
7
|
+
import { SurfaceMotion } from "../lib/surface-motion";
|
|
8
|
+
import { controlMotion, surfaceMotion } from "../lib/motion";
|
|
7
9
|
import { focusRing } from "../lib/focus";
|
|
8
10
|
import { cn } from "../lib/utils";
|
|
9
11
|
|
|
@@ -19,35 +21,34 @@ const SheetPortal = SheetPrimitive.Portal;
|
|
|
19
21
|
const SheetOverlay = React.forwardRef<
|
|
20
22
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
|
21
23
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
|
22
|
-
>(({ className, ...props }, ref) => (
|
|
24
|
+
>(({ className, asChild, children, ...props }, ref) => (
|
|
23
25
|
<SheetPrimitive.Overlay
|
|
24
26
|
ref={ref}
|
|
25
27
|
className={cn(
|
|
26
28
|
"fixed inset-0 z-50 bg-black/40",
|
|
27
|
-
|
|
28
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
29
|
+
surfaceMotion,
|
|
29
30
|
className,
|
|
30
31
|
)}
|
|
31
32
|
{...props}
|
|
32
|
-
|
|
33
|
+
asChild
|
|
34
|
+
><SurfaceMotion kind="overlay" asChild={asChild}>{children}</SurfaceMotion></SheetPrimitive.Overlay>
|
|
33
35
|
));
|
|
34
36
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
|
35
37
|
|
|
36
38
|
const sheetVariants = cva(
|
|
37
39
|
[
|
|
38
40
|
"fixed z-50 flex flex-col gap-4 border-border bg-card shadow-lg",
|
|
39
|
-
|
|
40
|
-
"data-[state=closed]:duration-200 data-[state=open]:duration-300 ease-out motion-reduce:animate-none",
|
|
41
|
+
surfaceMotion,
|
|
41
42
|
],
|
|
42
43
|
{
|
|
43
44
|
variants: {
|
|
44
45
|
side: {
|
|
45
46
|
right:
|
|
46
|
-
"inset-y-0 right-0 h-full w-3/4 border-l
|
|
47
|
-
left: "inset-y-0 left-0 h-full w-3/4 border-r
|
|
48
|
-
top: "inset-x-0 top-0 h-auto border-b
|
|
47
|
+
"inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
|
48
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
|
49
|
+
top: "inset-x-0 top-0 h-auto border-b",
|
|
49
50
|
bottom:
|
|
50
|
-
"inset-x-0 bottom-0 h-auto border-t
|
|
51
|
+
"inset-x-0 bottom-0 h-auto border-t",
|
|
51
52
|
},
|
|
52
53
|
},
|
|
53
54
|
defaultVariants: { side: "right" },
|
|
@@ -61,19 +62,22 @@ const SheetContent = React.forwardRef<
|
|
|
61
62
|
/** Hide only the built-in close button; Escape and outside clicks still dismiss. */
|
|
62
63
|
hideCloseButton?: boolean;
|
|
63
64
|
}
|
|
64
|
-
>(({ className, children, side = "right", hideCloseButton = false, ...props }, ref) => (
|
|
65
|
+
>(({ className, children, side = "right", hideCloseButton = false, asChild, ...props }, ref) => (
|
|
65
66
|
<SheetPortal>
|
|
66
67
|
<SheetOverlay />
|
|
67
68
|
<SheetPrimitive.Content
|
|
68
69
|
ref={ref}
|
|
69
70
|
className={cn(sheetVariants({ side }), className)}
|
|
70
71
|
{...props}
|
|
72
|
+
asChild
|
|
71
73
|
>
|
|
74
|
+
<SurfaceMotion kind="sheet" side={side ?? "right"} asChild={asChild}>
|
|
72
75
|
{children}
|
|
73
76
|
{!hideCloseButton && (
|
|
74
77
|
<SheetPrimitive.Close
|
|
75
78
|
className={cn(
|
|
76
79
|
"absolute top-4 right-4 rounded-sm text-muted-foreground opacity-70 transition-[opacity,box-shadow] hover:opacity-100 focus-visible:opacity-100 disabled:pointer-events-none",
|
|
80
|
+
controlMotion,
|
|
77
81
|
focusRing,
|
|
78
82
|
)}
|
|
79
83
|
>
|
|
@@ -81,6 +85,7 @@ const SheetContent = React.forwardRef<
|
|
|
81
85
|
<span className="sr-only">Close</span>
|
|
82
86
|
</SheetPrimitive.Close>
|
|
83
87
|
)}
|
|
88
|
+
</SurfaceMotion>
|
|
84
89
|
</SheetPrimitive.Content>
|
|
85
90
|
</SheetPortal>
|
|
86
91
|
));
|
|
@@ -6,7 +6,7 @@ function Skeleton({
|
|
|
6
6
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
7
7
|
return (
|
|
8
8
|
<div
|
|
9
|
-
className={cn("animate-pulse rounded-lg bg-muted/50", className)}
|
|
9
|
+
className={cn("animate-pulse motion-reduce:animate-none rounded-lg bg-muted/50", className)}
|
|
10
10
|
{...props}
|
|
11
11
|
/>
|
|
12
12
|
);
|