@claralight-design/react 0.0.1
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/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/lib/anchored.d.ts +152 -0
- package/dist/lib/anchored.js +821 -0
- package/dist/lib/anchored.js.map +1 -0
- package/dist/lib/morph.js +484 -0
- package/dist/lib/morph.js.map +1 -0
- package/dist/lib/squircle.d.ts +95 -0
- package/dist/lib/squircle.js +220 -0
- package/dist/lib/squircle.js.map +1 -0
- package/dist/lib/utils.d.ts +30 -0
- package/dist/lib/utils.js +76 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/ui/button.d.ts +40 -0
- package/dist/ui/button.js +104 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/card.d.ts +54 -0
- package/dist/ui/card.js +82 -0
- package/dist/ui/card.js.map +1 -0
- package/dist/ui/dialog.d.ts +82 -0
- package/dist/ui/dialog.js +129 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/input.d.ts +34 -0
- package/dist/ui/input.js +60 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/popover.d.ts +67 -0
- package/dist/ui/popover.js +93 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/ui/scroll-area.d.ts +110 -0
- package/dist/ui/scroll-area.js +125 -0
- package/dist/ui/scroll-area.js.map +1 -0
- package/dist/ui/select.d.ts +113 -0
- package/dist/ui/select.js +213 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/tooltip.d.ts +108 -0
- package/dist/ui/tooltip.js +142 -0
- package/dist/ui/tooltip.js.map +1 -0
- package/package.json +72 -0
- package/styles/anchored.css +115 -0
- package/styles/base.css +309 -0
- package/styles/fonts/README.md +63 -0
- package/styles/index.css +28 -0
- package/styles/scroll-area.css +299 -0
- package/styles/theme.css +540 -0
- package/styles/tooltip.css +156 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Squircle } from "../lib/squircle.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { Button } from "@base-ui/react/button";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
//#region src/ui/button.tsx
|
|
8
|
+
/**
|
|
9
|
+
* ClaraLight button variants.
|
|
10
|
+
*
|
|
11
|
+
* `secondary` is the default because ClaraLight is a tool-like design
|
|
12
|
+
* language: most buttons in a real screen are neutral glass controls, and the
|
|
13
|
+
* accent should mark one primary action per view, not every button.
|
|
14
|
+
*/
|
|
15
|
+
const buttonVariants = cva([
|
|
16
|
+
"inline-flex select-none items-center justify-center gap-2",
|
|
17
|
+
"border font-sans whitespace-nowrap",
|
|
18
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
19
|
+
"data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed"
|
|
20
|
+
], {
|
|
21
|
+
variants: {
|
|
22
|
+
/**
|
|
23
|
+
* Filled variants use the theme's perceptual hover mix. `ghost` instead
|
|
24
|
+
* reveals a fill, because lifting nothing is still nothing.
|
|
25
|
+
*
|
|
26
|
+
* Disabled behaviour is deliberate and differs per variant: neutral
|
|
27
|
+
* controls keep their glass layer, and semantic actions keep their
|
|
28
|
+
* identity — except `danger`, which drops its colour so a destructive
|
|
29
|
+
* action never reads as available.
|
|
30
|
+
*/
|
|
31
|
+
variant: {
|
|
32
|
+
primary: [
|
|
33
|
+
"border-outline bg-accent text-on-accent",
|
|
34
|
+
"hover:bg-accent-hover",
|
|
35
|
+
"data-[disabled]:text-foreground-disabled"
|
|
36
|
+
],
|
|
37
|
+
secondary: [
|
|
38
|
+
"border-outline bg-floating text-on-floating backdrop-blur-frost",
|
|
39
|
+
"hover:bg-floating-hover",
|
|
40
|
+
"data-[disabled]:text-foreground-disabled"
|
|
41
|
+
],
|
|
42
|
+
ghost: [
|
|
43
|
+
"border-transparent bg-transparent text-foreground",
|
|
44
|
+
"hover:bg-control-highlight",
|
|
45
|
+
"data-[disabled]:text-foreground-disabled"
|
|
46
|
+
],
|
|
47
|
+
danger: [
|
|
48
|
+
"border-outline bg-danger text-on-danger",
|
|
49
|
+
"hover:bg-danger-hover",
|
|
50
|
+
"data-[disabled]:border-transparent data-[disabled]:bg-control",
|
|
51
|
+
"data-[disabled]:text-foreground-disabled"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Heights, icon geometry, press travel and label typography come from
|
|
56
|
+
* theme tokens. Smaller controls travel further in relative terms.
|
|
57
|
+
*/
|
|
58
|
+
size: {
|
|
59
|
+
sm: ["h-control-sm gap-1.5 px-3 text-label", "[&_svg]:size-icon-sm"],
|
|
60
|
+
md: ["h-control-md gap-2 px-4 text-label", "[&_svg]:size-icon-md"],
|
|
61
|
+
lg: ["h-control-lg gap-2 px-4 text-button", "[&_svg]:size-icon-lg"]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
defaultVariants: {
|
|
65
|
+
variant: "secondary",
|
|
66
|
+
size: "lg"
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const pressScales = {
|
|
70
|
+
sm: "[--cl-press-scale:var(--cl-press-scale-sm)]",
|
|
71
|
+
md: "[--cl-press-scale:var(--cl-press-scale-md)]",
|
|
72
|
+
lg: "[--cl-press-scale:var(--cl-press-scale-lg)]"
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A ClaraLight capsule button.
|
|
76
|
+
*
|
|
77
|
+
* Wraps Base UI's `Button` rather than rendering a bare `<button>`: that gives
|
|
78
|
+
* the `render` prop for polymorphism (rendering an `<a>` while keeping button
|
|
79
|
+
* semantics), and correct disabled behaviour for non-native elements, which a
|
|
80
|
+
* plain `disabled` attribute cannot express.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* <Button variant="primary">Upload</Button>
|
|
84
|
+
* <Button render={<a href="/docs" />}>Docs</Button>
|
|
85
|
+
*/
|
|
86
|
+
function Button$1({ className, wrapperClassName, variant, size, ...props }) {
|
|
87
|
+
return /* @__PURE__ */ jsx(Squircle, {
|
|
88
|
+
asChild: true,
|
|
89
|
+
radius: "capsule",
|
|
90
|
+
ring: true,
|
|
91
|
+
wrapperClassName: cn("cl-press inline-flex shrink-0", pressScales[size ?? "lg"], wrapperClassName),
|
|
92
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
93
|
+
className: cn(buttonVariants({
|
|
94
|
+
variant,
|
|
95
|
+
size
|
|
96
|
+
}), className),
|
|
97
|
+
...props
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { Button$1 as Button, buttonVariants };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button.js","names":["Button","BaseButton"],"sources":["../../src/ui/button.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button as BaseButton } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\nimport { Squircle } from \"@/lib/squircle\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * ClaraLight button variants.\n *\n * `secondary` is the default because ClaraLight is a tool-like design\n * language: most buttons in a real screen are neutral glass controls, and the\n * accent should mark one primary action per view, not every button.\n */\nexport const buttonVariants = cva(\n [\n \"inline-flex select-none items-center justify-center gap-2\",\n \"border font-sans whitespace-nowrap\",\n // Icons inherit the variant's foreground through currentColor.\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed\",\n ],\n {\n variants: {\n /**\n * Filled variants use the theme's perceptual hover mix. `ghost` instead\n * reveals a fill, because lifting nothing is still nothing.\n *\n * Disabled behaviour is deliberate and differs per variant: neutral\n * controls keep their glass layer, and semantic actions keep their\n * identity — except `danger`, which drops its colour so a destructive\n * action never reads as available.\n */\n variant: {\n primary: [\n \"border-outline bg-accent text-on-accent\",\n \"hover:bg-accent-hover\",\n \"data-[disabled]:text-foreground-disabled\",\n ],\n secondary: [\n \"border-outline bg-floating text-on-floating backdrop-blur-frost\",\n \"hover:bg-floating-hover\",\n \"data-[disabled]:text-foreground-disabled\",\n ],\n ghost: [\n \"border-transparent bg-transparent text-foreground\",\n \"hover:bg-control-highlight\",\n \"data-[disabled]:text-foreground-disabled\",\n ],\n danger: [\n \"border-outline bg-danger text-on-danger\",\n \"hover:bg-danger-hover\",\n \"data-[disabled]:border-transparent data-[disabled]:bg-control\",\n \"data-[disabled]:text-foreground-disabled\",\n ],\n },\n /**\n * Heights, icon geometry, press travel and label typography come from\n * theme tokens. Smaller controls travel further in relative terms.\n */\n size: {\n sm: [\"h-control-sm gap-1.5 px-3 text-label\", \"[&_svg]:size-icon-sm\"],\n md: [\"h-control-md gap-2 px-4 text-label\", \"[&_svg]:size-icon-md\"],\n lg: [\"h-control-lg gap-2 px-4 text-button\", \"[&_svg]:size-icon-lg\"],\n },\n },\n defaultVariants: { variant: \"secondary\", size: \"lg\" },\n },\n);\n\nconst pressScales = {\n sm: \"[--cl-press-scale:var(--cl-press-scale-sm)]\",\n md: \"[--cl-press-scale:var(--cl-press-scale-md)]\",\n lg: \"[--cl-press-scale:var(--cl-press-scale-lg)]\",\n} as const;\n\nexport type ButtonProps = Omit<ComponentProps<typeof BaseButton>, \"className\"> &\n VariantProps<typeof buttonVariants> & {\n /** Applied to the clipped element: fill, border, label. */\n className?: string;\n /**\n * Applied to the wrapper, which is what a caller's flex or grid lays out —\n * put width, margin and placement here.\n */\n wrapperClassName?: string;\n };\n\n/**\n * A ClaraLight capsule button.\n *\n * Wraps Base UI's `Button` rather than rendering a bare `<button>`: that gives\n * the `render` prop for polymorphism (rendering an `<a>` while keeping button\n * semantics), and correct disabled behaviour for non-native elements, which a\n * plain `disabled` attribute cannot express.\n *\n * @example\n * <Button variant=\"primary\">Upload</Button>\n * <Button render={<a href=\"/docs\" />}>Docs</Button>\n */\nexport function Button({ className, wrapperClassName, variant, size, ...props }: ButtonProps) {\n return (\n /*\n * `.cl-press` sits on the wrapper so the press scale moves fill, border and\n * shadow together — the SVG overlays are siblings of the shape, so scaling\n * the shape alone would tear the 1px border off the edge. `:active`\n * propagates to ancestors, so the wrapper sees the press with no JS.\n *\n * `ring` puts the focus indicator on the same element, because `clip-path`\n * crops `outline` to nothing on the shape.\n */\n <Squircle\n asChild\n radius=\"capsule\"\n ring\n wrapperClassName={cn(\n \"cl-press inline-flex shrink-0\",\n pressScales[size ?? \"lg\"],\n wrapperClassName,\n )}\n >\n <BaseButton className={cn(buttonVariants({ variant, size }), className)} {...props} />\n </Squircle>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAa,iBAAiB,IAC5B;CACE;CACA;CAEA;CACA;AACF,GACA;CACE,UAAU;;;;;;;;;;EAUR,SAAS;GACP,SAAS;IACP;IACA;IACA;GACF;GACA,WAAW;IACT;IACA;IACA;GACF;GACA,OAAO;IACL;IACA;IACA;GACF;GACA,QAAQ;IACN;IACA;IACA;IACA;GACF;EACF;;;;;EAKA,MAAM;GACJ,IAAI,CAAC,wCAAwC,sBAAsB;GACnE,IAAI,CAAC,sCAAsC,sBAAsB;GACjE,IAAI,CAAC,uCAAuC,sBAAsB;EACpE;CACF;CACA,iBAAiB;EAAE,SAAS;EAAa,MAAM;CAAK;AACtD,CACF;AAEA,MAAM,cAAc;CAClB,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;;;;;;;;;;;;;AAyBA,SAAgBA,SAAO,EAAE,WAAW,kBAAkB,SAAS,MAAM,GAAG,SAAsB;CAC5F,OAUE,oBAAC,UAAD;EACE,SAAA;EACA,QAAO;EACP,MAAA;EACA,kBAAkB,GAChB,iCACA,YAAY,QAAQ,OACpB,gBACF;EAEA,UAAA,oBAACC,QAAD;GAAY,WAAW,GAAG,eAAe;IAAE;IAAS;GAAK,CAAC,GAAG,SAAS;GAAG,GAAI;EAAQ,CAAA;CAC7E,CAAA;AAEd"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { RadiusToken } from "../lib/squircle.js";
|
|
2
|
+
import { ComponentProps, ReactNode } from "react";
|
|
3
|
+
import { VariantProps } from "class-variance-authority";
|
|
4
|
+
//#region src/ui/card.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Card surfaces, mapped onto the ClaraLight layer stack.
|
|
7
|
+
*
|
|
8
|
+
* `panel` is a container sitting on the window background (the default).
|
|
9
|
+
* `control` puts the card *inside* a panel, where the translucent overlay
|
|
10
|
+
* reads correctly over `--color-panel`.
|
|
11
|
+
* `frost` is for cards that float over arbitrary content and need a backdrop
|
|
12
|
+
* blur.
|
|
13
|
+
*
|
|
14
|
+
* The corner shape itself is not here: `Squircle` owns it, and renders these
|
|
15
|
+
* classes onto the clipped element while the SVG border and shadow overlays go
|
|
16
|
+
* on its wrapper. So `border-*` and `shadow-*` below are declarations Lisse
|
|
17
|
+
* reads and re-renders as squircle-shaped SVG, not declarations that paint.
|
|
18
|
+
*/
|
|
19
|
+
export declare const cardVariants: (props?: ({
|
|
20
|
+
variant?: "control" | "frost" | "panel" | null | undefined;
|
|
21
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
22
|
+
export interface CardProps extends ComponentProps<"div">, VariantProps<typeof cardVariants> {
|
|
23
|
+
/** Applied to the clipped element: fill, padding, grid placement inside the card. */
|
|
24
|
+
className?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Applied to the layout wrapper, which is the element the caller's grid or
|
|
27
|
+
* flex actually lays out. Put span and size classes here —
|
|
28
|
+
* `wrapperClassName="sm:col-span-2"`, not `className`.
|
|
29
|
+
*/
|
|
30
|
+
wrapperClassName?: string;
|
|
31
|
+
/** Which `--radius-*` token shapes the corners. */
|
|
32
|
+
radius?: RadiusToken;
|
|
33
|
+
/** Figma's `xi`; see `Squircle`. */
|
|
34
|
+
smoothing?: number;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/** A ClaraLight surface. Compose with the parts below, or use it bare. */
|
|
38
|
+
export declare function Card({ className, wrapperClassName, variant, radius, smoothing, children, ...props }: CardProps): import("react").JSX.Element;
|
|
39
|
+
export type CardHeaderProps = ComponentProps<"div">;
|
|
40
|
+
export declare function CardHeader({ className, ...props }: CardHeaderProps): import("react").JSX.Element;
|
|
41
|
+
export type CardTitleProps = ComponentProps<"h3">;
|
|
42
|
+
/**
|
|
43
|
+
* `headline` (18/24 bold, tightened to -0.55px) rather than `title`, so a card
|
|
44
|
+
* title outranks the row text it contains.
|
|
45
|
+
*/
|
|
46
|
+
export declare function CardTitle({ className, ...props }: CardTitleProps): import("react").JSX.Element;
|
|
47
|
+
export type CardDescriptionProps = ComponentProps<"p">;
|
|
48
|
+
export declare function CardDescription({ className, ...props }: CardDescriptionProps): import("react").JSX.Element;
|
|
49
|
+
export type CardContentProps = ComponentProps<"div">;
|
|
50
|
+
export declare function CardContent({ className, ...props }: CardContentProps): import("react").JSX.Element;
|
|
51
|
+
export type CardFooterProps = ComponentProps<"div">;
|
|
52
|
+
export declare function CardFooter({ className, ...props }: CardFooterProps): import("react").JSX.Element;
|
|
53
|
+
//#endregion
|
|
54
|
+
//# sourceMappingURL=card.d.ts.map
|
package/dist/ui/card.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Squircle } from "../lib/squircle.js";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
//#region src/ui/card.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Card surfaces, mapped onto the ClaraLight layer stack.
|
|
8
|
+
*
|
|
9
|
+
* `panel` is a container sitting on the window background (the default).
|
|
10
|
+
* `control` puts the card *inside* a panel, where the translucent overlay
|
|
11
|
+
* reads correctly over `--color-panel`.
|
|
12
|
+
* `frost` is for cards that float over arbitrary content and need a backdrop
|
|
13
|
+
* blur.
|
|
14
|
+
*
|
|
15
|
+
* The corner shape itself is not here: `Squircle` owns it, and renders these
|
|
16
|
+
* classes onto the clipped element while the SVG border and shadow overlays go
|
|
17
|
+
* on its wrapper. So `border-*` and `shadow-*` below are declarations Lisse
|
|
18
|
+
* reads and re-renders as squircle-shaped SVG, not declarations that paint.
|
|
19
|
+
*/
|
|
20
|
+
const cardVariants = cva(["text-foreground", "[&_svg]:shrink-0"], {
|
|
21
|
+
variants: { variant: {
|
|
22
|
+
panel: "border border-outline bg-panel shadow-panel",
|
|
23
|
+
control: "border border-outline bg-control",
|
|
24
|
+
frost: "cl-frost"
|
|
25
|
+
} },
|
|
26
|
+
defaultVariants: { variant: "panel" }
|
|
27
|
+
});
|
|
28
|
+
/** A ClaraLight surface. Compose with the parts below, or use it bare. */
|
|
29
|
+
function Card({ className, wrapperClassName, variant, radius = "medium", smoothing, children, ...props }) {
|
|
30
|
+
return /* @__PURE__ */ jsx(Squircle, {
|
|
31
|
+
radius,
|
|
32
|
+
smoothing,
|
|
33
|
+
wrapperClassName,
|
|
34
|
+
className: cn(cardVariants({ variant }), className),
|
|
35
|
+
"data-cl-slot": "card",
|
|
36
|
+
...props,
|
|
37
|
+
children
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function CardHeader({ className, ...props }) {
|
|
41
|
+
return /* @__PURE__ */ jsx("div", {
|
|
42
|
+
"data-cl-slot": "card-header",
|
|
43
|
+
className: cn("flex flex-col gap-1 p-4", className),
|
|
44
|
+
...props
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `headline` (18/24 bold, tightened to -0.55px) rather than `title`, so a card
|
|
49
|
+
* title outranks the row text it contains.
|
|
50
|
+
*/
|
|
51
|
+
function CardTitle({ className, ...props }) {
|
|
52
|
+
return /* @__PURE__ */ jsx("h3", {
|
|
53
|
+
"data-cl-slot": "card-title",
|
|
54
|
+
className: cn("text-headline text-foreground", className),
|
|
55
|
+
...props
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function CardDescription({ className, ...props }) {
|
|
59
|
+
return /* @__PURE__ */ jsx("p", {
|
|
60
|
+
"data-cl-slot": "card-description",
|
|
61
|
+
className: cn("text-caption text-foreground-tertiary", className),
|
|
62
|
+
...props
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function CardContent({ className, ...props }) {
|
|
66
|
+
return /* @__PURE__ */ jsx("div", {
|
|
67
|
+
"data-cl-slot": "card-content",
|
|
68
|
+
className: cn("px-4 pb-4 [&:first-child]:pt-4", className),
|
|
69
|
+
...props
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function CardFooter({ className, ...props }) {
|
|
73
|
+
return /* @__PURE__ */ jsx("div", {
|
|
74
|
+
"data-cl-slot": "card-footer",
|
|
75
|
+
className: cn("flex items-center gap-2 px-4 pb-4", className),
|
|
76
|
+
...props
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, cardVariants };
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.js","names":[],"sources":["../../src/ui/card.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { type RadiusToken, Squircle } from \"@/lib/squircle\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Card surfaces, mapped onto the ClaraLight layer stack.\n *\n * `panel` is a container sitting on the window background (the default).\n * `control` puts the card *inside* a panel, where the translucent overlay\n * reads correctly over `--color-panel`.\n * `frost` is for cards that float over arbitrary content and need a backdrop\n * blur.\n *\n * The corner shape itself is not here: `Squircle` owns it, and renders these\n * classes onto the clipped element while the SVG border and shadow overlays go\n * on its wrapper. So `border-*` and `shadow-*` below are declarations Lisse\n * reads and re-renders as squircle-shaped SVG, not declarations that paint.\n */\nexport const cardVariants = cva([\"text-foreground\", \"[&_svg]:shrink-0\"], {\n variants: {\n variant: {\n panel: \"border border-outline bg-panel shadow-panel\",\n control: \"border border-outline bg-control\",\n frost: \"cl-frost\",\n },\n },\n defaultVariants: { variant: \"panel\" },\n});\n\nexport interface CardProps extends ComponentProps<\"div\">, VariantProps<typeof cardVariants> {\n /** Applied to the clipped element: fill, padding, grid placement inside the card. */\n className?: string;\n /**\n * Applied to the layout wrapper, which is the element the caller's grid or\n * flex actually lays out. Put span and size classes here —\n * `wrapperClassName=\"sm:col-span-2\"`, not `className`.\n */\n wrapperClassName?: string;\n /** Which `--radius-*` token shapes the corners. */\n radius?: RadiusToken;\n /** Figma's `xi`; see `Squircle`. */\n smoothing?: number;\n children?: ReactNode;\n}\n\n/** A ClaraLight surface. Compose with the parts below, or use it bare. */\nexport function Card({\n className,\n wrapperClassName,\n variant,\n radius = \"medium\",\n smoothing,\n children,\n ...props\n}: CardProps) {\n return (\n <Squircle\n radius={radius}\n smoothing={smoothing}\n wrapperClassName={wrapperClassName}\n className={cn(cardVariants({ variant }), className)}\n data-cl-slot=\"card\"\n {...props}\n >\n {children}\n </Squircle>\n );\n}\n\nexport type CardHeaderProps = ComponentProps<\"div\">;\nexport function CardHeader({ className, ...props }: CardHeaderProps) {\n return (\n <div\n data-cl-slot=\"card-header\"\n className={cn(\"flex flex-col gap-1 p-4\", className)}\n {...props}\n />\n );\n}\n\nexport type CardTitleProps = ComponentProps<\"h3\">;\n\n/**\n * `headline` (18/24 bold, tightened to -0.55px) rather than `title`, so a card\n * title outranks the row text it contains.\n */\nexport function CardTitle({ className, ...props }: CardTitleProps) {\n return (\n <h3\n data-cl-slot=\"card-title\"\n className={cn(\"text-headline text-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport type CardDescriptionProps = ComponentProps<\"p\">;\n\nexport function CardDescription({ className, ...props }: CardDescriptionProps) {\n return (\n <p\n data-cl-slot=\"card-description\"\n className={cn(\"text-caption text-foreground-tertiary\", className)}\n {...props}\n />\n );\n}\n\nexport type CardContentProps = ComponentProps<\"div\">;\n\nexport function CardContent({ className, ...props }: CardContentProps) {\n return (\n <div\n data-cl-slot=\"card-content\"\n className={cn(\"px-4 pb-4 [&:first-child]:pt-4\", className)}\n {...props}\n />\n );\n}\n\nexport type CardFooterProps = ComponentProps<\"div\">;\n\nexport function CardFooter({ className, ...props }: CardFooterProps) {\n return (\n <div\n data-cl-slot=\"card-footer\"\n className={cn(\"flex items-center gap-2 px-4 pb-4\", className)}\n {...props}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,MAAa,eAAe,IAAI,CAAC,mBAAmB,kBAAkB,GAAG;CACvE,UAAU,EACR,SAAS;EACP,OAAO;EACP,SAAS;EACT,OAAO;CACT,EACF;CACA,iBAAiB,EAAE,SAAS,QAAQ;AACtC,CAAC;;AAmBD,SAAgB,KAAK,EACnB,WACA,kBACA,SACA,SAAS,UACT,WACA,UACA,GAAG,SACS;CACZ,OACE,oBAAC,UAAD;EACU;EACG;EACO;EAClB,WAAW,GAAG,aAAa,EAAE,QAAQ,CAAC,GAAG,SAAS;EAClD,gBAAa;EACb,GAAI;EAEH;CACO,CAAA;AAEd;AAGA,SAAgB,WAAW,EAAE,WAAW,GAAG,SAA0B;CACnE,OACE,oBAAC,OAAD;EACE,gBAAa;EACb,WAAW,GAAG,2BAA2B,SAAS;EAClD,GAAI;CACL,CAAA;AAEL;;;;;AAQA,SAAgB,UAAU,EAAE,WAAW,GAAG,SAAyB;CACjE,OACE,oBAAC,MAAD;EACE,gBAAa;EACb,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;CACL,CAAA;AAEL;AAIA,SAAgB,gBAAgB,EAAE,WAAW,GAAG,SAA+B;CAC7E,OACE,oBAAC,KAAD;EACE,gBAAa;EACb,WAAW,GAAG,yCAAyC,SAAS;EAChE,GAAI;CACL,CAAA;AAEL;AAIA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAA2B;CACrE,OACE,oBAAC,OAAD;EACE,gBAAa;EACb,WAAW,GAAG,kCAAkC,SAAS;EACzD,GAAI;CACL,CAAA;AAEL;AAIA,SAAgB,WAAW,EAAE,WAAW,GAAG,SAA0B;CACnE,OACE,oBAAC,OAAD;EACE,gBAAa;EACb,WAAW,GAAG,qCAAqC,SAAS;EAC5D,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ComponentProps, ReactNode } from "react";
|
|
2
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
3
|
+
//#region src/ui/dialog.d.ts
|
|
4
|
+
export type DialogProps<Payload = unknown> = Dialog.Root.Props<Payload>;
|
|
5
|
+
/**
|
|
6
|
+
* The modal root, which also remembers where it was opened from.
|
|
7
|
+
*
|
|
8
|
+
* `Dialog.Popup` grows out of the trigger's rectangle, so the two have to know
|
|
9
|
+
* about each other; passing the anchor between siblings is what this component
|
|
10
|
+
* exists for. It is the same provider the select will use, so both surfaces
|
|
11
|
+
* morph with one implementation.
|
|
12
|
+
*
|
|
13
|
+
* The open state is mirrored here — not invented here. `open` still wins when it
|
|
14
|
+
* is controlled, and `onOpenChange` still fires for every change, so a consumer's
|
|
15
|
+
* own state stays the single source of truth; the mirror only exists because the
|
|
16
|
+
* popup, which is a *sibling* of the trigger, has to know when a dismissal began
|
|
17
|
+
* in order to reverse the morph before Base UI unmounts it.
|
|
18
|
+
*/ declare function Dialog$1<Payload = unknown>({ open, defaultOpen, onOpenChange, ...props }: DialogProps<Payload>): import("react").JSX.Element;
|
|
19
|
+
export type DialogTriggerProps = Omit<ComponentProps<typeof Dialog.Trigger>, "className"> & {
|
|
20
|
+
className?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function DialogTrigger({ className, ref, ...props }: DialogTriggerProps): import("react").JSX.Element;
|
|
23
|
+
export type DialogBackdropProps = Omit<ComponentProps<typeof Dialog.Backdrop>, "className"> & {
|
|
24
|
+
className?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The barrier behind the dialog. Fades on one duration only, so it lands fast
|
|
28
|
+
* enough that the page behind never feels stuck.
|
|
29
|
+
*/
|
|
30
|
+
export declare function DialogBackdrop({ className, ...props }: DialogBackdropProps): import("react").JSX.Element;
|
|
31
|
+
export type DialogPopupProps = Omit<ComponentProps<typeof Dialog.Popup>, "className"> & {
|
|
32
|
+
/** Applied to the clipped element: the frost, padding and text. */
|
|
33
|
+
className?: string;
|
|
34
|
+
/** Applied to the wrapper, which is what gets positioned and animated. */
|
|
35
|
+
wrapperClassName?: string;
|
|
36
|
+
/** Rendered inside the popup, above the body. */
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The frosted modal surface.
|
|
41
|
+
*
|
|
42
|
+
* Base UI marks the popup with `data-starting-style` and `data-ending-style`
|
|
43
|
+
* and waits for animations on the popup element before calling `forceUnmount`.
|
|
44
|
+
* The visible transition belongs to the wrapper, so `cl-exit-sentinel` adds a
|
|
45
|
+
* non-visual custom-property animation to the popup; Base UI can then wait for
|
|
46
|
+
* the wrapper's exit duration without moving the clipped surface separately.
|
|
47
|
+
*
|
|
48
|
+
* The positioning and the entrance live on the **wrapper**, not the popup. The
|
|
49
|
+
* popup is the clipped element and the SVG border and shadow overlays are its
|
|
50
|
+
* siblings, so scaling the popup alone would leave the 1px border and the shadow
|
|
51
|
+
* at their final size — 5% of a 512px dialog is 25px, plainly visible over the
|
|
52
|
+
* 250ms entrance. `.cl-enter-root` lifts Base UI's transition state onto the
|
|
53
|
+
* wrapper with `:has()`, so fill, border and shadow move as one object.
|
|
54
|
+
*
|
|
55
|
+
* The wrapper is also what `useSurfaceMorph` transforms: the dialog is laid out
|
|
56
|
+
* at its final size and carried from the trigger's rectangle onto itself, which
|
|
57
|
+
* is the one entrance the whole language shares. The geometry drives the
|
|
58
|
+
* content's opacity with it, so the layer is still visible while it collapses
|
|
59
|
+
* back into its trigger.
|
|
60
|
+
*
|
|
61
|
+
* **No shadow**, unlike every other floating layer. A modal is separated from the
|
|
62
|
+
* page by its scrim, and `--shadow-frost` under a 45% scrim is two depth cues
|
|
63
|
+
* claiming the same edge, which reads as a halo rather than as distance. A dialog
|
|
64
|
+
* that has no scrim — `modal={false}`, or one laid over its own artwork — asks for
|
|
65
|
+
* it back with `shadow-dialog`, which is what that token is for.
|
|
66
|
+
*/
|
|
67
|
+
export declare function DialogPopup({ className, wrapperClassName, ...props }: DialogPopupProps): import("react").JSX.Element;
|
|
68
|
+
export type DialogTitleProps = Omit<ComponentProps<typeof Dialog.Title>, "className"> & {
|
|
69
|
+
className?: string;
|
|
70
|
+
};
|
|
71
|
+
export declare function DialogTitle({ className, ...props }: DialogTitleProps): import("react").JSX.Element;
|
|
72
|
+
export type DialogDescriptionProps = Omit<ComponentProps<typeof Dialog.Description>, "className"> & {
|
|
73
|
+
className?: string;
|
|
74
|
+
};
|
|
75
|
+
export declare function DialogDescription({ className, ...props }: DialogDescriptionProps): import("react").JSX.Element;
|
|
76
|
+
export type DialogCloseProps = Omit<ComponentProps<typeof Dialog.Close>, "className"> & {
|
|
77
|
+
className?: string;
|
|
78
|
+
};
|
|
79
|
+
export declare function DialogClose({ className, ...props }: DialogCloseProps): import("react").JSX.Element;
|
|
80
|
+
//#endregion
|
|
81
|
+
export { Dialog$1 as Dialog };
|
|
82
|
+
//# sourceMappingURL=dialog.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Squircle, composeRefs } from "../lib/squircle.js";
|
|
4
|
+
import { MorphSourceProvider, useMorphAnchor, useMorphSource, useSurfaceMorph } from "../lib/morph.js";
|
|
5
|
+
import { useCallback, useMemo, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
8
|
+
//#region src/ui/dialog.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The modal root, which also remembers where it was opened from.
|
|
11
|
+
*
|
|
12
|
+
* `Dialog.Popup` grows out of the trigger's rectangle, so the two have to know
|
|
13
|
+
* about each other; passing the anchor between siblings is what this component
|
|
14
|
+
* exists for. It is the same provider the select will use, so both surfaces
|
|
15
|
+
* morph with one implementation.
|
|
16
|
+
*
|
|
17
|
+
* The open state is mirrored here — not invented here. `open` still wins when it
|
|
18
|
+
* is controlled, and `onOpenChange` still fires for every change, so a consumer's
|
|
19
|
+
* own state stays the single source of truth; the mirror only exists because the
|
|
20
|
+
* popup, which is a *sibling* of the trigger, has to know when a dismissal began
|
|
21
|
+
* in order to reverse the morph before Base UI unmounts it.
|
|
22
|
+
*/ function Dialog$1({ open, defaultOpen, onOpenChange, ...props }) {
|
|
23
|
+
const [uncontrolled, setUncontrolled] = useState(defaultOpen ?? false);
|
|
24
|
+
const handleOpenChange = useCallback((next, details) => {
|
|
25
|
+
setUncontrolled(next);
|
|
26
|
+
onOpenChange?.(next, details);
|
|
27
|
+
}, [onOpenChange]);
|
|
28
|
+
return /* @__PURE__ */ jsx(MorphSourceProvider, {
|
|
29
|
+
open: open ?? uncontrolled,
|
|
30
|
+
children: /* @__PURE__ */ jsx(Dialog.Root, {
|
|
31
|
+
open,
|
|
32
|
+
defaultOpen: open === void 0 ? defaultOpen : void 0,
|
|
33
|
+
onOpenChange: handleOpenChange,
|
|
34
|
+
...props
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function DialogTrigger({ className, ref, ...props }) {
|
|
39
|
+
const anchor = useMorphAnchor();
|
|
40
|
+
const mergedRef = useMemo(() => composeRefs(anchor ?? void 0, ref), [anchor, ref]);
|
|
41
|
+
return /* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
42
|
+
ref: mergedRef,
|
|
43
|
+
className,
|
|
44
|
+
...props
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The barrier behind the dialog. Fades on one duration only, so it lands fast
|
|
49
|
+
* enough that the page behind never feels stuck.
|
|
50
|
+
*/
|
|
51
|
+
function DialogBackdrop({ className, ...props }) {
|
|
52
|
+
return /* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
53
|
+
"data-cl-slot": "dialog-backdrop",
|
|
54
|
+
className: cn("fixed inset-0 z-50 bg-scrim", "transition-opacity duration-(--cl-duration-surface) ease-cl-out", "data-[starting-style]:opacity-0 data-[ending-style]:opacity-0", className),
|
|
55
|
+
...props
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The frosted modal surface.
|
|
60
|
+
*
|
|
61
|
+
* Base UI marks the popup with `data-starting-style` and `data-ending-style`
|
|
62
|
+
* and waits for animations on the popup element before calling `forceUnmount`.
|
|
63
|
+
* The visible transition belongs to the wrapper, so `cl-exit-sentinel` adds a
|
|
64
|
+
* non-visual custom-property animation to the popup; Base UI can then wait for
|
|
65
|
+
* the wrapper's exit duration without moving the clipped surface separately.
|
|
66
|
+
*
|
|
67
|
+
* The positioning and the entrance live on the **wrapper**, not the popup. The
|
|
68
|
+
* popup is the clipped element and the SVG border and shadow overlays are its
|
|
69
|
+
* siblings, so scaling the popup alone would leave the 1px border and the shadow
|
|
70
|
+
* at their final size — 5% of a 512px dialog is 25px, plainly visible over the
|
|
71
|
+
* 250ms entrance. `.cl-enter-root` lifts Base UI's transition state onto the
|
|
72
|
+
* wrapper with `:has()`, so fill, border and shadow move as one object.
|
|
73
|
+
*
|
|
74
|
+
* The wrapper is also what `useSurfaceMorph` transforms: the dialog is laid out
|
|
75
|
+
* at its final size and carried from the trigger's rectangle onto itself, which
|
|
76
|
+
* is the one entrance the whole language shares. The geometry drives the
|
|
77
|
+
* content's opacity with it, so the layer is still visible while it collapses
|
|
78
|
+
* back into its trigger.
|
|
79
|
+
*
|
|
80
|
+
* **No shadow**, unlike every other floating layer. A modal is separated from the
|
|
81
|
+
* page by its scrim, and `--shadow-frost` under a 45% scrim is two depth cues
|
|
82
|
+
* claiming the same edge, which reads as a halo rather than as distance. A dialog
|
|
83
|
+
* that has no scrim — `modal={false}`, or one laid over its own artwork — asks for
|
|
84
|
+
* it back with `shadow-dialog`, which is what that token is for.
|
|
85
|
+
*/
|
|
86
|
+
function DialogPopup({ className, wrapperClassName, ...props }) {
|
|
87
|
+
const source = useMorphSource();
|
|
88
|
+
const [surface, setSurface] = useState(null);
|
|
89
|
+
useSurfaceMorph({
|
|
90
|
+
surface,
|
|
91
|
+
anchor: source?.anchor,
|
|
92
|
+
open: source?.open ?? true
|
|
93
|
+
});
|
|
94
|
+
return /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(DialogBackdrop, {}), /* @__PURE__ */ jsx(Squircle, {
|
|
95
|
+
asChild: true,
|
|
96
|
+
radius: "dialog",
|
|
97
|
+
wrapperRef: setSurface,
|
|
98
|
+
wrapperClassName: cn("cl-enter-root cl-morph fixed top-1/2 left-1/2 z-50 w-full max-w-lg", "-translate-x-1/2 -translate-y-1/2", wrapperClassName),
|
|
99
|
+
children: /* @__PURE__ */ jsx(Dialog.Popup, {
|
|
100
|
+
"data-cl-slot": "dialog-popup",
|
|
101
|
+
className: cn("cl-exit-sentinel cl-frost p-6 font-sans text-foreground shadow-none", className),
|
|
102
|
+
...props
|
|
103
|
+
})
|
|
104
|
+
})] });
|
|
105
|
+
}
|
|
106
|
+
function DialogTitle({ className, ...props }) {
|
|
107
|
+
return /* @__PURE__ */ jsx(Dialog.Title, {
|
|
108
|
+
"data-cl-slot": "dialog-title",
|
|
109
|
+
className: cn("text-headline text-foreground", className),
|
|
110
|
+
...props
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function DialogDescription({ className, ...props }) {
|
|
114
|
+
return /* @__PURE__ */ jsx(Dialog.Description, {
|
|
115
|
+
"data-cl-slot": "dialog-description",
|
|
116
|
+
className: cn("text-caption text-foreground-tertiary", className),
|
|
117
|
+
...props
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function DialogClose({ className, ...props }) {
|
|
121
|
+
return /* @__PURE__ */ jsx(Dialog.Close, {
|
|
122
|
+
className,
|
|
123
|
+
...props
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { Dialog$1 as Dialog, DialogBackdrop, DialogClose, DialogDescription, DialogPopup, DialogTitle, DialogTrigger };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog.js","names":["Dialog","BaseDialog"],"sources":["../../src/ui/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as BaseDialog } from \"@base-ui/react/dialog\";\nimport { type ComponentProps, type ReactNode, useCallback, useMemo, useState } from \"react\";\nimport { MorphSourceProvider, useMorphAnchor, useMorphSource, useSurfaceMorph } from \"@/lib/morph\";\nimport { composeRefs, Squircle } from \"@/lib/squircle\";\nimport { cn } from \"@/lib/utils\";\n\nexport type DialogProps<Payload = unknown> = BaseDialog.Root.Props<Payload>;\n\n/**\n * The modal root, which also remembers where it was opened from.\n *\n * `Dialog.Popup` grows out of the trigger's rectangle, so the two have to know\n * about each other; passing the anchor between siblings is what this component\n * exists for. It is the same provider the select will use, so both surfaces\n * morph with one implementation.\n *\n * The open state is mirrored here — not invented here. `open` still wins when it\n * is controlled, and `onOpenChange` still fires for every change, so a consumer's\n * own state stays the single source of truth; the mirror only exists because the\n * popup, which is a *sibling* of the trigger, has to know when a dismissal began\n * in order to reverse the morph before Base UI unmounts it.\n */ export function Dialog<Payload = unknown>({\n open,\n defaultOpen,\n onOpenChange,\n ...props\n}: DialogProps<Payload>) {\n const [uncontrolled, setUncontrolled] = useState(defaultOpen ?? false);\n const handleOpenChange = useCallback(\n (next: boolean, details: BaseDialog.Root.ChangeEventDetails) => {\n setUncontrolled(next);\n onOpenChange?.(next, details);\n },\n [onOpenChange],\n );\n\n return (\n <MorphSourceProvider open={open ?? uncontrolled}>\n <BaseDialog.Root\n open={open}\n defaultOpen={open === undefined ? defaultOpen : undefined}\n onOpenChange={handleOpenChange}\n {...props}\n />\n </MorphSourceProvider>\n );\n}\n\nexport type DialogTriggerProps = Omit<ComponentProps<typeof BaseDialog.Trigger>, \"className\"> & {\n className?: string;\n};\n\nexport function DialogTrigger({ className, ref, ...props }: DialogTriggerProps) {\n const anchor = useMorphAnchor();\n const mergedRef = useMemo(() => composeRefs(anchor ?? undefined, ref), [anchor, ref]);\n return <BaseDialog.Trigger ref={mergedRef} className={className} {...props} />;\n}\n\nexport type DialogBackdropProps = Omit<ComponentProps<typeof BaseDialog.Backdrop>, \"className\"> & {\n className?: string;\n};\n\n/**\n * The barrier behind the dialog. Fades on one duration only, so it lands fast\n * enough that the page behind never feels stuck.\n */\nexport function DialogBackdrop({ className, ...props }: DialogBackdropProps) {\n return (\n <BaseDialog.Backdrop\n data-cl-slot=\"dialog-backdrop\"\n className={cn(\n \"fixed inset-0 z-50 bg-scrim\",\n \"transition-opacity duration-(--cl-duration-surface) ease-cl-out\",\n \"data-[starting-style]:opacity-0 data-[ending-style]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport type DialogPopupProps = Omit<ComponentProps<typeof BaseDialog.Popup>, \"className\"> & {\n /** Applied to the clipped element: the frost, padding and text. */\n className?: string;\n /** Applied to the wrapper, which is what gets positioned and animated. */\n wrapperClassName?: string;\n /** Rendered inside the popup, above the body. */\n children?: ReactNode;\n};\n\n/**\n * The frosted modal surface.\n *\n * Base UI marks the popup with `data-starting-style` and `data-ending-style`\n * and waits for animations on the popup element before calling `forceUnmount`.\n * The visible transition belongs to the wrapper, so `cl-exit-sentinel` adds a\n * non-visual custom-property animation to the popup; Base UI can then wait for\n * the wrapper's exit duration without moving the clipped surface separately.\n *\n * The positioning and the entrance live on the **wrapper**, not the popup. The\n * popup is the clipped element and the SVG border and shadow overlays are its\n * siblings, so scaling the popup alone would leave the 1px border and the shadow\n * at their final size — 5% of a 512px dialog is 25px, plainly visible over the\n * 250ms entrance. `.cl-enter-root` lifts Base UI's transition state onto the\n * wrapper with `:has()`, so fill, border and shadow move as one object.\n *\n * The wrapper is also what `useSurfaceMorph` transforms: the dialog is laid out\n * at its final size and carried from the trigger's rectangle onto itself, which\n * is the one entrance the whole language shares. The geometry drives the\n * content's opacity with it, so the layer is still visible while it collapses\n * back into its trigger.\n *\n * **No shadow**, unlike every other floating layer. A modal is separated from the\n * page by its scrim, and `--shadow-frost` under a 45% scrim is two depth cues\n * claiming the same edge, which reads as a halo rather than as distance. A dialog\n * that has no scrim — `modal={false}`, or one laid over its own artwork — asks for\n * it back with `shadow-dialog`, which is what that token is for.\n */\nexport function DialogPopup({ className, wrapperClassName, ...props }: DialogPopupProps) {\n const source = useMorphSource();\n const [surface, setSurface] = useState<HTMLDivElement | null>(null);\n useSurfaceMorph({ surface, anchor: source?.anchor, open: source?.open ?? true });\n\n return (\n <BaseDialog.Portal>\n <DialogBackdrop />\n <Squircle\n asChild\n radius=\"dialog\"\n wrapperRef={setSurface}\n wrapperClassName={cn(\n \"cl-enter-root cl-morph fixed top-1/2 left-1/2 z-50 w-full max-w-lg\",\n \"-translate-x-1/2 -translate-y-1/2\",\n wrapperClassName,\n )}\n >\n <BaseDialog.Popup\n data-cl-slot=\"dialog-popup\"\n className={cn(\n \"cl-exit-sentinel cl-frost p-6 font-sans text-foreground shadow-none\",\n className,\n )}\n {...props}\n />\n </Squircle>\n </BaseDialog.Portal>\n );\n}\n\nexport type DialogTitleProps = Omit<ComponentProps<typeof BaseDialog.Title>, \"className\"> & {\n className?: string;\n};\n\nexport function DialogTitle({ className, ...props }: DialogTitleProps) {\n return (\n <BaseDialog.Title\n data-cl-slot=\"dialog-title\"\n className={cn(\"text-headline text-foreground\", className)}\n {...props}\n />\n );\n}\n\nexport type DialogDescriptionProps = Omit<\n ComponentProps<typeof BaseDialog.Description>,\n \"className\"\n> & { className?: string };\n\nexport function DialogDescription({ className, ...props }: DialogDescriptionProps) {\n return (\n <BaseDialog.Description\n data-cl-slot=\"dialog-description\"\n className={cn(\"text-caption text-foreground-tertiary\", className)}\n {...props}\n />\n );\n}\n\nexport type DialogCloseProps = Omit<ComponentProps<typeof BaseDialog.Close>, \"className\"> & {\n className?: string;\n};\n\nexport function DialogClose({ className, ...props }: DialogCloseProps) {\n return <BaseDialog.Close className={className} {...props} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;GAuBI,SAAgBA,SAA0B,EAC5C,MACA,aACA,cACA,GAAG,SACoB;CACvB,MAAM,CAAC,cAAc,mBAAmB,SAAS,eAAe,KAAK;CACrE,MAAM,mBAAmB,aACtB,MAAe,YAAgD;EAC9D,gBAAgB,IAAI;EACpB,eAAe,MAAM,OAAO;CAC9B,GACA,CAAC,YAAY,CACf;CAEA,OACE,oBAAC,qBAAD;EAAqB,MAAM,QAAQ;EACjC,UAAA,oBAACC,OAAW,MAAZ;GACQ;GACN,aAAa,SAAS,KAAA,IAAY,cAAc,KAAA;GAChD,cAAc;GACd,GAAI;EACL,CAAA;CACkB,CAAA;AAEzB;AAMA,SAAgB,cAAc,EAAE,WAAW,KAAK,GAAG,SAA6B;CAC9E,MAAM,SAAS,eAAe;CAC9B,MAAM,YAAY,cAAc,YAAY,UAAU,KAAA,GAAW,GAAG,GAAG,CAAC,QAAQ,GAAG,CAAC;CACpF,OAAO,oBAACA,OAAW,SAAZ;EAAoB,KAAK;EAAsB;EAAW,GAAI;CAAQ,CAAA;AAC/E;;;;;AAUA,SAAgB,eAAe,EAAE,WAAW,GAAG,SAA8B;CAC3E,OACE,oBAACA,OAAW,UAAZ;EACE,gBAAa;EACb,WAAW,GACT,+BACA,mEACA,iEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,YAAY,EAAE,WAAW,kBAAkB,GAAG,SAA2B;CACvF,MAAM,SAAS,eAAe;CAC9B,MAAM,CAAC,SAAS,cAAc,SAAgC,IAAI;CAClE,gBAAgB;EAAE;EAAS,QAAQ,QAAQ;EAAQ,MAAM,QAAQ,QAAQ;CAAK,CAAC;CAE/E,OACE,qBAACA,OAAW,QAAZ,EAAA,UAAA,CACE,oBAAC,gBAAD,CAAiB,CAAA,GACjB,oBAAC,UAAD;EACE,SAAA;EACA,QAAO;EACP,YAAY;EACZ,kBAAkB,GAChB,sEACA,qCACA,gBACF;EAEA,UAAA,oBAACA,OAAW,OAAZ;GACE,gBAAa;GACb,WAAW,GACT,uEACA,SACF;GACA,GAAI;EACL,CAAA;CACO,CAAA,CACO,EAAA,CAAA;AAEvB;AAMA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAA2B;CACrE,OACE,oBAACA,OAAW,OAAZ;EACE,gBAAa;EACb,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;CACL,CAAA;AAEL;AAOA,SAAgB,kBAAkB,EAAE,WAAW,GAAG,SAAiC;CACjF,OACE,oBAACA,OAAW,aAAZ;EACE,gBAAa;EACb,WAAW,GAAG,yCAAyC,SAAS;EAChE,GAAI;CACL,CAAA;AAEL;AAMA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAA2B;CACrE,OAAO,oBAACA,OAAW,OAAZ;EAA6B;EAAW,GAAI;CAAQ,CAAA;AAC7D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
import { VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Input } from "@base-ui/react/input";
|
|
4
|
+
//#region src/ui/input.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* ClaraLight text field.
|
|
7
|
+
*
|
|
8
|
+
* The fill is `control` — a translucent overlay rather than an opaque colour —
|
|
9
|
+
* so the same field reads correctly whether it sits on `background` or on a
|
|
10
|
+
* `panel`. That is the core trick of the layered surface stack and it is why
|
|
11
|
+
* this component must not hard-code a background.
|
|
12
|
+
*/
|
|
13
|
+
export declare const inputVariants: (props?: ({
|
|
14
|
+
size?: "lg" | "md" | "sm" | null | undefined;
|
|
15
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
16
|
+
export type InputProps = Omit<ComponentProps<typeof Input>, "className" | "size"> & VariantProps<typeof inputVariants> & {
|
|
17
|
+
className?: string;
|
|
18
|
+
/** Applied to the wrapper, which is what a caller's layout sizes. */
|
|
19
|
+
wrapperClassName?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A ClaraLight text field.
|
|
23
|
+
*
|
|
24
|
+
* `sm` uses caption, `md` uses callout, and `lg` uses body typography.
|
|
25
|
+
* Dense inspector rows stay legible without shrinking the hit target.
|
|
26
|
+
*
|
|
27
|
+
* Note the `size` in `InputProps` is the ClaraLight density step, not the
|
|
28
|
+
* native `<input size>` character-width hint — the native attribute is omitted
|
|
29
|
+
* above, because the two collide and an intersection would make both unusable.
|
|
30
|
+
*/
|
|
31
|
+
declare function Input$1({ className, wrapperClassName, size, ...props }: InputProps): import("react").JSX.Element;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { Input$1 as Input };
|
|
34
|
+
//# sourceMappingURL=input.d.ts.map
|
package/dist/ui/input.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
import { Squircle } from "../lib/squircle.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import { Input } from "@base-ui/react/input";
|
|
7
|
+
//#region src/ui/input.tsx
|
|
8
|
+
/**
|
|
9
|
+
* ClaraLight text field.
|
|
10
|
+
*
|
|
11
|
+
* The fill is `control` — a translucent overlay rather than an opaque colour —
|
|
12
|
+
* so the same field reads correctly whether it sits on `background` or on a
|
|
13
|
+
* `panel`. That is the core trick of the layered surface stack and it is why
|
|
14
|
+
* this component must not hard-code a background.
|
|
15
|
+
*/
|
|
16
|
+
const inputVariants = cva([
|
|
17
|
+
"w-full border border-outline",
|
|
18
|
+
"bg-control font-sans text-foreground",
|
|
19
|
+
"placeholder:text-foreground-hint",
|
|
20
|
+
"transition-[background-color,border-color,color] duration-(--cl-duration-fast) ease-cl-out",
|
|
21
|
+
"hover:bg-control-highlight",
|
|
22
|
+
"data-[disabled]:pointer-events-none",
|
|
23
|
+
"data-[disabled]:text-foreground-disabled data-[disabled]:placeholder:text-foreground-disabled",
|
|
24
|
+
"data-[invalid]:border-danger"
|
|
25
|
+
], {
|
|
26
|
+
variants: {
|
|
27
|
+
/** Shared control geometry; typography follows density. */
|
|
28
|
+
size: {
|
|
29
|
+
sm: "h-control-sm px-2 text-caption",
|
|
30
|
+
md: "h-control-md px-3 text-callout",
|
|
31
|
+
lg: "h-control-lg px-4 text-body"
|
|
32
|
+
} },
|
|
33
|
+
defaultVariants: { size: "md" }
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* A ClaraLight text field.
|
|
37
|
+
*
|
|
38
|
+
* `sm` uses caption, `md` uses callout, and `lg` uses body typography.
|
|
39
|
+
* Dense inspector rows stay legible without shrinking the hit target.
|
|
40
|
+
*
|
|
41
|
+
* Note the `size` in `InputProps` is the ClaraLight density step, not the
|
|
42
|
+
* native `<input size>` character-width hint — the native attribute is omitted
|
|
43
|
+
* above, because the two collide and an intersection would make both unusable.
|
|
44
|
+
*/
|
|
45
|
+
function Input$1({ className, wrapperClassName, size, ...props }) {
|
|
46
|
+
return /* @__PURE__ */ jsx(Squircle, {
|
|
47
|
+
asChild: true,
|
|
48
|
+
radius: "control",
|
|
49
|
+
ring: "field",
|
|
50
|
+
wrapperClassName: cn("block w-full", wrapperClassName),
|
|
51
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
52
|
+
className: cn(inputVariants({ size }), className),
|
|
53
|
+
...props
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { Input$1 as Input, inputVariants };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","names":["Input","BaseInput"],"sources":["../../src/ui/input.tsx"],"sourcesContent":["\"use client\";\n\nimport { Input as BaseInput } from \"@base-ui/react/input\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\nimport { Squircle } from \"@/lib/squircle\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * ClaraLight text field.\n *\n * The fill is `control` — a translucent overlay rather than an opaque colour —\n * so the same field reads correctly whether it sits on `background` or on a\n * `panel`. That is the core trick of the layered surface stack and it is why\n * this component must not hard-code a background.\n */\nexport const inputVariants = cva(\n [\n \"w-full border border-outline\",\n \"bg-control font-sans text-foreground\",\n \"placeholder:text-foreground-hint\",\n \"transition-[background-color,border-color,color] duration-(--cl-duration-fast) ease-cl-out\",\n \"hover:bg-control-highlight\",\n \"data-[disabled]:pointer-events-none\",\n \"data-[disabled]:text-foreground-disabled data-[disabled]:placeholder:text-foreground-disabled\",\n // Set by Base UI when the field sits inside a Field.Root.\n \"data-[invalid]:border-danger\",\n ],\n {\n variants: {\n /** Shared control geometry; typography follows density. */\n size: {\n sm: \"h-control-sm px-2 text-caption\",\n md: \"h-control-md px-3 text-callout\",\n lg: \"h-control-lg px-4 text-body\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport type InputProps = Omit<ComponentProps<typeof BaseInput>, \"className\" | \"size\"> &\n VariantProps<typeof inputVariants> & {\n className?: string;\n /** Applied to the wrapper, which is what a caller's layout sizes. */\n wrapperClassName?: string;\n };\n\n/**\n * A ClaraLight text field.\n *\n * `sm` uses caption, `md` uses callout, and `lg` uses body typography.\n * Dense inspector rows stay legible without shrinking the hit target.\n *\n * Note the `size` in `InputProps` is the ClaraLight density step, not the\n * native `<input size>` character-width hint — the native attribute is omitted\n * above, because the two collide and an intersection would make both unusable.\n */\nexport function Input({ className, wrapperClassName, size, ...props }: InputProps) {\n return (\n <Squircle\n asChild\n radius=\"control\"\n ring=\"field\"\n wrapperClassName={cn(\"block w-full\", wrapperClassName)}\n >\n <BaseInput className={cn(inputVariants({ size }), className)} {...props} />\n </Squircle>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAa,gBAAgB,IAC3B;CACE;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;AACF,GACA;CACE,UAAU;;AAER,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;;;;;;;;;;;AAmBA,SAAgBA,QAAM,EAAE,WAAW,kBAAkB,MAAM,GAAG,SAAqB;CACjF,OACE,oBAAC,UAAD;EACE,SAAA;EACA,QAAO;EACP,MAAK;EACL,kBAAkB,GAAG,gBAAgB,gBAAgB;EAErD,UAAA,oBAACC,OAAD;GAAW,WAAW,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,SAAS;GAAG,GAAI;EAAQ,CAAA;CAClE,CAAA;AAEd"}
|