@alpic-ai/ui 0.0.0-staging.d6c772d → 0.0.0-staging.d8beda7
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/components/avatar.d.mts +1 -1
- package/dist/components/button.d.mts +3 -1
- package/dist/components/button.mjs +17 -3
- package/dist/components/page-loader.d.mts +11 -0
- package/dist/components/page-loader.mjs +122 -0
- package/dist/components/shimmer-text.d.mts +12 -0
- package/dist/components/shimmer-text.mjs +22 -0
- package/dist/components/sidebar.mjs +4 -4
- package/dist/components/table.d.mts +10 -1
- package/dist/components/table.mjs +2 -2
- package/dist/components/tabs.d.mts +1 -1
- package/package.json +9 -9
- package/src/components/button.tsx +10 -0
- package/src/components/combobox.tsx +18 -6
- package/src/components/page-loader.tsx +59 -0
- package/src/components/shimmer-text.tsx +23 -0
- package/src/components/sidebar.tsx +4 -4
- package/src/components/table.tsx +16 -2
- package/src/hooks/use-copy-to-clipboard.ts +6 -2
- package/src/stories/button.stories.tsx +23 -1
- package/src/stories/table.stories.tsx +2 -2
- package/src/styles/tokens.css +193 -4
|
@@ -6,7 +6,7 @@ import * as _$class_variance_authority_types0 from "class-variance-authority/typ
|
|
|
6
6
|
|
|
7
7
|
//#region src/components/avatar.d.ts
|
|
8
8
|
declare const avatarVariants: (props?: ({
|
|
9
|
-
size?: "
|
|
9
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl" | null | undefined;
|
|
10
10
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
11
11
|
type AvatarSize = NonNullable<VariantProps<typeof avatarVariants>["size"]>;
|
|
12
12
|
type AvatarStatus = "online";
|
|
@@ -5,13 +5,14 @@ import * as _$class_variance_authority_types0 from "class-variance-authority/typ
|
|
|
5
5
|
|
|
6
6
|
//#region src/components/button.d.ts
|
|
7
7
|
declare const buttonVariants: (props?: ({
|
|
8
|
-
variant?: "destructive" | "secondary" | "primary" | "
|
|
8
|
+
variant?: "destructive" | "secondary" | "primary" | "tertiary" | "link" | "link-muted" | "cta" | null | undefined;
|
|
9
9
|
size?: "default" | "icon" | "icon-rounded" | "pill" | null | undefined;
|
|
10
10
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
11
11
|
interface ButtonProps extends React.ComponentProps<"button">, VariantProps<typeof buttonVariants> {
|
|
12
12
|
asChild?: boolean;
|
|
13
13
|
loading?: boolean;
|
|
14
14
|
icon?: React.ReactNode;
|
|
15
|
+
iconTrailing?: React.ReactNode;
|
|
15
16
|
}
|
|
16
17
|
declare function Button({
|
|
17
18
|
className,
|
|
@@ -21,6 +22,7 @@ declare function Button({
|
|
|
21
22
|
asChild,
|
|
22
23
|
loading,
|
|
23
24
|
icon,
|
|
25
|
+
iconTrailing,
|
|
24
26
|
disabled,
|
|
25
27
|
children,
|
|
26
28
|
...props
|
|
@@ -35,7 +35,14 @@ const buttonVariants = cva([
|
|
|
35
35
|
"[@media(hover:hover)]:hover:underline",
|
|
36
36
|
"focus-visible:bg-background"
|
|
37
37
|
].join(" "),
|
|
38
|
-
destructive: ["bg-destructive text-destructive-foreground", "[@media(hover:hover)]:hover:bg-destructive-hover"].join(" ")
|
|
38
|
+
destructive: ["bg-destructive text-destructive-foreground", "[@media(hover:hover)]:hover:bg-destructive-hover"].join(" "),
|
|
39
|
+
cta: [
|
|
40
|
+
"button-cta",
|
|
41
|
+
"h-9 px-4 gap-2 rounded-md",
|
|
42
|
+
"dark:bg-inverted text-foreground",
|
|
43
|
+
"transition-[transform,filter] duration-300 ease-out",
|
|
44
|
+
"active:scale-[0.99]"
|
|
45
|
+
].join(" ")
|
|
39
46
|
},
|
|
40
47
|
size: {
|
|
41
48
|
default: "type-text-sm",
|
|
@@ -49,7 +56,7 @@ const buttonVariants = cva([
|
|
|
49
56
|
size: "default"
|
|
50
57
|
}
|
|
51
58
|
});
|
|
52
|
-
function Button({ className, variant, size, type = "button", asChild = false, loading = false, icon, disabled, children, ...props }) {
|
|
59
|
+
function Button({ className, variant, size, type = "button", asChild = false, loading = false, icon, iconTrailing, disabled, children, ...props }) {
|
|
53
60
|
return /* @__PURE__ */ jsxs(asChild ? Slot : "button", {
|
|
54
61
|
"data-slot": "button",
|
|
55
62
|
className: cn(buttonVariants({
|
|
@@ -60,7 +67,14 @@ function Button({ className, variant, size, type = "button", asChild = false, lo
|
|
|
60
67
|
disabled: disabled || loading,
|
|
61
68
|
"aria-busy": loading || void 0,
|
|
62
69
|
...props,
|
|
63
|
-
children: [
|
|
70
|
+
children: [
|
|
71
|
+
loading ? /* @__PURE__ */ jsx(Loader2, { className: "motion-safe:animate-spin" }) : icon,
|
|
72
|
+
asChild ? /* @__PURE__ */ jsx(Slottable, { children }) : children,
|
|
73
|
+
!loading && iconTrailing ? /* @__PURE__ */ jsx("span", {
|
|
74
|
+
"data-cta-icon-trailing": true,
|
|
75
|
+
children: iconTrailing
|
|
76
|
+
}) : null
|
|
77
|
+
]
|
|
64
78
|
});
|
|
65
79
|
}
|
|
66
80
|
//#endregion
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/components/page-loader.d.ts
|
|
4
|
+
interface PageLoaderProps {
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
declare function PageLoader({
|
|
8
|
+
className
|
|
9
|
+
}: PageLoaderProps): _$react_jsx_runtime0.JSX.Element;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { PageLoader, type PageLoaderProps };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.mjs";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/page-loader.tsx
|
|
5
|
+
const CABLE_CAR_SVG = /* @__PURE__ */ jsxs("svg", {
|
|
6
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
7
|
+
viewBox: "0 0 120 130",
|
|
8
|
+
width: "240",
|
|
9
|
+
height: "260",
|
|
10
|
+
"aria-hidden": "true",
|
|
11
|
+
className: "block h-auto w-full",
|
|
12
|
+
children: [
|
|
13
|
+
/* @__PURE__ */ jsx("line", {
|
|
14
|
+
x1: "60",
|
|
15
|
+
y1: "3",
|
|
16
|
+
x2: "60",
|
|
17
|
+
y2: "58",
|
|
18
|
+
stroke: "#333",
|
|
19
|
+
strokeWidth: "4"
|
|
20
|
+
}),
|
|
21
|
+
/* @__PURE__ */ jsx("circle", {
|
|
22
|
+
cx: "60",
|
|
23
|
+
cy: "11",
|
|
24
|
+
r: "10",
|
|
25
|
+
fill: "#555"
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ jsx("rect", {
|
|
28
|
+
x: "5",
|
|
29
|
+
y: "58",
|
|
30
|
+
width: "110",
|
|
31
|
+
height: "64",
|
|
32
|
+
rx: "4",
|
|
33
|
+
fill: "#e90060"
|
|
34
|
+
}),
|
|
35
|
+
/* @__PURE__ */ jsx("rect", {
|
|
36
|
+
x: "5",
|
|
37
|
+
y: "58",
|
|
38
|
+
width: "110",
|
|
39
|
+
height: "20",
|
|
40
|
+
rx: "4",
|
|
41
|
+
fill: "#F5F0E8"
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsx("rect", {
|
|
44
|
+
x: "5",
|
|
45
|
+
y: "68",
|
|
46
|
+
width: "110",
|
|
47
|
+
height: "10",
|
|
48
|
+
fill: "#F5F0E8"
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ jsx("rect", {
|
|
51
|
+
x: "14",
|
|
52
|
+
y: "66",
|
|
53
|
+
width: "26",
|
|
54
|
+
height: "30",
|
|
55
|
+
rx: "2",
|
|
56
|
+
fill: "#5B8EC9",
|
|
57
|
+
stroke: "#C4B9A8",
|
|
58
|
+
strokeWidth: "1.5"
|
|
59
|
+
}),
|
|
60
|
+
/* @__PURE__ */ jsx("rect", {
|
|
61
|
+
x: "47",
|
|
62
|
+
y: "66",
|
|
63
|
+
width: "26",
|
|
64
|
+
height: "30",
|
|
65
|
+
rx: "2",
|
|
66
|
+
fill: "#5B8EC9",
|
|
67
|
+
stroke: "#C4B9A8",
|
|
68
|
+
strokeWidth: "1.5"
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ jsx("rect", {
|
|
71
|
+
x: "80",
|
|
72
|
+
y: "66",
|
|
73
|
+
width: "26",
|
|
74
|
+
height: "30",
|
|
75
|
+
rx: "2",
|
|
76
|
+
fill: "#5B8EC9",
|
|
77
|
+
stroke: "#C4B9A8",
|
|
78
|
+
strokeWidth: "1.5"
|
|
79
|
+
}),
|
|
80
|
+
/* @__PURE__ */ jsx("rect", {
|
|
81
|
+
x: "5",
|
|
82
|
+
y: "115",
|
|
83
|
+
width: "110",
|
|
84
|
+
height: "7",
|
|
85
|
+
rx: "3",
|
|
86
|
+
fill: "#9f0042"
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
});
|
|
90
|
+
function PageLoader({ className }) {
|
|
91
|
+
return /* @__PURE__ */ jsx("div", {
|
|
92
|
+
className: cn("flex min-h-screen items-center justify-center bg-background", className),
|
|
93
|
+
role: "status",
|
|
94
|
+
"aria-label": "Loading Alpic…",
|
|
95
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
96
|
+
className: "relative h-[120px] w-[200px]",
|
|
97
|
+
children: [
|
|
98
|
+
/* @__PURE__ */ jsx("div", {
|
|
99
|
+
className: "absolute top-[30px] left-0 h-[3px] w-full rounded-sm bg-[#6c6c77]",
|
|
100
|
+
style: {
|
|
101
|
+
transform: "rotate(-15deg)",
|
|
102
|
+
transformOrigin: "left center"
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ jsx("div", {
|
|
106
|
+
className: "absolute top-[33px] -left-[45px] w-[45px] motion-safe:animate-[alpic-ride_4s_linear_infinite]",
|
|
107
|
+
children: CABLE_CAR_SVG
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ jsx("div", {
|
|
110
|
+
className: "pointer-events-none absolute -top-[40px] -left-[45px] z-10 h-[200px] w-[95px]",
|
|
111
|
+
style: { background: "linear-gradient(to right, var(--color-background) 47%, transparent)" }
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ jsx("div", {
|
|
114
|
+
className: "pointer-events-none absolute -top-[40px] left-[142px] z-10 h-[200px] w-[95px]",
|
|
115
|
+
style: { background: "linear-gradient(to right, transparent, var(--color-background) 53%)" }
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
})
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { PageLoader };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/components/shimmer-text.d.ts
|
|
4
|
+
declare function ShimmerText({
|
|
5
|
+
children,
|
|
6
|
+
className
|
|
7
|
+
}: {
|
|
8
|
+
children: string | number;
|
|
9
|
+
className?: string;
|
|
10
|
+
}): _$react_jsx_runtime0.JSX.Element;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { ShimmerText };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.mjs";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/shimmer-text.tsx
|
|
5
|
+
const shimmerStyle = {
|
|
6
|
+
background: `linear-gradient(90deg, #0000 calc(50% - 60px), var(--color-foreground), #0000 calc(50% + 60px)), linear-gradient(color-mix(in oklab, var(--color-muted-foreground) 70%, transparent), color-mix(in oklab, var(--color-muted-foreground) 70%, transparent))`,
|
|
7
|
+
backgroundSize: "250% 100%, auto",
|
|
8
|
+
backgroundRepeat: "no-repeat, padding-box",
|
|
9
|
+
WebkitBackgroundClip: "text",
|
|
10
|
+
WebkitTextFillColor: "transparent",
|
|
11
|
+
backgroundClip: "text",
|
|
12
|
+
animation: "shimmer-text 2.5s linear infinite"
|
|
13
|
+
};
|
|
14
|
+
function ShimmerText({ children, className }) {
|
|
15
|
+
return /* @__PURE__ */ jsx("span", {
|
|
16
|
+
className: cn(className),
|
|
17
|
+
style: shimmerStyle,
|
|
18
|
+
children
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { ShimmerText };
|
|
@@ -105,7 +105,7 @@ function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas"
|
|
|
105
105
|
}
|
|
106
106
|
if (collapsible === "none") return /* @__PURE__ */ jsx("div", {
|
|
107
107
|
"data-slot": "sidebar",
|
|
108
|
-
className: cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className),
|
|
108
|
+
className: cn("bg-sidebar-surface text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className),
|
|
109
109
|
...props,
|
|
110
110
|
children
|
|
111
111
|
});
|
|
@@ -117,7 +117,7 @@ function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas"
|
|
|
117
117
|
"data-sidebar": "sidebar",
|
|
118
118
|
"data-slot": "sidebar",
|
|
119
119
|
"data-mobile": "true",
|
|
120
|
-
className: "bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
|
|
120
|
+
className: "bg-sidebar-surface text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
|
|
121
121
|
style: { "--sidebar-width": SIDEBAR_WIDTH_MOBILE },
|
|
122
122
|
side,
|
|
123
123
|
children: [/* @__PURE__ */ jsxs(SheetHeader, {
|
|
@@ -141,12 +141,12 @@ function Sidebar({ side = "left", variant = "sidebar", collapsible = "offcanvas"
|
|
|
141
141
|
className: cn("relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-out", "group-data-[collapsible=offcanvas]:w-0", "group-data-[side=right]:rotate-180", variant === "floating" || variant === "inset" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)")
|
|
142
142
|
}), /* @__PURE__ */ jsx("div", {
|
|
143
143
|
"data-slot": "sidebar-container",
|
|
144
|
-
className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-out md:flex", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", className),
|
|
144
|
+
className: cn("fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-out md:flex", side === "left" ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", variant === "floating" || variant === "inset" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" : "border-sidebar-border group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", className),
|
|
145
145
|
...props,
|
|
146
146
|
children: /* @__PURE__ */ jsx("div", {
|
|
147
147
|
"data-sidebar": "sidebar",
|
|
148
148
|
"data-slot": "sidebar-inner",
|
|
149
|
-
className: "bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full cursor-pointer flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm",
|
|
149
|
+
className: "bg-sidebar-surface group-data-[variant=floating]:border-sidebar-border flex h-full w-full cursor-pointer flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm",
|
|
150
150
|
onClickCapture: handleSurfaceClickCapture,
|
|
151
151
|
children
|
|
152
152
|
})
|
|
@@ -26,10 +26,19 @@ declare function TableHead({
|
|
|
26
26
|
className,
|
|
27
27
|
...props
|
|
28
28
|
}: React.ComponentProps<"th">): _$react_jsx_runtime0.JSX.Element;
|
|
29
|
+
interface TableCellProps extends React.ComponentProps<"td"> {
|
|
30
|
+
/**
|
|
31
|
+
* When true, the cell renders edge-to-edge so the child can act as the
|
|
32
|
+
* interactive surface (e.g. a button or popover trigger filling the cell).
|
|
33
|
+
* Defaults to false (standard padded cell).
|
|
34
|
+
*/
|
|
35
|
+
interactive?: boolean;
|
|
36
|
+
}
|
|
29
37
|
declare function TableCell({
|
|
30
38
|
className,
|
|
39
|
+
interactive,
|
|
31
40
|
...props
|
|
32
|
-
}:
|
|
41
|
+
}: TableCellProps): _$react_jsx_runtime0.JSX.Element;
|
|
33
42
|
declare function TableCaption({
|
|
34
43
|
className,
|
|
35
44
|
...props
|
|
@@ -47,10 +47,10 @@ function TableHead({ className, ...props }) {
|
|
|
47
47
|
...props
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
function TableCell({ className, ...props }) {
|
|
50
|
+
function TableCell({ className, interactive = false, ...props }) {
|
|
51
51
|
return /* @__PURE__ */ jsx("td", {
|
|
52
52
|
"data-slot": "table-cell",
|
|
53
|
-
className: cn("px-6 py-2
|
|
53
|
+
className: cn("align-middle", interactive ? "h-px p-0" : "px-6 py-2", "[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pr-3", className),
|
|
54
54
|
...props
|
|
55
55
|
});
|
|
56
56
|
}
|
|
@@ -6,7 +6,7 @@ import * as _$class_variance_authority_types0 from "class-variance-authority/typ
|
|
|
6
6
|
|
|
7
7
|
//#region src/components/tabs.d.ts
|
|
8
8
|
declare const tabsTriggerVariants: (props?: ({
|
|
9
|
-
variant?: "default" | "
|
|
9
|
+
variant?: "default" | "pill" | "line" | null | undefined;
|
|
10
10
|
} & _$class_variance_authority_types0.ClassProp) | undefined) => string;
|
|
11
11
|
declare function Tabs({
|
|
12
12
|
className,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpic-ai/ui",
|
|
3
|
-
"version": "0.0.0-staging.
|
|
3
|
+
"version": "0.0.0-staging.d8beda7",
|
|
4
4
|
"description": "Alpic design system — shared UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"lucide-react": "^1.
|
|
26
|
+
"lucide-react": "^1.14.0",
|
|
27
27
|
"react": "^19.2.5",
|
|
28
28
|
"react-dom": "^19.2.5",
|
|
29
|
-
"react-hook-form": "^7.
|
|
29
|
+
"react-hook-form": "^7.74.0",
|
|
30
30
|
"sonner": "^2.0.7",
|
|
31
|
-
"tailwindcss": "^4.2.
|
|
31
|
+
"tailwindcss": "^4.2.4",
|
|
32
32
|
"tw-animate-css": "^1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@ladle/react": "^5.1.1",
|
|
59
|
-
"@tailwindcss/postcss": "^4.2.
|
|
59
|
+
"@tailwindcss/postcss": "^4.2.4",
|
|
60
60
|
"@types/react": "19.2.14",
|
|
61
61
|
"@types/react-dom": "19.2.3",
|
|
62
|
-
"lucide-react": "^1.
|
|
63
|
-
"react-hook-form": "^7.
|
|
62
|
+
"lucide-react": "^1.14.0",
|
|
63
|
+
"react-hook-form": "^7.74.0",
|
|
64
64
|
"shx": "^0.4.0",
|
|
65
65
|
"sonner": "^2.0.7",
|
|
66
|
-
"tailwindcss": "^4.2.
|
|
67
|
-
"tsdown": "^0.21.
|
|
66
|
+
"tailwindcss": "^4.2.4",
|
|
67
|
+
"tsdown": "^0.21.10",
|
|
68
68
|
"tw-animate-css": "^1.4.0",
|
|
69
69
|
"typescript": "^6.0.3"
|
|
70
70
|
},
|
|
@@ -46,6 +46,13 @@ const buttonVariants = cva(
|
|
|
46
46
|
"bg-destructive text-destructive-foreground",
|
|
47
47
|
"[@media(hover:hover)]:hover:bg-destructive-hover",
|
|
48
48
|
].join(" "),
|
|
49
|
+
cta: [
|
|
50
|
+
"button-cta",
|
|
51
|
+
"h-9 px-4 gap-2 rounded-md",
|
|
52
|
+
"dark:bg-inverted text-foreground",
|
|
53
|
+
"transition-[transform,filter] duration-300 ease-out",
|
|
54
|
+
"active:scale-[0.99]",
|
|
55
|
+
].join(" "),
|
|
49
56
|
},
|
|
50
57
|
size: {
|
|
51
58
|
default: "type-text-sm",
|
|
@@ -65,6 +72,7 @@ interface ButtonProps extends React.ComponentProps<"button">, VariantProps<typeo
|
|
|
65
72
|
asChild?: boolean;
|
|
66
73
|
loading?: boolean;
|
|
67
74
|
icon?: React.ReactNode;
|
|
75
|
+
iconTrailing?: React.ReactNode;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
function Button({
|
|
@@ -75,6 +83,7 @@ function Button({
|
|
|
75
83
|
asChild = false,
|
|
76
84
|
loading = false,
|
|
77
85
|
icon,
|
|
86
|
+
iconTrailing,
|
|
78
87
|
disabled,
|
|
79
88
|
children,
|
|
80
89
|
...props
|
|
@@ -92,6 +101,7 @@ function Button({
|
|
|
92
101
|
>
|
|
93
102
|
{loading ? <Loader2 className="motion-safe:animate-spin" /> : icon}
|
|
94
103
|
{asChild ? <Slottable>{children}</Slottable> : children}
|
|
104
|
+
{!loading && iconTrailing ? <span data-cta-icon-trailing>{iconTrailing}</span> : null}
|
|
95
105
|
</Comp>
|
|
96
106
|
);
|
|
97
107
|
}
|
|
@@ -84,7 +84,9 @@ function Combobox(props: ComboboxProps) {
|
|
|
84
84
|
|
|
85
85
|
const onOpenChange = useCallback(
|
|
86
86
|
(newOpen: boolean) => {
|
|
87
|
-
if (!isOpenControlled)
|
|
87
|
+
if (!isOpenControlled) {
|
|
88
|
+
setUncontrolledOpen(newOpen);
|
|
89
|
+
}
|
|
88
90
|
controlledOnOpenChange?.(newOpen);
|
|
89
91
|
},
|
|
90
92
|
[isOpenControlled, controlledOnOpenChange],
|
|
@@ -105,7 +107,9 @@ function Combobox(props: ComboboxProps) {
|
|
|
105
107
|
|
|
106
108
|
const isSelected = useCallback(
|
|
107
109
|
(itemValue: string) => {
|
|
108
|
-
if (multiple)
|
|
110
|
+
if (multiple) {
|
|
111
|
+
return multiValues.includes(itemValue);
|
|
112
|
+
}
|
|
109
113
|
return singleValue === itemValue;
|
|
110
114
|
},
|
|
111
115
|
[multiple, singleValue, multiValues],
|
|
@@ -123,11 +127,15 @@ function Combobox(props: ComboboxProps) {
|
|
|
123
127
|
const next = multiValues.includes(itemValue)
|
|
124
128
|
? multiValues.filter((val) => val !== itemValue)
|
|
125
129
|
: [...multiValues, itemValue];
|
|
126
|
-
if (!isControlledMulti)
|
|
130
|
+
if (!isControlledMulti) {
|
|
131
|
+
setUncontrolledMultiValue(next);
|
|
132
|
+
}
|
|
127
133
|
onValueChangeMulti?.(next);
|
|
128
134
|
// Stay open in multi mode
|
|
129
135
|
} else {
|
|
130
|
-
if (!isControlledSingle)
|
|
136
|
+
if (!isControlledSingle) {
|
|
137
|
+
setUncontrolledSingleValue(itemValue);
|
|
138
|
+
}
|
|
131
139
|
onValueChangeSingle?.(itemValue);
|
|
132
140
|
onOpenChange(false);
|
|
133
141
|
}
|
|
@@ -145,9 +153,13 @@ function Combobox(props: ComboboxProps) {
|
|
|
145
153
|
|
|
146
154
|
const onDeselect = useCallback(
|
|
147
155
|
(itemValue: string) => {
|
|
148
|
-
if (!multiple)
|
|
156
|
+
if (!multiple) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
149
159
|
const next = multiValues.filter((val) => val !== itemValue);
|
|
150
|
-
if (!isControlledMulti)
|
|
160
|
+
if (!isControlledMulti) {
|
|
161
|
+
setUncontrolledMultiValue(next);
|
|
162
|
+
}
|
|
151
163
|
onValueChangeMulti?.(next);
|
|
152
164
|
},
|
|
153
165
|
[multiple, isControlledMulti, onValueChangeMulti, multiValues],
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
|
|
5
|
+
const CABLE_CAR_SVG = (
|
|
6
|
+
<svg
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
viewBox="0 0 120 130"
|
|
9
|
+
width="240"
|
|
10
|
+
height="260"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
className="block h-auto w-full"
|
|
13
|
+
>
|
|
14
|
+
<line x1="60" y1="3" x2="60" y2="58" stroke="#333" strokeWidth="4" />
|
|
15
|
+
<circle cx="60" cy="11" r="10" fill="#555" />
|
|
16
|
+
<rect x="5" y="58" width="110" height="64" rx="4" fill="#e90060" />
|
|
17
|
+
<rect x="5" y="58" width="110" height="20" rx="4" fill="#F5F0E8" />
|
|
18
|
+
<rect x="5" y="68" width="110" height="10" fill="#F5F0E8" />
|
|
19
|
+
<rect x="14" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
|
|
20
|
+
<rect x="47" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
|
|
21
|
+
<rect x="80" y="66" width="26" height="30" rx="2" fill="#5B8EC9" stroke="#C4B9A8" strokeWidth="1.5" />
|
|
22
|
+
<rect x="5" y="115" width="110" height="7" rx="3" fill="#9f0042" />
|
|
23
|
+
</svg>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
interface PageLoaderProps {
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function PageLoader({ className }: PageLoaderProps) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={cn("flex min-h-screen items-center justify-center bg-background", className)}
|
|
34
|
+
role="status"
|
|
35
|
+
aria-label="Loading Alpic…"
|
|
36
|
+
>
|
|
37
|
+
<div className="relative h-[120px] w-[200px]">
|
|
38
|
+
<div
|
|
39
|
+
className="absolute top-[30px] left-0 h-[3px] w-full rounded-sm bg-[#6c6c77]"
|
|
40
|
+
style={{ transform: "rotate(-15deg)", transformOrigin: "left center" }}
|
|
41
|
+
/>
|
|
42
|
+
<div className="absolute top-[33px] -left-[45px] w-[45px] motion-safe:animate-[alpic-ride_4s_linear_infinite]">
|
|
43
|
+
{CABLE_CAR_SVG}
|
|
44
|
+
</div>
|
|
45
|
+
<div
|
|
46
|
+
className="pointer-events-none absolute -top-[40px] -left-[45px] z-10 h-[200px] w-[95px]"
|
|
47
|
+
style={{ background: "linear-gradient(to right, var(--color-background) 47%, transparent)" }}
|
|
48
|
+
/>
|
|
49
|
+
<div
|
|
50
|
+
className="pointer-events-none absolute -top-[40px] left-[142px] z-10 h-[200px] w-[95px]"
|
|
51
|
+
style={{ background: "linear-gradient(to right, transparent, var(--color-background) 53%)" }}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type { PageLoaderProps };
|
|
59
|
+
export { PageLoader };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type React from "react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/cn";
|
|
6
|
+
|
|
7
|
+
const shimmerStyle: React.CSSProperties = {
|
|
8
|
+
background: `linear-gradient(90deg, #0000 calc(50% - 60px), var(--color-foreground), #0000 calc(50% + 60px)), linear-gradient(color-mix(in oklab, var(--color-muted-foreground) 70%, transparent), color-mix(in oklab, var(--color-muted-foreground) 70%, transparent))`,
|
|
9
|
+
backgroundSize: "250% 100%, auto",
|
|
10
|
+
backgroundRepeat: "no-repeat, padding-box",
|
|
11
|
+
WebkitBackgroundClip: "text",
|
|
12
|
+
WebkitTextFillColor: "transparent",
|
|
13
|
+
backgroundClip: "text",
|
|
14
|
+
animation: "shimmer-text 2.5s linear infinite",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function ShimmerText({ children, className }: { children: string | number; className?: string }) {
|
|
18
|
+
return (
|
|
19
|
+
<span className={cn(className)} style={shimmerStyle}>
|
|
20
|
+
{children}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -179,7 +179,7 @@ function Sidebar({
|
|
|
179
179
|
return (
|
|
180
180
|
<div
|
|
181
181
|
data-slot="sidebar"
|
|
182
|
-
className={cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className)}
|
|
182
|
+
className={cn("bg-sidebar-surface text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className)}
|
|
183
183
|
{...props}
|
|
184
184
|
>
|
|
185
185
|
{children}
|
|
@@ -194,7 +194,7 @@ function Sidebar({
|
|
|
194
194
|
data-sidebar="sidebar"
|
|
195
195
|
data-slot="sidebar"
|
|
196
196
|
data-mobile="true"
|
|
197
|
-
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
|
197
|
+
className="bg-sidebar-surface text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
|
198
198
|
style={
|
|
199
199
|
{
|
|
200
200
|
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
|
@@ -243,7 +243,7 @@ function Sidebar({
|
|
|
243
243
|
// Adjust the padding for floating and inset variants.
|
|
244
244
|
variant === "floating" || variant === "inset"
|
|
245
245
|
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
246
|
-
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
|
246
|
+
: "border-sidebar-border group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
|
247
247
|
className,
|
|
248
248
|
)}
|
|
249
249
|
{...props}
|
|
@@ -251,7 +251,7 @@ function Sidebar({
|
|
|
251
251
|
<div
|
|
252
252
|
data-sidebar="sidebar"
|
|
253
253
|
data-slot="sidebar-inner"
|
|
254
|
-
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full cursor-pointer flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
|
|
254
|
+
className="bg-sidebar-surface group-data-[variant=floating]:border-sidebar-border flex h-full w-full cursor-pointer flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
|
|
255
255
|
onClickCapture={handleSurfaceClickCapture}
|
|
256
256
|
>
|
|
257
257
|
{children}
|
package/src/components/table.tsx
CHANGED
|
@@ -66,11 +66,25 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
interface TableCellProps extends React.ComponentProps<"td"> {
|
|
70
|
+
/**
|
|
71
|
+
* When true, the cell renders edge-to-edge so the child can act as the
|
|
72
|
+
* interactive surface (e.g. a button or popover trigger filling the cell).
|
|
73
|
+
* Defaults to false (standard padded cell).
|
|
74
|
+
*/
|
|
75
|
+
interactive?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function TableCell({ className, interactive = false, ...props }: TableCellProps) {
|
|
70
79
|
return (
|
|
71
80
|
<td
|
|
72
81
|
data-slot="table-cell"
|
|
73
|
-
className={cn(
|
|
82
|
+
className={cn(
|
|
83
|
+
"align-middle",
|
|
84
|
+
interactive ? "h-px p-0" : "px-6 py-2",
|
|
85
|
+
"[&:has([role=checkbox])]:w-px [&:has([role=checkbox])]:pr-3",
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
74
88
|
{...props}
|
|
75
89
|
/>
|
|
76
90
|
);
|
|
@@ -8,7 +8,9 @@ export function useCopyToClipboard({ resetDelay = 2000 }: { resetDelay?: number
|
|
|
8
8
|
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
return () => {
|
|
11
|
-
if (timeoutRef.current)
|
|
11
|
+
if (timeoutRef.current) {
|
|
12
|
+
clearTimeout(timeoutRef.current);
|
|
13
|
+
}
|
|
12
14
|
};
|
|
13
15
|
}, []);
|
|
14
16
|
|
|
@@ -16,7 +18,9 @@ export function useCopyToClipboard({ resetDelay = 2000 }: { resetDelay?: number
|
|
|
16
18
|
(text: string) => {
|
|
17
19
|
navigator.clipboard.writeText(text).then(() => {
|
|
18
20
|
setIsCopied(true);
|
|
19
|
-
if (timeoutRef.current)
|
|
21
|
+
if (timeoutRef.current) {
|
|
22
|
+
clearTimeout(timeoutRef.current);
|
|
23
|
+
}
|
|
20
24
|
timeoutRef.current = setTimeout(() => setIsCopied(false), resetDelay);
|
|
21
25
|
});
|
|
22
26
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Plus } from "lucide-react";
|
|
1
|
+
import { ArrowRight, Plus, Sparkles } from "lucide-react";
|
|
2
2
|
|
|
3
3
|
import { Button } from "../components/button";
|
|
4
4
|
|
|
@@ -323,6 +323,28 @@ export const AllVariants = () => {
|
|
|
323
323
|
</Button>
|
|
324
324
|
</div>
|
|
325
325
|
|
|
326
|
+
{/* ── CTA (animated gradient ring) ────────────────────────────────── */}
|
|
327
|
+
<span className={SECTION_HEADER}>CTA — animated</span>
|
|
328
|
+
<p className="type-text-xs text-muted-foreground -mt-2 max-w-md">
|
|
329
|
+
Hover to rotate the conic gradient around the border and ignite the soft halo.
|
|
330
|
+
</p>
|
|
331
|
+
|
|
332
|
+
<div className="flex items-center gap-6">
|
|
333
|
+
<Button variant="cta" iconTrailing={<ArrowRight />}>
|
|
334
|
+
Get started
|
|
335
|
+
</Button>
|
|
336
|
+
<Button variant="cta" icon={<Sparkles />} iconTrailing={<ArrowRight />}>
|
|
337
|
+
Launch server
|
|
338
|
+
</Button>
|
|
339
|
+
<Button variant="cta">Deploy now</Button>
|
|
340
|
+
<Button variant="cta" iconTrailing={<ArrowRight />} disabled>
|
|
341
|
+
Disabled
|
|
342
|
+
</Button>
|
|
343
|
+
<Button variant="cta" iconTrailing={<ArrowRight />} loading>
|
|
344
|
+
Deploying
|
|
345
|
+
</Button>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
326
348
|
{/* ── asChild ─────────────────────────────────────────────────────── */}
|
|
327
349
|
<span className={SECTION_HEADER}>asChild</span>
|
|
328
350
|
|
|
@@ -190,8 +190,8 @@ export const AllVariants: Story = () => (
|
|
|
190
190
|
</TableRow>
|
|
191
191
|
</TableHeader>
|
|
192
192
|
<TableBody>
|
|
193
|
-
{USERS.slice(0, 3).map((user,
|
|
194
|
-
<TableRow key={user.id} data-state={
|
|
193
|
+
{USERS.slice(0, 3).map((user, index) => (
|
|
194
|
+
<TableRow key={user.id} data-state={index === 1 ? "selected" : undefined}>
|
|
195
195
|
<TableCell className="font-medium text-foreground">{user.name}</TableCell>
|
|
196
196
|
<TableCell className="text-subtle-foreground">{user.role}</TableCell>
|
|
197
197
|
<TableCell className="text-subtle-foreground">{user.email}</TableCell>
|
package/src/styles/tokens.css
CHANGED
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
|
|
40
40
|
--color-ring: #f22b79; /* Figma: focus-ring */
|
|
41
41
|
|
|
42
|
+
/* cta — decorative gradient accent, used with --color-primary in the CTA button ring */
|
|
43
|
+
--color-cta-accent: #6eece7; /* Figma: CTA border accent (cyan) */
|
|
44
|
+
|
|
42
45
|
/* sidebar */
|
|
43
46
|
--color-sidebar: #f8fafa; /* Figma: bg-secondary-subtle */
|
|
44
47
|
--color-sidebar-foreground: #3a4848; /* Figma: fg-secondary */
|
|
@@ -110,6 +113,24 @@
|
|
|
110
113
|
--animate-beacon-ring-pulse: beacon-ring-pulse 2.2s ease-in-out infinite;
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
@keyframes shimmer-text {
|
|
117
|
+
from {
|
|
118
|
+
background-position: 100% center;
|
|
119
|
+
}
|
|
120
|
+
to {
|
|
121
|
+
background-position: 0% center;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@keyframes alpic-ride {
|
|
126
|
+
0% {
|
|
127
|
+
transform: translate(0px, 0px);
|
|
128
|
+
}
|
|
129
|
+
100% {
|
|
130
|
+
transform: translate(237px, -64px);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
@keyframes accordion-down {
|
|
114
135
|
from {
|
|
115
136
|
height: 0;
|
|
@@ -143,14 +164,166 @@
|
|
|
143
164
|
@keyframes beacon-ring-pulse {
|
|
144
165
|
0%,
|
|
145
166
|
100% {
|
|
146
|
-
opacity: 0.
|
|
167
|
+
opacity: 0.55;
|
|
147
168
|
transform: scale(1);
|
|
148
|
-
filter: drop-shadow(0 0 0 rgba(59, 130, 246, 0));
|
|
149
169
|
}
|
|
150
170
|
50% {
|
|
151
|
-
opacity:
|
|
171
|
+
opacity: 0.9;
|
|
152
172
|
transform: scale(1.02);
|
|
153
|
-
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* ─── CTA button — animated conic gradient ring ───────────────────────────── */
|
|
177
|
+
|
|
178
|
+
@property --cta-angle {
|
|
179
|
+
syntax: "<angle>";
|
|
180
|
+
inherits: false;
|
|
181
|
+
initial-value: 135deg;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@keyframes cta-rotate {
|
|
185
|
+
to {
|
|
186
|
+
--cta-angle: 495deg;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.button-cta {
|
|
191
|
+
position: relative;
|
|
192
|
+
isolation: isolate;
|
|
193
|
+
/* light mode: whisper-of-gradient surface tint + soft rose drop-shadow */
|
|
194
|
+
background-image: linear-gradient(
|
|
195
|
+
135deg,
|
|
196
|
+
color-mix(in oklab, var(--color-primary) 5%, transparent) 0%,
|
|
197
|
+
color-mix(in oklab, var(--color-cta-accent) 5%, transparent) 100%
|
|
198
|
+
);
|
|
199
|
+
box-shadow:
|
|
200
|
+
0 6px 24px -10px color-mix(in oklab, var(--color-primary) 38%, transparent),
|
|
201
|
+
0 2px 6px -4px color-mix(in oklab, var(--color-cta-accent) 30%, transparent);
|
|
202
|
+
transition:
|
|
203
|
+
box-shadow 400ms ease,
|
|
204
|
+
transform 300ms ease,
|
|
205
|
+
filter 300ms ease;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@media (hover: hover) {
|
|
209
|
+
.button-cta:hover {
|
|
210
|
+
box-shadow:
|
|
211
|
+
0 10px 30px -8px color-mix(in oklab, var(--color-primary) 52%, transparent),
|
|
212
|
+
0 3px 10px -3px color-mix(in oklab, var(--color-cta-accent) 40%, transparent);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* dark mode: solid inverted surface, no tint or shadow — let the halo do the work */
|
|
217
|
+
.dark .button-cta {
|
|
218
|
+
background-image: none;
|
|
219
|
+
box-shadow: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.button-cta::before,
|
|
223
|
+
.button-cta::after {
|
|
224
|
+
content: "";
|
|
225
|
+
position: absolute;
|
|
226
|
+
inset: 0;
|
|
227
|
+
border-radius: inherit;
|
|
228
|
+
pointer-events: none;
|
|
229
|
+
/* Always "running" in browser terms, but paused at rest — freezes at current
|
|
230
|
+
angle on unhover instead of snapping back. */
|
|
231
|
+
animation: cta-rotate 3.2s linear infinite;
|
|
232
|
+
animation-play-state: paused;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Gradient ring (masked so only the border shows) */
|
|
236
|
+
.button-cta::before {
|
|
237
|
+
padding: 1.5px;
|
|
238
|
+
background: conic-gradient(
|
|
239
|
+
from var(--cta-angle),
|
|
240
|
+
var(--color-cta-accent) 0deg,
|
|
241
|
+
var(--color-primary) 150deg,
|
|
242
|
+
var(--color-cta-accent) 300deg,
|
|
243
|
+
var(--color-cta-accent) 360deg
|
|
244
|
+
);
|
|
245
|
+
-webkit-mask:
|
|
246
|
+
linear-gradient(#000 0 0) content-box,
|
|
247
|
+
linear-gradient(#000 0 0);
|
|
248
|
+
-webkit-mask-composite: xor;
|
|
249
|
+
mask-composite: exclude;
|
|
250
|
+
transition: filter 400ms ease;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Blurred glow halo behind the button — subtle in light, bolder in dark */
|
|
254
|
+
.button-cta::after {
|
|
255
|
+
z-index: -1;
|
|
256
|
+
background: conic-gradient(
|
|
257
|
+
from var(--cta-angle),
|
|
258
|
+
var(--color-cta-accent) 0deg,
|
|
259
|
+
var(--color-primary) 150deg,
|
|
260
|
+
var(--color-cta-accent) 300deg,
|
|
261
|
+
var(--color-cta-accent) 360deg
|
|
262
|
+
);
|
|
263
|
+
filter: blur(12px);
|
|
264
|
+
opacity: 0.05;
|
|
265
|
+
transition: opacity 400ms ease;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.dark .button-cta::after {
|
|
269
|
+
opacity: 0.14;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@media (hover: hover) {
|
|
273
|
+
.button-cta:hover::before,
|
|
274
|
+
.button-cta:hover::after {
|
|
275
|
+
animation-play-state: running;
|
|
276
|
+
}
|
|
277
|
+
.button-cta:hover::before {
|
|
278
|
+
filter: saturate(1.15) brightness(1.05);
|
|
279
|
+
}
|
|
280
|
+
.button-cta:hover::after {
|
|
281
|
+
opacity: 0.18;
|
|
282
|
+
}
|
|
283
|
+
.dark .button-cta:hover::after {
|
|
284
|
+
opacity: 0.32;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.button-cta:focus-visible::before,
|
|
289
|
+
.button-cta:focus-visible::after {
|
|
290
|
+
animation-play-state: running;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.button-cta:disabled::before,
|
|
294
|
+
.button-cta:disabled::after,
|
|
295
|
+
[aria-busy="true"].button-cta::before,
|
|
296
|
+
[aria-busy="true"].button-cta::after {
|
|
297
|
+
animation-play-state: paused;
|
|
298
|
+
}
|
|
299
|
+
.button-cta:disabled::after {
|
|
300
|
+
opacity: 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@media (prefers-reduced-motion: reduce) {
|
|
304
|
+
.button-cta::before,
|
|
305
|
+
.button-cta::after {
|
|
306
|
+
animation: none;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* Icon slide on hover (applied to [data-cta-icon-trailing]) */
|
|
311
|
+
.button-cta [data-cta-icon-trailing] {
|
|
312
|
+
transition: transform 300ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@media (hover: hover) {
|
|
316
|
+
.button-cta:hover [data-cta-icon-trailing] {
|
|
317
|
+
transform: translateX(2px);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@media (prefers-reduced-motion: reduce) {
|
|
322
|
+
.button-cta [data-cta-icon-trailing] {
|
|
323
|
+
transition: none;
|
|
324
|
+
}
|
|
325
|
+
.button-cta:hover [data-cta-icon-trailing] {
|
|
326
|
+
transform: none;
|
|
154
327
|
}
|
|
155
328
|
}
|
|
156
329
|
|
|
@@ -191,6 +364,9 @@
|
|
|
191
364
|
|
|
192
365
|
--color-ring: #f22b79; /* Figma: focus-ring */
|
|
193
366
|
|
|
367
|
+
/* cta — decorative gradient accent, used with --color-primary in the CTA button ring */
|
|
368
|
+
--color-cta-accent: #6eece7; /* Figma: CTA border accent (cyan) */
|
|
369
|
+
|
|
194
370
|
/* sidebar */
|
|
195
371
|
--color-sidebar: #0c1c1c; /* Figma: bg-secondary */
|
|
196
372
|
--color-sidebar-foreground: #90a4a4; /* Figma: fg-secondary */
|
|
@@ -249,6 +425,14 @@
|
|
|
249
425
|
|
|
250
426
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
251
427
|
|
|
428
|
+
:root {
|
|
429
|
+
--gradient-sidebar: linear-gradient(0deg, #c9e2e280 0%, #ffffff 70%); /* Figma: bg-nav-gradiant-light */
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.dark {
|
|
433
|
+
--gradient-sidebar: linear-gradient(0deg, #213535 0%, #121e1e 70%); /* Figma: bg-nav-gradiant-dark */
|
|
434
|
+
}
|
|
435
|
+
|
|
252
436
|
@layer base {
|
|
253
437
|
* {
|
|
254
438
|
@apply border-border shadow-shadow;
|
|
@@ -302,6 +486,11 @@
|
|
|
302
486
|
|
|
303
487
|
/* ─── Type preset utilities ───────────────────────────────────────────────── */
|
|
304
488
|
|
|
489
|
+
@utility bg-sidebar-surface {
|
|
490
|
+
background-color: var(--color-background);
|
|
491
|
+
background-image: var(--gradient-sidebar);
|
|
492
|
+
}
|
|
493
|
+
|
|
305
494
|
@utility type-display-2xl {
|
|
306
495
|
font-family: var(--font-display);
|
|
307
496
|
font-size: var(--font-size-display-2xl);
|