feat(web): bootstrap cadence ui foundation
This commit is contained in:
@@ -55,6 +55,14 @@ For `skills/`:
|
||||
- when updating an existing skill, keep `SKILL.md`, `agents/openai.yaml`, and bundled assets consistent with each other
|
||||
- if you add or materially change a project skill, update [docs/implementation-roadmap.md](/home/kurihada/project/ai-workflow-skill/docs/implementation-roadmap.md) in the same change
|
||||
|
||||
## Frontend UI Reuse
|
||||
|
||||
For frontend work in this repository:
|
||||
|
||||
- default to reusing Cadence UI source-owned components before building custom UI pieces
|
||||
- business pages and feature flows may compose existing components freely, but do not introduce new foundational UI primitives when the need can be met by existing Cadence UI components or by installing additional Cadence UI components
|
||||
- if a task appears to require a genuinely new foundational UI primitive, explicitly tell the user before creating it instead of adding it silently
|
||||
|
||||
## Sub-Agent Delegation
|
||||
|
||||
This repository allows sub-agent delegation for parallel implementation work.
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@tanstack/react-query": "^5.91.2",
|
||||
"@tanstack/react-router": "^1.167.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"motion": "^12.38.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4"
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.71.2",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"items": [
|
||||
"alert",
|
||||
"badge",
|
||||
"button",
|
||||
"card",
|
||||
"dialog",
|
||||
"form",
|
||||
"input",
|
||||
"tabs",
|
||||
"textarea",
|
||||
"tokens"
|
||||
],
|
||||
"packageDependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"motion": "^12.38.0",
|
||||
"react": "^18.3.1 || ^19.0.0",
|
||||
"react-hook-form": "^7.71.2",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"registry": "cadence-ui",
|
||||
"sourcePackages": {
|
||||
"@ai-ui/tokens": "0.0.0",
|
||||
"@ai-ui/ui": "0.0.0"
|
||||
},
|
||||
"targetDir": "src/cadence-ui"
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import {
|
||||
alertDescriptionVariants,
|
||||
alertIconVariants,
|
||||
alertTitleVariants,
|
||||
alertVariants
|
||||
} from "./alert.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
|
||||
export type AlertProps = React.ComponentPropsWithoutRef<"div"> &
|
||||
VariantProps<typeof alertVariants> & {
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
icon,
|
||||
variant = "default",
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const hasIcon = Boolean(icon);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("root")}
|
||||
{...createDataAttributes({
|
||||
"has-icon": hasIcon,
|
||||
variant
|
||||
})}
|
||||
className={cn(alertVariants({ hasIcon, variant }), className)}
|
||||
ref={ref}
|
||||
role={props.role ?? "alert"}
|
||||
>
|
||||
{hasIcon ? <span {...createSlot("icon")} className={alertIconVariants()}>{icon}</span> : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export type AlertTitleProps = React.ComponentPropsWithoutRef<"h4">;
|
||||
|
||||
export const AlertTitle = forwardRef<HTMLHeadingElement, AlertTitleProps>(function AlertTitle(
|
||||
{ className, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<h4
|
||||
{...props}
|
||||
{...createSlot("label")}
|
||||
className={cn(alertTitleVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type AlertDescriptionProps = React.ComponentPropsWithoutRef<"div">;
|
||||
|
||||
export const AlertDescription = forwardRef<HTMLDivElement, AlertDescriptionProps>(
|
||||
function AlertDescription({ className, ...props }, ref) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("description")}
|
||||
className={cn(alertDescriptionVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const alertVariants = cva(
|
||||
[
|
||||
"relative grid gap-x-3 gap-y-1 rounded-[var(--radius-lg)] border p-4 shadow-[var(--shadow-xs)]",
|
||||
"text-[var(--color-foreground)]",
|
||||
getMotionRecipeClassNames("transition", "ring")
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-[var(--color-border)] bg-[var(--color-card)]",
|
||||
success:
|
||||
"border-[color-mix(in_oklch,var(--color-success)_34%,var(--color-border))] bg-[color-mix(in_oklch,var(--color-success)_10%,var(--color-card))]",
|
||||
warning:
|
||||
"border-[color-mix(in_oklch,var(--color-warning)_34%,var(--color-border))] bg-[color-mix(in_oklch,var(--color-warning)_12%,var(--color-card))]",
|
||||
destructive:
|
||||
"border-[color-mix(in_oklch,var(--color-destructive)_38%,var(--color-border))] bg-[color-mix(in_oklch,var(--color-destructive)_10%,var(--color-card))]"
|
||||
},
|
||||
hasIcon: {
|
||||
false: "",
|
||||
true: "grid-cols-[auto_1fr]"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
hasIcon: false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const alertIconVariants = cva(
|
||||
"row-span-2 mt-0.5 inline-flex size-5 items-center justify-center rounded-[var(--radius-full)] text-[var(--color-muted-foreground)]"
|
||||
);
|
||||
|
||||
export const alertTitleVariants = cva(
|
||||
"text-sm font-semibold tracking-[var(--tracking-tight)] text-[var(--color-foreground)]"
|
||||
);
|
||||
|
||||
export const alertDescriptionVariants = cva(
|
||||
"text-sm leading-6 text-[var(--color-muted-foreground)]"
|
||||
);
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Slot, Slottable } from "@radix-ui/react-slot";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import { badgeVariants } from "./badge.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import { createDataAttributes, createSlot, type AsChildProp } from "../lib/contracts";
|
||||
|
||||
export type BadgeProps = React.ComponentPropsWithoutRef<"span"> &
|
||||
AsChildProp &
|
||||
VariantProps<typeof badgeVariants>;
|
||||
|
||||
export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(
|
||||
{
|
||||
asChild = false,
|
||||
children,
|
||||
className,
|
||||
size,
|
||||
tone,
|
||||
variant,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const Component = asChild ? Slot : "span";
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
{...createSlot("root")}
|
||||
{...createDataAttributes({
|
||||
size,
|
||||
tone,
|
||||
variant
|
||||
})}
|
||||
className={cn(badgeVariants({ size, tone, variant }), className)}
|
||||
ref={ref}
|
||||
>
|
||||
{asChild ? <Slottable>{children}</Slottable> : <span {...createSlot("label")}>{children}</span>}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const badgeVariants = cva(
|
||||
[
|
||||
"inline-flex shrink-0 items-center justify-center gap-1 whitespace-nowrap rounded-[var(--radius-full)] border font-medium",
|
||||
"outline-none select-none",
|
||||
getMotionRecipeClassNames("transition", "ring")
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: "min-h-6 px-2 py-0.5 text-[0.7rem]",
|
||||
md: "min-h-7 px-2.5 py-1 text-xs"
|
||||
},
|
||||
variant: {
|
||||
subtle:
|
||||
"border-[var(--color-border)] bg-[var(--color-card)] text-[var(--color-foreground)]",
|
||||
solid:
|
||||
"border-transparent bg-[var(--color-foreground)] text-[var(--color-background)]",
|
||||
outline:
|
||||
"border-[var(--color-border-strong)] bg-transparent text-[var(--color-foreground)]"
|
||||
},
|
||||
tone: {
|
||||
neutral: "",
|
||||
primary:
|
||||
"data-[variant=subtle]:bg-[color-mix(in_oklch,var(--color-primary)_14%,var(--color-card))] data-[variant=subtle]:text-[var(--color-primary)] data-[variant=solid]:bg-[var(--color-primary)] data-[variant=solid]:text-[var(--color-primary-foreground)] data-[variant=outline]:border-[color-mix(in_oklch,var(--color-primary)_38%,var(--color-border-strong))] data-[variant=outline]:text-[var(--color-primary)]",
|
||||
success:
|
||||
"data-[variant=subtle]:bg-[color-mix(in_oklch,var(--color-success)_14%,var(--color-card))] data-[variant=subtle]:text-[color-mix(in_oklch,var(--color-success)_78%,var(--color-foreground))] data-[variant=solid]:bg-[var(--color-success)] data-[variant=solid]:text-[var(--color-success-foreground)] data-[variant=outline]:border-[color-mix(in_oklch,var(--color-success)_38%,var(--color-border-strong))] data-[variant=outline]:text-[color-mix(in_oklch,var(--color-success)_72%,var(--color-foreground))]",
|
||||
warning:
|
||||
"data-[variant=subtle]:bg-[color-mix(in_oklch,var(--color-warning)_18%,var(--color-card))] data-[variant=subtle]:text-[color-mix(in_oklch,var(--color-warning)_70%,var(--color-foreground))] data-[variant=solid]:bg-[var(--color-warning)] data-[variant=solid]:text-[var(--color-warning-foreground)] data-[variant=outline]:border-[color-mix(in_oklch,var(--color-warning)_40%,var(--color-border-strong))] data-[variant=outline]:text-[color-mix(in_oklch,var(--color-warning)_70%,var(--color-foreground))]",
|
||||
destructive:
|
||||
"data-[variant=subtle]:bg-[color-mix(in_oklch,var(--color-destructive)_12%,var(--color-card))] data-[variant=subtle]:text-[var(--color-destructive)] data-[variant=solid]:bg-[var(--color-destructive)] data-[variant=solid]:text-[var(--color-destructive-foreground)] data-[variant=outline]:border-[color-mix(in_oklch,var(--color-destructive)_38%,var(--color-border-strong))] data-[variant=outline]:text-[var(--color-destructive)]"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md",
|
||||
tone: "neutral",
|
||||
variant: "subtle"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,120 @@
|
||||
import { Slot, Slottable } from "@radix-ui/react-slot";
|
||||
import { forwardRef, useState } from "react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
|
||||
import { buttonVariants } from "./button.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import type { ButtonLikeElementProps } from "../lib/contracts";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
|
||||
export type ButtonProps = Omit<
|
||||
ButtonLikeElementProps,
|
||||
"onDrag" | "onDragEnd" | "onDragStart"
|
||||
> &
|
||||
VariantProps<typeof buttonVariants>;
|
||||
|
||||
function Spinner() {
|
||||
return (
|
||||
<motion.span
|
||||
{...createSlot("icon")}
|
||||
aria-hidden="true"
|
||||
animate={{ opacity: 1, rotate: 0, scale: 1 }}
|
||||
className="size-4 rounded-full border-2 border-current border-r-transparent animate-spin"
|
||||
exit={{ opacity: 0, rotate: 90, scale: 0.7 }}
|
||||
initial={{ opacity: 0, rotate: -90, scale: 0.7 }}
|
||||
transition={{
|
||||
duration: 0.18,
|
||||
ease: [0.22, 1, 0.36, 1]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||
{
|
||||
asChild = false,
|
||||
children,
|
||||
className,
|
||||
disabled,
|
||||
loading = false,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
size,
|
||||
type,
|
||||
variant,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const prefersReducedMotion = useReducedMotion();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const isDisabled = disabled || loading;
|
||||
const Component = asChild ? Slot : "button";
|
||||
const baseClassName = cn(buttonVariants({ loading, size, variant }), className);
|
||||
const label = asChild ? (
|
||||
<Slottable>{children}</Slottable>
|
||||
) : (
|
||||
<motion.span
|
||||
{...createSlot("label")}
|
||||
animate={{
|
||||
opacity: loading ? 0.94 : 1,
|
||||
x: loading ? 1.5 : 0
|
||||
}}
|
||||
transition={{
|
||||
duration: prefersReducedMotion ? 0.01 : 0.18,
|
||||
ease: [0.22, 1, 0.36, 1]
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</motion.span>
|
||||
);
|
||||
|
||||
const sheen = !asChild ? (
|
||||
<motion.span
|
||||
animate={
|
||||
prefersReducedMotion || isDisabled
|
||||
? { opacity: 0, x: "-120%" }
|
||||
: isHovered
|
||||
? { opacity: 0.75, x: "115%" }
|
||||
: { opacity: 0, x: "-120%" }
|
||||
}
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-y-0 left-0 w-1/2 rounded-[inherit] bg-[linear-gradient(120deg,transparent_0%,rgba(255,255,255,0.32)_45%,transparent_100%)] mix-blend-screen"
|
||||
initial={false}
|
||||
transition={{
|
||||
duration: prefersReducedMotion ? 0.01 : 0.55,
|
||||
ease: [0.16, 1, 0.3, 1]
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
{...createSlot("root")}
|
||||
{...createDataAttributes({
|
||||
disabled: isDisabled,
|
||||
loading,
|
||||
size,
|
||||
variant
|
||||
})}
|
||||
className={baseClassName}
|
||||
disabled={asChild ? undefined : isDisabled}
|
||||
onMouseEnter={(event) => {
|
||||
setIsHovered(true);
|
||||
onMouseEnter?.(event as never);
|
||||
}}
|
||||
onMouseLeave={(event) => {
|
||||
setIsHovered(false);
|
||||
onMouseLeave?.(event as never);
|
||||
}}
|
||||
ref={ref}
|
||||
type={asChild ? undefined : type ?? "button"}
|
||||
>
|
||||
{sheen}
|
||||
<AnimatePresence initial={false}>{loading ? <Spinner /> : null}</AnimatePresence>
|
||||
{label}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
[
|
||||
"relative isolate inline-flex shrink-0 items-center justify-center gap-2 overflow-hidden whitespace-nowrap",
|
||||
"rounded-[var(--radius-sm)] border font-medium select-none",
|
||||
"outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-ring)]",
|
||||
"focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-background)]",
|
||||
"disabled:pointer-events-none disabled:opacity-55",
|
||||
getMotionRecipeClassNames("pressable", "ring")
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: "h-9 px-3 text-sm",
|
||||
md: "h-10 px-4 text-sm",
|
||||
lg: "h-12 px-5 text-base",
|
||||
icon: "h-10 w-10 text-sm"
|
||||
},
|
||||
variant: {
|
||||
primary:
|
||||
"border-transparent bg-[var(--color-primary)] text-[var(--color-primary-foreground)] shadow-[var(--shadow-xs)]",
|
||||
secondary:
|
||||
"border-[var(--color-border-strong)] bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)]",
|
||||
ghost:
|
||||
"border-transparent bg-transparent text-[var(--color-foreground)] hover:bg-[var(--color-surface)]",
|
||||
subtle:
|
||||
"border-[var(--color-border)] bg-[var(--color-card)] text-[var(--color-foreground)] shadow-[var(--shadow-xs)]",
|
||||
destructive:
|
||||
"border-transparent bg-[var(--color-destructive)] text-[var(--color-destructive-foreground)] shadow-[var(--shadow-xs)]"
|
||||
},
|
||||
loading: {
|
||||
false: "",
|
||||
true: "cursor-wait"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md",
|
||||
variant: "primary"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,119 @@
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import {
|
||||
cardContentVariants,
|
||||
cardDescriptionVariants,
|
||||
cardFooterVariants,
|
||||
cardHeaderVariants,
|
||||
cardTitleVariants,
|
||||
cardVariants
|
||||
} from "./card.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
|
||||
export type CardProps = React.ComponentPropsWithoutRef<"div"> &
|
||||
VariantProps<typeof cardVariants>;
|
||||
|
||||
export const Card = forwardRef<HTMLDivElement, CardProps>(function Card(
|
||||
{
|
||||
className,
|
||||
interactive,
|
||||
tone,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("root")}
|
||||
{...createDataAttributes({
|
||||
interactive,
|
||||
tone
|
||||
})}
|
||||
className={cn(cardVariants({ interactive, tone }), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type CardHeaderProps = React.ComponentPropsWithoutRef<"div">;
|
||||
|
||||
export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(function CardHeader(
|
||||
{ className, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("header")}
|
||||
className={cn(cardHeaderVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type CardTitleProps = React.ComponentPropsWithoutRef<"h3">;
|
||||
|
||||
export const CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>(function CardTitle(
|
||||
{ className, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<h3
|
||||
{...props}
|
||||
{...createSlot("label")}
|
||||
className={cn(cardTitleVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export type CardDescriptionProps = React.ComponentPropsWithoutRef<"p">;
|
||||
|
||||
export const CardDescription = forwardRef<HTMLParagraphElement, CardDescriptionProps>(
|
||||
function CardDescription({ className, ...props }, ref) {
|
||||
return (
|
||||
<p
|
||||
{...props}
|
||||
{...createSlot("description")}
|
||||
className={cn(cardDescriptionVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export type CardContentProps = React.ComponentPropsWithoutRef<"div">;
|
||||
|
||||
export const CardContent = forwardRef<HTMLDivElement, CardContentProps>(function CardContent(
|
||||
{ className, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("content")}
|
||||
className={cn(cardContentVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type CardFooterProps = React.ComponentPropsWithoutRef<"div">;
|
||||
|
||||
export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(function CardFooter(
|
||||
{ className, ...props },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("footer")}
|
||||
className={cn(cardFooterVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const cardVariants = cva(
|
||||
[
|
||||
"rounded-[var(--radius-lg)] border text-[var(--color-card-foreground)]",
|
||||
"bg-[var(--color-card)] shadow-[var(--shadow-sm)]",
|
||||
getMotionRecipeClassNames("transition", "ring")
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
tone: {
|
||||
default: "border-[var(--color-border)]",
|
||||
subtle:
|
||||
"border-[color-mix(in_oklch,var(--color-border)_86%,transparent)] bg-[var(--color-surface)] shadow-[var(--shadow-xs)]",
|
||||
accent:
|
||||
"border-[color-mix(in_oklch,var(--color-primary)_26%,var(--color-border))] bg-[color-mix(in_oklch,var(--color-primary)_8%,var(--color-card))]"
|
||||
},
|
||||
interactive: {
|
||||
false: "",
|
||||
true: "hover:-translate-y-[2px] hover:shadow-[var(--shadow-md)]"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
tone: "default",
|
||||
interactive: false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const cardHeaderVariants = cva("grid gap-2 p-6 pb-0");
|
||||
|
||||
export const cardTitleVariants = cva(
|
||||
"text-lg font-semibold tracking-[var(--tracking-tight)] text-[var(--color-foreground)]"
|
||||
);
|
||||
|
||||
export const cardDescriptionVariants = cva(
|
||||
"text-sm leading-6 text-[var(--color-muted-foreground)]"
|
||||
);
|
||||
|
||||
export const cardContentVariants = cva("p-6");
|
||||
|
||||
export const cardFooterVariants = cva("flex flex-wrap items-center gap-3 p-6 pt-0");
|
||||
@@ -0,0 +1,111 @@
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
|
||||
|
||||
import { dialogContentVariants, dialogFooterVariants, dialogHeaderVariants, dialogOverlayVariants } from "./dialog.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
|
||||
export const Dialog = DialogPrimitive.Root;
|
||||
export const DialogTrigger = DialogPrimitive.Trigger;
|
||||
export const DialogPortal = DialogPrimitive.Portal;
|
||||
export const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
export const DialogOverlay = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(function DialogOverlay({ className, ...props }, ref) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
{...props}
|
||||
{...createSlot("overlay")}
|
||||
className={cn(dialogOverlayVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type DialogContentProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Content> &
|
||||
VariantProps<typeof dialogContentVariants>;
|
||||
|
||||
export const DialogContent = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Content>,
|
||||
DialogContentProps
|
||||
>(function DialogContent({ children, className, size, ...props }, ref) {
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
{...props}
|
||||
{...createSlot("content")}
|
||||
{...createDataAttributes({ size })}
|
||||
className={cn(dialogContentVariants({ size }), className)}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close
|
||||
aria-label="Close dialog"
|
||||
className="absolute right-4 top-4 inline-flex size-9 items-center justify-center rounded-[var(--radius-full)] text-[var(--color-muted-foreground)] outline-none transition-colors duration-[var(--dur-fast)] ease-[var(--ease-standard)] hover:bg-[var(--color-surface)] hover:text-[var(--color-foreground)] focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-card)]"
|
||||
>
|
||||
<span aria-hidden="true" className="text-lg leading-none">
|
||||
×
|
||||
</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
});
|
||||
|
||||
export function DialogHeader({
|
||||
className,
|
||||
...props
|
||||
}: ComponentPropsWithoutRef<"div">) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("header")}
|
||||
className={cn(dialogHeaderVariants(), className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DialogFooter({
|
||||
className,
|
||||
...props
|
||||
}: ComponentPropsWithoutRef<"div">) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("footer")}
|
||||
className={cn(dialogFooterVariants(), className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const DialogTitle = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Title>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(function DialogTitle({ className, ...props }, ref) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
{...props}
|
||||
{...createSlot("label")}
|
||||
className={cn("pr-10 text-xl font-semibold tracking-tight", className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const DialogDescription = forwardRef<
|
||||
ElementRef<typeof DialogPrimitive.Description>,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(function DialogDescription({ className, ...props }, ref) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
{...props}
|
||||
{...createSlot("description")}
|
||||
className={cn("text-sm leading-6 text-[var(--color-muted-foreground)]", className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { cva } from "../lib/cva";
|
||||
|
||||
export const dialogOverlayVariants = cva([
|
||||
"fixed inset-0 z-50 bg-[var(--color-overlay)] backdrop-blur-[2px]",
|
||||
"data-[state=open]:motion-overlay-enter data-[state=closed]:motion-overlay-exit"
|
||||
]);
|
||||
|
||||
export const dialogContentVariants = cva(
|
||||
[
|
||||
"fixed left-1/2 top-1/2 z-50 grid -translate-x-1/2 -translate-y-1/2 gap-5",
|
||||
"w-[min(calc(100vw-2rem),40rem)] max-h-[calc(100vh-2rem)] overflow-y-auto",
|
||||
"rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 text-[var(--color-card-foreground)] shadow-[var(--shadow-md)] outline-none",
|
||||
"data-[state=open]:motion-enter-rise data-[state=closed]:motion-exit-drop"
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: "w-[min(calc(100vw-2rem),30rem)]",
|
||||
md: "w-[min(calc(100vw-2rem),40rem)]",
|
||||
lg: "w-[min(calc(100vw-2rem),52rem)]"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const dialogHeaderVariants = cva(["flex flex-col gap-2 text-left"]);
|
||||
|
||||
export const dialogFooterVariants = cva([
|
||||
"flex flex-col-reverse gap-3 sm:flex-row sm:justify-end"
|
||||
]);
|
||||
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
createContext,
|
||||
forwardRef,
|
||||
useContext,
|
||||
useId,
|
||||
type ComponentPropsWithoutRef
|
||||
} from "react";
|
||||
|
||||
import { Label } from "./label";
|
||||
import { cn } from "../lib/cn";
|
||||
import { createDataAttributes, createSlot, type FieldStateProps } from "../lib/contracts";
|
||||
|
||||
type FieldContextValue = {
|
||||
descriptionId: string;
|
||||
disabled: boolean;
|
||||
errorId: string;
|
||||
inputId: string;
|
||||
invalid: boolean;
|
||||
readOnly: boolean;
|
||||
required: boolean;
|
||||
};
|
||||
|
||||
export type FieldRenderProps = FieldContextValue;
|
||||
|
||||
const FieldContext = createContext<FieldContextValue | null>(null);
|
||||
|
||||
export function useFieldContext() {
|
||||
return useContext(FieldContext);
|
||||
}
|
||||
|
||||
export type FieldProps = ComponentPropsWithoutRef<"div"> &
|
||||
FieldStateProps & {
|
||||
orientation?: "horizontal" | "vertical";
|
||||
};
|
||||
|
||||
export const Field = forwardRef<HTMLDivElement, FieldProps>(function Field(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
disabled = false,
|
||||
id,
|
||||
invalid = false,
|
||||
orientation = "vertical",
|
||||
readOnly = false,
|
||||
required = false,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const reactId = useId();
|
||||
const baseId = id ?? `field-${reactId.replace(/:/g, "")}`;
|
||||
const value: FieldContextValue = {
|
||||
descriptionId: `${baseId}-description`,
|
||||
disabled,
|
||||
errorId: `${baseId}-error`,
|
||||
inputId: `${baseId}-control`,
|
||||
invalid,
|
||||
readOnly,
|
||||
required
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldContext.Provider value={value}>
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("root")}
|
||||
{...createDataAttributes({
|
||||
disabled,
|
||||
invalid,
|
||||
orientation,
|
||||
readonly: readOnly,
|
||||
required
|
||||
})}
|
||||
className={cn(
|
||||
"grid gap-2.5",
|
||||
orientation === "horizontal" && "gap-3 sm:grid-cols-[minmax(10rem,12rem)_minmax(0,1fr)] sm:items-start",
|
||||
className
|
||||
)}
|
||||
id={id}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export const FormItem = Field;
|
||||
export const FieldLabel = Label;
|
||||
|
||||
export type FieldDescriptionProps = ComponentPropsWithoutRef<"p">;
|
||||
|
||||
export const FieldDescription = forwardRef<
|
||||
HTMLParagraphElement,
|
||||
FieldDescriptionProps
|
||||
>(function FieldDescription({ className, id, ...props }, ref) {
|
||||
const field = useFieldContext();
|
||||
|
||||
return (
|
||||
<p
|
||||
{...props}
|
||||
{...createSlot("description")}
|
||||
className={cn(
|
||||
"text-sm leading-6 text-[var(--color-muted-foreground)]",
|
||||
className
|
||||
)}
|
||||
id={id ?? field?.descriptionId}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type FieldErrorProps = ComponentPropsWithoutRef<"p">;
|
||||
|
||||
export const FieldError = forwardRef<
|
||||
HTMLParagraphElement,
|
||||
FieldErrorProps
|
||||
>(function FieldError({ children, className, id, ...props }, ref) {
|
||||
const field = useFieldContext();
|
||||
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
{...props}
|
||||
{...createSlot("description")}
|
||||
className={cn(
|
||||
"text-sm font-medium leading-6 text-[var(--color-destructive)]",
|
||||
className
|
||||
)}
|
||||
id={id ?? field?.errorId}
|
||||
ref={ref}
|
||||
role="alert"
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
|
||||
export type FieldControlProps = ComponentPropsWithoutRef<"div">;
|
||||
|
||||
export const FieldControl = forwardRef<
|
||||
HTMLDivElement,
|
||||
FieldControlProps
|
||||
>(function FieldControl({ className, ...props }, ref) {
|
||||
const field = useFieldContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
{...createSlot("control")}
|
||||
{...createDataAttributes({
|
||||
disabled: field?.disabled,
|
||||
invalid: field?.invalid,
|
||||
readonly: field?.readOnly,
|
||||
required: field?.required
|
||||
})}
|
||||
className={cn("grid gap-2", className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export function useFieldIds() {
|
||||
const field = useFieldContext();
|
||||
|
||||
if (!field) {
|
||||
throw new Error("useFieldIds must be used within <Field>.");
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import {
|
||||
createContext,
|
||||
forwardRef,
|
||||
useContext,
|
||||
useId,
|
||||
type ComponentPropsWithoutRef,
|
||||
type ReactNode
|
||||
} from "react";
|
||||
import {
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
type FieldError,
|
||||
type FieldValues,
|
||||
type FormProviderProps,
|
||||
type UseFormReturn
|
||||
} from "react-hook-form";
|
||||
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError as BaseFieldError,
|
||||
FieldLabel,
|
||||
useFieldContext,
|
||||
type FieldDescriptionProps,
|
||||
type FieldErrorProps,
|
||||
type FieldProps
|
||||
} from "./field";
|
||||
import { cn } from "../lib/cn";
|
||||
import { createDataAttributes } from "../lib/contracts";
|
||||
|
||||
type FormItemContextValue = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
const FormItemContext = createContext<FormItemContextValue | null>(null);
|
||||
|
||||
function mergeIds(...ids: Array<string | undefined>) {
|
||||
const value = ids.filter(Boolean).join(" ").trim();
|
||||
return value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function useSafeFormContext() {
|
||||
try {
|
||||
return useFormContext();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function useFormFieldState(name?: string) {
|
||||
const form = useSafeFormContext();
|
||||
|
||||
if (!form || !name) {
|
||||
return {
|
||||
error: undefined as FieldError | undefined,
|
||||
invalid: false
|
||||
};
|
||||
}
|
||||
|
||||
// Accessing concrete formState branches subscribes this hook to RHF updates.
|
||||
void form.formState.errors;
|
||||
void form.formState.touchedFields;
|
||||
void form.formState.dirtyFields;
|
||||
|
||||
const fieldState = form.getFieldState(name, form.formState);
|
||||
|
||||
return {
|
||||
error: fieldState.error,
|
||||
invalid: fieldState.invalid
|
||||
};
|
||||
}
|
||||
|
||||
function getErrorMessage(error?: FieldError) {
|
||||
if (!error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof error.message === "string" && error.message.length > 0) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
if (typeof error.type === "string" && error.type.length > 0) {
|
||||
return error.type;
|
||||
}
|
||||
|
||||
return "Invalid value.";
|
||||
}
|
||||
|
||||
export const Form = FormProvider;
|
||||
|
||||
export type FormProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TContext = unknown,
|
||||
TTransformedValues extends FieldValues | undefined = undefined
|
||||
> = FormProviderProps<TFieldValues, TContext, TTransformedValues>;
|
||||
|
||||
export type FormItemProps = Omit<FieldProps, "id" | "invalid"> & {
|
||||
name?: string;
|
||||
invalid?: boolean;
|
||||
};
|
||||
|
||||
export const FormItem = forwardRef<HTMLDivElement, FormItemProps>(function FormItem(
|
||||
{ name, invalid, ...props },
|
||||
ref
|
||||
) {
|
||||
const reactId = useId();
|
||||
const { invalid: formInvalid } = useFormFieldState(name);
|
||||
const resolvedInvalid = invalid ?? formInvalid;
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ name }}>
|
||||
<Field
|
||||
{...props}
|
||||
id={`form-item-${reactId.replace(/:/g, "")}`}
|
||||
invalid={resolvedInvalid}
|
||||
ref={ref}
|
||||
/>
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export type FormLabelProps = ComponentPropsWithoutRef<typeof FieldLabel>;
|
||||
|
||||
export const FormLabel = forwardRef<HTMLLabelElement, FormLabelProps>(function FormLabel(
|
||||
props,
|
||||
ref
|
||||
) {
|
||||
const { name } = useContext(FormItemContext) ?? {};
|
||||
const { invalid: formInvalid } = useFormFieldState(name);
|
||||
|
||||
return (
|
||||
<FieldLabel
|
||||
{...props}
|
||||
aria-invalid={props["aria-invalid"] ?? (formInvalid || undefined)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export type FormControlProps = ComponentPropsWithoutRef<typeof Slot> & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const FormControl = forwardRef<HTMLDivElement, FormControlProps>(function FormControl(
|
||||
{ children, className, ...props },
|
||||
ref
|
||||
) {
|
||||
const { name } = useContext(FormItemContext) ?? {};
|
||||
const field = useFieldContext();
|
||||
const { invalid } = useFormFieldState(name);
|
||||
const describedBy = mergeIds(
|
||||
typeof props["aria-describedby"] === "string" ? props["aria-describedby"] : undefined,
|
||||
field?.descriptionId,
|
||||
invalid ? field?.errorId : undefined
|
||||
);
|
||||
const controlId = typeof props.id === "string" ? props.id : field?.inputId;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...createDataAttributes({
|
||||
invalid
|
||||
})}
|
||||
className={cn("grid gap-2", className)}
|
||||
data-slot="control"
|
||||
ref={ref}
|
||||
>
|
||||
<Slot
|
||||
{...props}
|
||||
aria-describedby={describedBy}
|
||||
aria-invalid={props["aria-invalid"] ?? (invalid || undefined)}
|
||||
id={controlId}
|
||||
>
|
||||
{children}
|
||||
</Slot>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export type FormDescriptionProps = FieldDescriptionProps;
|
||||
|
||||
export const FormDescription = forwardRef<HTMLParagraphElement, FormDescriptionProps>(
|
||||
function FormDescription(props, ref) {
|
||||
return <FieldDescription {...props} ref={ref} />;
|
||||
}
|
||||
);
|
||||
|
||||
export type FormMessageProps = Omit<FieldErrorProps, "children"> & {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormMessage = forwardRef<HTMLParagraphElement, FormMessageProps>(
|
||||
function FormMessage({ children, ...props }, ref) {
|
||||
const { name } = useContext(FormItemContext) ?? {};
|
||||
const { error } = useFormFieldState(name);
|
||||
const message = getErrorMessage(error) ?? children;
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseFieldError {...props} ref={ref}>
|
||||
{message}
|
||||
</BaseFieldError>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export type FormMethods<TFieldValues extends FieldValues = FieldValues> = UseFormReturn<TFieldValues>;
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
forwardRef,
|
||||
type ComponentPropsWithoutRef
|
||||
} from "react";
|
||||
|
||||
import { inputVariants } from "./input.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import type { FieldStateProps } from "../lib/contracts";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
import { useFieldContext } from "./field";
|
||||
|
||||
function mergeIds(...ids: Array<string | undefined>) {
|
||||
const value = ids.filter(Boolean).join(" ").trim();
|
||||
return value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
export type InputProps = Omit<ComponentPropsWithoutRef<"input">, "size"> &
|
||||
FieldStateProps &
|
||||
VariantProps<typeof inputVariants>;
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
{
|
||||
className,
|
||||
disabled,
|
||||
id,
|
||||
invalid,
|
||||
readOnly,
|
||||
required,
|
||||
size,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const field = useFieldContext();
|
||||
const resolvedDisabled = disabled ?? field?.disabled ?? false;
|
||||
const resolvedInvalid = invalid ?? field?.invalid ?? false;
|
||||
const resolvedReadOnly = readOnly ?? field?.readOnly ?? false;
|
||||
const resolvedRequired = required ?? field?.required ?? false;
|
||||
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
{...createSlot("input")}
|
||||
{...createDataAttributes({
|
||||
disabled: resolvedDisabled,
|
||||
invalid: resolvedInvalid,
|
||||
readonly: resolvedReadOnly,
|
||||
required: resolvedRequired,
|
||||
size
|
||||
})}
|
||||
aria-describedby={mergeIds(
|
||||
props["aria-describedby"],
|
||||
field?.descriptionId,
|
||||
resolvedInvalid ? field?.errorId : undefined
|
||||
)}
|
||||
aria-invalid={resolvedInvalid || undefined}
|
||||
className={cn(inputVariants({ size }), className)}
|
||||
disabled={resolvedDisabled}
|
||||
id={id ?? field?.inputId}
|
||||
readOnly={resolvedReadOnly}
|
||||
ref={ref}
|
||||
required={resolvedRequired}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const inputVariants = cva(
|
||||
[
|
||||
"flex w-full min-w-0 rounded-[var(--radius-md)] border border-[var(--color-input)] bg-[var(--color-card)]",
|
||||
"text-[var(--color-foreground)] shadow-[var(--shadow-xs)] outline-none",
|
||||
"placeholder:text-[var(--color-muted-foreground)]",
|
||||
"focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-background)]",
|
||||
"disabled:cursor-not-allowed disabled:bg-[var(--color-surface)] disabled:text-[var(--color-muted-foreground)] disabled:opacity-100",
|
||||
"read-only:bg-[var(--color-surface)] read-only:text-[var(--color-muted-foreground)]",
|
||||
"aria-[invalid=true]:border-[color-mix(in_oklch,var(--color-destructive)_42%,var(--color-border-strong))]",
|
||||
"aria-[invalid=true]:shadow-[0_0_0_1px_color-mix(in_oklch,var(--color-destructive)_28%,transparent)]",
|
||||
getMotionRecipeClassNames("ring")
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: "h-10 px-3 text-sm",
|
||||
md: "h-11 px-4 text-sm",
|
||||
lg: "h-12 px-4 text-base"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,56 @@
|
||||
import { forwardRef, type ComponentPropsWithoutRef } from "react";
|
||||
|
||||
import { cn } from "../lib/cn";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
import { useFieldContext } from "./field";
|
||||
|
||||
export type LabelProps = ComponentPropsWithoutRef<"label"> & {
|
||||
requiredIndicator?: boolean;
|
||||
};
|
||||
|
||||
export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
htmlFor,
|
||||
requiredIndicator = false,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const field = useFieldContext();
|
||||
const disabled = props["aria-disabled"] === true || field?.disabled === true;
|
||||
const invalid = props["aria-invalid"] === true || field?.invalid === true;
|
||||
const required = props["aria-required"] === true || field?.required === true;
|
||||
|
||||
return (
|
||||
<label
|
||||
{...props}
|
||||
{...createSlot("label")}
|
||||
{...createDataAttributes({
|
||||
disabled,
|
||||
invalid,
|
||||
required
|
||||
})}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 text-sm font-medium leading-none text-[var(--color-foreground)]",
|
||||
"transition-colors duration-[var(--dur-fast)] ease-[var(--ease-standard)]",
|
||||
"data-[disabled]:cursor-not-allowed data-[disabled]:text-[var(--color-muted-foreground)]",
|
||||
"data-[invalid]:text-[color-mix(in_oklch,var(--color-destructive)_82%,var(--color-foreground))]",
|
||||
className
|
||||
)}
|
||||
htmlFor={htmlFor ?? field?.inputId}
|
||||
ref={ref}
|
||||
>
|
||||
<span>{children}</span>
|
||||
{requiredIndicator && required ? (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="text-[var(--color-destructive)]"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
|
||||
|
||||
import { tabsContentVariants, tabsListVariants, tabsTriggerVariants } from "./tabs.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
|
||||
export function Tabs({
|
||||
className,
|
||||
orientation = "horizontal",
|
||||
...props
|
||||
}: ComponentPropsWithoutRef<typeof TabsPrimitive.Root>) {
|
||||
return (
|
||||
<TabsPrimitive.Root
|
||||
{...props}
|
||||
{...createSlot("root")}
|
||||
{...createDataAttributes({ orientation })}
|
||||
className={cn("flex flex-col", className)}
|
||||
orientation={orientation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const TabsList = forwardRef<
|
||||
ElementRef<typeof TabsPrimitive.List>,
|
||||
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(function TabsList({ className, ...props }, ref) {
|
||||
return (
|
||||
<TabsPrimitive.List
|
||||
{...props}
|
||||
{...createSlot("list")}
|
||||
className={cn(tabsListVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const TabsTrigger = forwardRef<
|
||||
ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(function TabsTrigger({ className, disabled, ...props }, ref) {
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
{...props}
|
||||
{...createSlot("trigger")}
|
||||
{...createDataAttributes({ disabled })}
|
||||
className={cn(tabsTriggerVariants(), className)}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const TabsContent = forwardRef<
|
||||
ElementRef<typeof TabsPrimitive.Content>,
|
||||
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(function TabsContent({ className, ...props }, ref) {
|
||||
return (
|
||||
<TabsPrimitive.Content
|
||||
{...props}
|
||||
{...createSlot("content")}
|
||||
className={cn(tabsContentVariants(), className)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const tabsListVariants = cva([
|
||||
"inline-flex h-12 items-center gap-1 rounded-[var(--radius-full)] border border-[var(--color-border)] bg-[var(--color-surface)] p-1 shadow-[var(--shadow-xs)]"
|
||||
]);
|
||||
|
||||
export const tabsTriggerVariants = cva([
|
||||
"inline-flex min-w-[7rem] items-center justify-center rounded-[var(--radius-full)] px-4 py-2.5 text-sm font-medium outline-none",
|
||||
"text-[var(--color-muted-foreground)] transition-[color,background-color,box-shadow,transform] duration-[var(--dur-fast)] ease-[var(--ease-standard)]",
|
||||
"focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-background)]",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-45",
|
||||
"data-[state=active]:bg-[var(--color-card)] data-[state=active]:text-[var(--color-foreground)] data-[state=active]:shadow-[var(--shadow-xs)]",
|
||||
getMotionRecipeClassNames("ring")
|
||||
]);
|
||||
|
||||
export const tabsContentVariants = cva([
|
||||
"mt-4 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 text-[var(--color-card-foreground)] shadow-[var(--shadow-sm)] outline-none",
|
||||
"data-[state=active]:motion-enter-rise"
|
||||
]);
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
forwardRef,
|
||||
type ComponentPropsWithoutRef
|
||||
} from "react";
|
||||
|
||||
import { textareaVariants } from "./textarea.variants";
|
||||
import { cn } from "../lib/cn";
|
||||
import type { VariantProps } from "../lib/cva";
|
||||
import type { FieldStateProps } from "../lib/contracts";
|
||||
import { createDataAttributes, createSlot } from "../lib/contracts";
|
||||
import { useFieldContext } from "./field";
|
||||
|
||||
function mergeIds(...ids: Array<string | undefined>) {
|
||||
const value = ids.filter(Boolean).join(" ").trim();
|
||||
return value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
export type TextareaProps = ComponentPropsWithoutRef<"textarea"> &
|
||||
FieldStateProps &
|
||||
VariantProps<typeof textareaVariants>;
|
||||
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
|
||||
{
|
||||
className,
|
||||
disabled,
|
||||
id,
|
||||
invalid,
|
||||
readOnly,
|
||||
required,
|
||||
size,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const field = useFieldContext();
|
||||
const resolvedDisabled = disabled ?? field?.disabled ?? false;
|
||||
const resolvedInvalid = invalid ?? field?.invalid ?? false;
|
||||
const resolvedReadOnly = readOnly ?? field?.readOnly ?? false;
|
||||
const resolvedRequired = required ?? field?.required ?? false;
|
||||
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
{...createSlot("input")}
|
||||
{...createDataAttributes({
|
||||
disabled: resolvedDisabled,
|
||||
invalid: resolvedInvalid,
|
||||
readonly: resolvedReadOnly,
|
||||
required: resolvedRequired,
|
||||
size
|
||||
})}
|
||||
aria-describedby={mergeIds(
|
||||
props["aria-describedby"],
|
||||
field?.descriptionId,
|
||||
resolvedInvalid ? field?.errorId : undefined
|
||||
)}
|
||||
aria-invalid={resolvedInvalid || undefined}
|
||||
className={cn(textareaVariants({ size }), className)}
|
||||
disabled={resolvedDisabled}
|
||||
id={id ?? field?.inputId}
|
||||
readOnly={resolvedReadOnly}
|
||||
ref={ref}
|
||||
required={resolvedRequired}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { cva } from "../lib/cva";
|
||||
import { getMotionRecipeClassNames } from "../lib/motion";
|
||||
|
||||
export const textareaVariants = cva(
|
||||
[
|
||||
"flex min-h-[8.75rem] w-full min-w-0 resize-y rounded-[var(--radius-md)] border border-[var(--color-input)] bg-[var(--color-card)] px-4 py-3",
|
||||
"text-[var(--color-foreground)] shadow-[var(--shadow-xs)] outline-none",
|
||||
"placeholder:text-[var(--color-muted-foreground)]",
|
||||
"focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-background)]",
|
||||
"disabled:cursor-not-allowed disabled:bg-[var(--color-surface)] disabled:text-[var(--color-muted-foreground)] disabled:opacity-100",
|
||||
"read-only:bg-[var(--color-surface)] read-only:text-[var(--color-muted-foreground)]",
|
||||
"aria-[invalid=true]:border-[color-mix(in_oklch,var(--color-destructive)_42%,var(--color-border-strong))]",
|
||||
"aria-[invalid=true]:shadow-[0_0_0_1px_color-mix(in_oklch,var(--color-destructive)_28%,transparent)]",
|
||||
getMotionRecipeClassNames("ring")
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: "min-h-[7.5rem] px-3 py-2.5 text-sm",
|
||||
md: "min-h-[8.75rem] px-4 py-3 text-sm",
|
||||
lg: "min-h-[10.5rem] px-4 py-3.5 text-base"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "md"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
export type CommonComponentProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export type AsChildProp = {
|
||||
asChild?: boolean;
|
||||
};
|
||||
|
||||
export type DisableableProps = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type InvalidatableProps = {
|
||||
invalid?: boolean;
|
||||
};
|
||||
|
||||
export type LoadingProps = {
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
export type ReadonlyProps = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export type RequiredProps = {
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
export type PressableStateProps = CommonComponentProps &
|
||||
DisableableProps &
|
||||
LoadingProps &
|
||||
AsChildProp;
|
||||
|
||||
export type FieldStateProps = CommonComponentProps &
|
||||
DisableableProps &
|
||||
InvalidatableProps &
|
||||
ReadonlyProps &
|
||||
RequiredProps;
|
||||
|
||||
export type ControllableStateProps<T> = {
|
||||
defaultValue?: T;
|
||||
onValueChange?: (value: T) => void;
|
||||
value?: T;
|
||||
};
|
||||
|
||||
export type DataAttributes = Partial<Record<`data-${string}`, string | undefined>>;
|
||||
|
||||
export type SlotAttributes<Name extends string> = {
|
||||
"data-slot": Name;
|
||||
};
|
||||
|
||||
export const commonSlotNames = [
|
||||
{
|
||||
slot: "root",
|
||||
guidance: "The outermost element rendered by the component."
|
||||
},
|
||||
{
|
||||
slot: "label",
|
||||
guidance: "Primary visible label or title within the component."
|
||||
},
|
||||
{
|
||||
slot: "description",
|
||||
guidance: "Secondary supporting copy connected to the component."
|
||||
},
|
||||
{
|
||||
slot: "control",
|
||||
guidance: "Focusable or interactive control surface."
|
||||
},
|
||||
{
|
||||
slot: "input",
|
||||
guidance: "Typed value entry element such as input or textarea."
|
||||
},
|
||||
{
|
||||
slot: "trigger",
|
||||
guidance: "Element that opens, closes, or toggles related content."
|
||||
},
|
||||
{
|
||||
slot: "content",
|
||||
guidance: "Popover, drawer, menu, dialog, or expandable content region."
|
||||
},
|
||||
{
|
||||
slot: "icon",
|
||||
guidance: "Decorative or stateful icon container."
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const commonStateNames = [
|
||||
{
|
||||
state: "state",
|
||||
guidance: "Use for finite machine-like values such as open, closed, active, or inactive."
|
||||
},
|
||||
{
|
||||
state: "disabled",
|
||||
guidance: "Set when interaction is blocked."
|
||||
},
|
||||
{
|
||||
state: "invalid",
|
||||
guidance: "Set when validation fails or the field is in an error state."
|
||||
},
|
||||
{
|
||||
state: "loading",
|
||||
guidance: "Set when the component is waiting on async work."
|
||||
},
|
||||
{
|
||||
state: "readonly",
|
||||
guidance: "Set when the value can be viewed but not edited."
|
||||
},
|
||||
{
|
||||
state: "required",
|
||||
guidance: "Set when the field requires a value."
|
||||
},
|
||||
{
|
||||
state: "orientation",
|
||||
guidance: "Use for horizontal or vertical layout state when styling depends on it."
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const authoringChecklist = [
|
||||
"Expose `className` on every styled public component.",
|
||||
"Forward `ref` on every focusable or measurable public component.",
|
||||
"Use `asChild` only for components whose root element is meant to be polymorphic.",
|
||||
"Represent boolean UI states with empty-string `data-*` attributes.",
|
||||
"Represent finite machine states with `data-state=\"...\"` and keep the values stable.",
|
||||
"Name internal stylable parts with `data-slot` so variants and docs can target them consistently.",
|
||||
"Prefer controlled and uncontrolled APIs together when the component manages user state.",
|
||||
"Consume tokens and motion recipes instead of raw visual values."
|
||||
] as const;
|
||||
|
||||
export const cvaConventions = [
|
||||
"Put shared layout and focus primitives in the CVA base string, not in individual variants.",
|
||||
"Reserve `variant` for semantic appearance changes and `size` only when spacing or density genuinely changes.",
|
||||
"Keep default variants explicit so stories and tests do not depend on implicit visual fallbacks.",
|
||||
"Prefer a small stable variant surface over one-off booleans that fragment the API."
|
||||
] as const;
|
||||
|
||||
export function dataAttr(active?: boolean) {
|
||||
return active ? "" : undefined;
|
||||
}
|
||||
|
||||
export function createDataAttributes(
|
||||
states: Record<string, boolean | number | string | null | undefined>
|
||||
): DataAttributes {
|
||||
const attributes: DataAttributes = {};
|
||||
|
||||
for (const [name, value] of Object.entries(states)) {
|
||||
const key = `data-${name}` as const;
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
attributes[key] = dataAttr(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
attributes[key] = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
export function createSlot<Name extends string>(name: Name): SlotAttributes<Name> {
|
||||
return { "data-slot": name };
|
||||
}
|
||||
|
||||
export function withRootProps<T extends object>(
|
||||
props: T,
|
||||
options: {
|
||||
slot?: string;
|
||||
states?: Record<string, boolean | number | string | null | undefined>;
|
||||
} = {}
|
||||
) {
|
||||
return {
|
||||
...props,
|
||||
...(options.slot ? createSlot(options.slot) : {}),
|
||||
...(options.states ? createDataAttributes(options.states) : {})
|
||||
};
|
||||
}
|
||||
|
||||
export type ButtonLikeElementProps = ComponentPropsWithoutRef<"button"> &
|
||||
PressableStateProps;
|
||||
|
||||
export type InputLikeElementProps = ComponentPropsWithoutRef<"input"> &
|
||||
FieldStateProps;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { cva, cx, type VariantProps } from "class-variance-authority";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
export const motionDurations = {
|
||||
instant: "var(--dur-instant)",
|
||||
fast: "var(--dur-fast)",
|
||||
base: "var(--dur-base)",
|
||||
slow: "var(--dur-slow)",
|
||||
deliberate: "var(--dur-deliberate)"
|
||||
} as const;
|
||||
|
||||
export const motionEasings = {
|
||||
standard: "var(--ease-standard)",
|
||||
emphasized: "var(--ease-emphasized)",
|
||||
exit: "var(--ease-exit)"
|
||||
} as const;
|
||||
|
||||
export const motionDistances = {
|
||||
xs: "var(--distance-xs)",
|
||||
sm: "var(--distance-sm)",
|
||||
md: "var(--distance-md)",
|
||||
lg: "var(--distance-lg)"
|
||||
} as const;
|
||||
|
||||
export const motionScales = {
|
||||
press: "var(--scale-press)",
|
||||
hover: "var(--scale-hover)",
|
||||
pop: "var(--scale-pop)"
|
||||
} as const;
|
||||
|
||||
export const motionRecipes = {
|
||||
transition: "motion-transition",
|
||||
pressable: "motion-pressable",
|
||||
enterFade: "motion-enter-fade",
|
||||
enterRise: "motion-enter-rise",
|
||||
overlayEnter: "motion-overlay-enter",
|
||||
overlayExit: "motion-overlay-exit",
|
||||
exitFade: "motion-exit-fade",
|
||||
exitDrop: "motion-exit-drop",
|
||||
ring: "motion-ring"
|
||||
} as const;
|
||||
|
||||
export type MotionRecipeName = keyof typeof motionRecipes;
|
||||
|
||||
export function getMotionRecipeClassNames(...recipes: MotionRecipeName[]) {
|
||||
return recipes.map((recipe) => motionRecipes[recipe]).join(" ");
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: light;
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--color-background);
|
||||
color: var(--color-foreground);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-normal);
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: color-mix(in oklch, var(--color-primary) 24%, transparent);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
export const themeNames = ["light", "dark", "brand"] as const;
|
||||
export type ThemeName = (typeof themeNames)[number];
|
||||
|
||||
export const defaultTheme: ThemeName = "light";
|
||||
|
||||
export const themeDetails = {
|
||||
light: {
|
||||
label: "Light",
|
||||
note: "Warm editorial default"
|
||||
},
|
||||
dark: {
|
||||
label: "Dark",
|
||||
note: "Warm charcoal default"
|
||||
},
|
||||
brand: {
|
||||
label: "Brand",
|
||||
note: "Verdant accent scaffold"
|
||||
}
|
||||
} as const satisfies Record<ThemeName, { label: string; note: string }>;
|
||||
|
||||
export const motionModeNames = ["system", "reduced"] as const;
|
||||
export type MotionModeName = (typeof motionModeNames)[number];
|
||||
|
||||
export const defaultMotionMode: MotionModeName = "system";
|
||||
|
||||
export const motionScale = {
|
||||
instant: "var(--dur-instant)",
|
||||
fast: "var(--dur-fast)",
|
||||
base: "var(--dur-base)",
|
||||
slow: "var(--dur-slow)",
|
||||
deliberate: "var(--dur-deliberate)"
|
||||
} as const;
|
||||
|
||||
export const colorTokens = [
|
||||
{ name: "background", cssVar: "--color-background", role: "Application canvas" },
|
||||
{ name: "foreground", cssVar: "--color-foreground", role: "Primary text and icons" },
|
||||
{ name: "surface", cssVar: "--color-surface", role: "Secondary surface backgrounds" },
|
||||
{
|
||||
name: "surface-strong",
|
||||
cssVar: "--color-surface-strong",
|
||||
role: "Elevated surface emphasis"
|
||||
},
|
||||
{ name: "card", cssVar: "--color-card", role: "Cards and floating panels" },
|
||||
{ name: "border", cssVar: "--color-border", role: "Default dividers and input borders" },
|
||||
{
|
||||
name: "border-strong",
|
||||
cssVar: "--color-border-strong",
|
||||
role: "Higher emphasis dividers"
|
||||
},
|
||||
{ name: "primary", cssVar: "--color-primary", role: "Primary actions and highlights" },
|
||||
{
|
||||
name: "secondary",
|
||||
cssVar: "--color-secondary",
|
||||
role: "Secondary fills and supporting actions"
|
||||
},
|
||||
{ name: "muted", cssVar: "--color-muted", role: "Subtle supporting surfaces" },
|
||||
{
|
||||
name: "muted-foreground",
|
||||
cssVar: "--color-muted-foreground",
|
||||
role: "Secondary text and captions"
|
||||
},
|
||||
{ name: "accent", cssVar: "--color-accent", role: "Moments of emphasis or delight" },
|
||||
{ name: "success", cssVar: "--color-success", role: "Success feedback" },
|
||||
{ name: "warning", cssVar: "--color-warning", role: "Warning feedback" },
|
||||
{ name: "destructive", cssVar: "--color-destructive", role: "Destructive actions" }
|
||||
] as const;
|
||||
|
||||
export const typographyTokens = [
|
||||
{
|
||||
name: "caption",
|
||||
fontVar: "--text-xs",
|
||||
lineHeightVar: "--leading-normal",
|
||||
familyVar: "--font-sans",
|
||||
sample: "Small labels, metadata, and supporting notes."
|
||||
},
|
||||
{
|
||||
name: "body",
|
||||
fontVar: "--text-base",
|
||||
lineHeightVar: "--leading-normal",
|
||||
familyVar: "--font-sans",
|
||||
sample: "Body copy stays warm, readable, and stable across themes."
|
||||
},
|
||||
{
|
||||
name: "lead",
|
||||
fontVar: "--text-xl",
|
||||
lineHeightVar: "--leading-loose",
|
||||
familyVar: "--font-sans",
|
||||
sample: "Lead text introduces a surface without becoming display copy."
|
||||
},
|
||||
{
|
||||
name: "display",
|
||||
fontVar: "--text-4xl",
|
||||
lineHeightVar: "--leading-tight",
|
||||
familyVar: "--font-display",
|
||||
sample: "Display text carries the editorial voice of the system."
|
||||
}
|
||||
] as const;
|
||||
|
||||
export const radiusTokens = [
|
||||
{ name: "xs", cssVar: "--radius-xs" },
|
||||
{ name: "sm", cssVar: "--radius-sm" },
|
||||
{ name: "md", cssVar: "--radius-md" },
|
||||
{ name: "lg", cssVar: "--radius-lg" },
|
||||
{ name: "xl", cssVar: "--radius-xl" },
|
||||
{ name: "full", cssVar: "--radius-full" }
|
||||
] as const;
|
||||
|
||||
export const shadowTokens = [
|
||||
{ name: "xs", cssVar: "--shadow-xs" },
|
||||
{ name: "sm", cssVar: "--shadow-sm" },
|
||||
{ name: "md", cssVar: "--shadow-md" },
|
||||
{ name: "lg", cssVar: "--shadow-lg" }
|
||||
] as const;
|
||||
|
||||
export const motionTokens = {
|
||||
durations: [
|
||||
{ name: "instant", cssVar: "--dur-instant" },
|
||||
{ name: "fast", cssVar: "--dur-fast" },
|
||||
{ name: "base", cssVar: "--dur-base" },
|
||||
{ name: "slow", cssVar: "--dur-slow" },
|
||||
{ name: "deliberate", cssVar: "--dur-deliberate" }
|
||||
],
|
||||
easings: [
|
||||
{ name: "standard", cssVar: "--ease-standard" },
|
||||
{ name: "emphasized", cssVar: "--ease-emphasized" },
|
||||
{ name: "exit", cssVar: "--ease-exit" }
|
||||
],
|
||||
distances: [
|
||||
{ name: "xs", cssVar: "--distance-xs" },
|
||||
{ name: "sm", cssVar: "--distance-sm" },
|
||||
{ name: "md", cssVar: "--distance-md" },
|
||||
{ name: "lg", cssVar: "--distance-lg" }
|
||||
],
|
||||
scales: [
|
||||
{ name: "press", cssVar: "--scale-press" },
|
||||
{ name: "hover", cssVar: "--scale-hover" },
|
||||
{ name: "pop", cssVar: "--scale-pop" }
|
||||
]
|
||||
} as const;
|
||||
|
||||
function getTargetElement(root?: HTMLElement) {
|
||||
if (root) {
|
||||
return root;
|
||||
}
|
||||
|
||||
if (typeof document === "undefined") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return document.documentElement;
|
||||
}
|
||||
|
||||
export function setTheme(theme: ThemeName, root?: HTMLElement) {
|
||||
const target = getTargetElement(root);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.dataset.theme = theme;
|
||||
}
|
||||
|
||||
export function setMotionMode(mode: MotionModeName, root?: HTMLElement) {
|
||||
const target = getTargetElement(root);
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === "system") {
|
||||
delete target.dataset.motion;
|
||||
return;
|
||||
}
|
||||
|
||||
target.dataset.motion = mode;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
:root {
|
||||
--dur-instant: 1ms;
|
||||
--dur-fast: 120ms;
|
||||
--dur-base: 200ms;
|
||||
--dur-slow: 320ms;
|
||||
--dur-deliberate: 460ms;
|
||||
|
||||
--ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--ease-emphasized: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
|
||||
|
||||
--distance-xs: 4px;
|
||||
--distance-sm: 8px;
|
||||
--distance-md: 16px;
|
||||
--distance-lg: 24px;
|
||||
|
||||
--scale-press: 0.98;
|
||||
--scale-hover: 1.01;
|
||||
--scale-pop: 1.02;
|
||||
}
|
||||
|
||||
:root[data-motion="reduced"] {
|
||||
--dur-instant: 1ms;
|
||||
--dur-fast: 1ms;
|
||||
--dur-base: 1ms;
|
||||
--dur-slow: 1ms;
|
||||
--dur-deliberate: 1ms;
|
||||
--distance-xs: 0px;
|
||||
--distance-sm: 0px;
|
||||
--distance-md: 0px;
|
||||
--distance-lg: 0px;
|
||||
--scale-press: 1;
|
||||
--scale-hover: 1;
|
||||
--scale-pop: 1;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root:not([data-motion="full"]) {
|
||||
--dur-instant: 1ms;
|
||||
--dur-fast: 1ms;
|
||||
--dur-base: 1ms;
|
||||
--dur-slow: 1ms;
|
||||
--dur-deliberate: 1ms;
|
||||
--distance-xs: 0px;
|
||||
--distance-sm: 0px;
|
||||
--distance-md: 0px;
|
||||
--distance-lg: 0px;
|
||||
--scale-press: 1;
|
||||
--scale-hover: 1;
|
||||
--scale-pop: 1;
|
||||
}
|
||||
|
||||
:root {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
:root:not([data-motion="full"]) *,
|
||||
:root:not([data-motion="full"]) *::before,
|
||||
:root:not([data-motion="full"]) *::after {
|
||||
animation-duration: 1ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
scroll-behavior: auto !important;
|
||||
transition-duration: 1ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes aiui-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes aiui-fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes aiui-slide-up-sm {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(var(--distance-sm));
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes aiui-slide-down-sm {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(calc(var(--distance-sm) * -1));
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes aiui-skeleton-shimmer {
|
||||
from {
|
||||
transform: translateX(-120%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(120%);
|
||||
}
|
||||
}
|
||||
|
||||
.motion-transition {
|
||||
transition-duration: var(--dur-base);
|
||||
transition-property: color, background-color, border-color, box-shadow, opacity,
|
||||
transform;
|
||||
transition-timing-function: var(--ease-standard);
|
||||
}
|
||||
|
||||
.motion-pressable {
|
||||
transition-duration: var(--dur-fast);
|
||||
transition-property: color, background-color, border-color, box-shadow, transform;
|
||||
transition-timing-function: var(--ease-standard);
|
||||
}
|
||||
|
||||
.motion-pressable:hover {
|
||||
transform: translateY(calc(var(--distance-xs) * -0.25)) scale(var(--scale-hover));
|
||||
}
|
||||
|
||||
.motion-pressable:active {
|
||||
transform: scale(var(--scale-press));
|
||||
}
|
||||
|
||||
.motion-enter-fade {
|
||||
animation: aiui-fade-in var(--dur-base) var(--ease-standard) both;
|
||||
}
|
||||
|
||||
.motion-enter-rise {
|
||||
animation: aiui-slide-up-sm var(--dur-slow) var(--ease-emphasized) both;
|
||||
}
|
||||
|
||||
.motion-overlay-enter {
|
||||
animation: aiui-fade-in var(--dur-fast) var(--ease-standard) both;
|
||||
}
|
||||
|
||||
.motion-overlay-exit {
|
||||
animation: aiui-fade-out var(--dur-fast) var(--ease-exit) both;
|
||||
}
|
||||
|
||||
.motion-exit-fade {
|
||||
animation: aiui-fade-out var(--dur-fast) var(--ease-exit) both;
|
||||
}
|
||||
|
||||
.motion-exit-drop {
|
||||
animation: aiui-slide-down-sm calc(var(--dur-fast) * 0.9) var(--ease-exit) reverse
|
||||
both;
|
||||
}
|
||||
|
||||
.motion-ring {
|
||||
transition-duration: var(--dur-fast);
|
||||
transition-property: box-shadow, outline-color, border-color;
|
||||
transition-timing-function: var(--ease-standard);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "./base.css";
|
||||
@import "./tokens.css";
|
||||
@import "./motion.css";
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
:root {
|
||||
--font-sans: "Avenir Next", "Segoe UI", sans-serif;
|
||||
--font-display: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia,
|
||||
serif;
|
||||
--font-mono: "SF Mono", "SFMono-Regular", "Consolas", monospace;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 2rem;
|
||||
--text-4xl: clamp(2.5rem, 4vw, 4rem);
|
||||
|
||||
--leading-tight: 1.1;
|
||||
--leading-snug: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-loose: 1.7;
|
||||
|
||||
--tracking-tight: -0.03em;
|
||||
--tracking-normal: 0;
|
||||
--tracking-caps: 0.18em;
|
||||
|
||||
--border-width-thin: 1px;
|
||||
--border-width-strong: 1.5px;
|
||||
|
||||
--radius-xs: 8px;
|
||||
--radius-sm: 12px;
|
||||
--radius-md: 18px;
|
||||
--radius-lg: 28px;
|
||||
--radius-xl: 40px;
|
||||
--radius-full: 999px;
|
||||
|
||||
--shadow-xs: 0 1px 2px oklch(0.28 0.02 55 / 0.06);
|
||||
--shadow-sm: 0 8px 24px oklch(0.28 0.02 55 / 0.08);
|
||||
--shadow-md: 0 18px 48px oklch(0.28 0.03 55 / 0.12);
|
||||
--shadow-lg: 0 32px 72px oklch(0.2 0.02 55 / 0.16);
|
||||
}
|
||||
|
||||
:root,
|
||||
[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
--color-background: oklch(0.985 0.004 85);
|
||||
--color-foreground: oklch(0.24 0.03 60);
|
||||
--color-surface: oklch(0.965 0.008 80);
|
||||
--color-surface-strong: oklch(0.93 0.012 78);
|
||||
--color-surface-contrast: oklch(0.28 0.028 58);
|
||||
--color-border: oklch(0.87 0.01 75);
|
||||
--color-border-strong: oklch(0.72 0.018 68);
|
||||
--color-input: var(--color-border);
|
||||
--color-ring: oklch(0.56 0.12 32);
|
||||
--color-primary: oklch(0.53 0.15 30);
|
||||
--color-primary-foreground: oklch(0.98 0.01 80);
|
||||
--color-secondary: oklch(0.9 0.02 74);
|
||||
--color-secondary-foreground: oklch(0.26 0.024 60);
|
||||
--color-muted: oklch(0.94 0.008 78);
|
||||
--color-muted-foreground: oklch(0.42 0.028 60);
|
||||
--color-accent: oklch(0.76 0.1 82);
|
||||
--color-accent-foreground: oklch(0.24 0.03 60);
|
||||
--color-success: oklch(0.58 0.12 152);
|
||||
--color-success-foreground: oklch(0.97 0.01 155);
|
||||
--color-warning: oklch(0.74 0.12 80);
|
||||
--color-warning-foreground: oklch(0.22 0.02 64);
|
||||
--color-destructive: oklch(0.51 0.18 28);
|
||||
--color-destructive-foreground: oklch(0.98 0.01 80);
|
||||
--color-card: color-mix(in oklch, var(--color-surface) 86%, white 14%);
|
||||
--color-card-foreground: var(--color-foreground);
|
||||
--color-overlay: oklch(0.12 0.01 40 / 0.48);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--color-background: oklch(0.2 0.015 60);
|
||||
--color-foreground: oklch(0.94 0.01 80);
|
||||
--color-surface: oklch(0.26 0.018 60);
|
||||
--color-surface-strong: oklch(0.31 0.018 60);
|
||||
--color-surface-contrast: oklch(0.93 0.012 78);
|
||||
--color-border: oklch(0.4 0.015 60);
|
||||
--color-border-strong: oklch(0.56 0.028 64);
|
||||
--color-input: var(--color-border);
|
||||
--color-ring: oklch(0.7 0.12 35);
|
||||
--color-primary: oklch(0.72 0.13 40);
|
||||
--color-primary-foreground: oklch(0.22 0.014 60);
|
||||
--color-secondary: oklch(0.39 0.035 66);
|
||||
--color-secondary-foreground: oklch(0.95 0.01 80);
|
||||
--color-muted: oklch(0.28 0.015 60);
|
||||
--color-muted-foreground: oklch(0.8 0.02 72);
|
||||
--color-accent: oklch(0.68 0.09 82);
|
||||
--color-accent-foreground: oklch(0.17 0.012 60);
|
||||
--color-success: oklch(0.7 0.12 152);
|
||||
--color-success-foreground: oklch(0.17 0.012 152);
|
||||
--color-warning: oklch(0.78 0.12 82);
|
||||
--color-warning-foreground: oklch(0.16 0.012 64);
|
||||
--color-destructive: oklch(0.68 0.16 28);
|
||||
--color-destructive-foreground: oklch(0.16 0.012 60);
|
||||
--color-card: color-mix(in oklch, var(--color-surface) 90%, black 10%);
|
||||
--color-card-foreground: var(--color-foreground);
|
||||
--color-overlay: oklch(0.05 0.01 50 / 0.72);
|
||||
}
|
||||
|
||||
[data-theme="brand"] {
|
||||
color-scheme: light;
|
||||
--color-background: oklch(0.972 0.016 172);
|
||||
--color-foreground: oklch(0.24 0.03 182);
|
||||
--color-surface: oklch(0.946 0.018 172);
|
||||
--color-surface-strong: oklch(0.91 0.024 172);
|
||||
--color-surface-contrast: oklch(0.29 0.034 182);
|
||||
--color-border: oklch(0.83 0.026 172);
|
||||
--color-border-strong: oklch(0.67 0.045 176);
|
||||
--color-input: var(--color-border);
|
||||
--color-ring: oklch(0.53 0.12 190);
|
||||
--color-primary: oklch(0.48 0.12 188);
|
||||
--color-primary-foreground: oklch(0.97 0.008 172);
|
||||
--color-secondary: oklch(0.82 0.066 156);
|
||||
--color-secondary-foreground: oklch(0.2 0.02 178);
|
||||
--color-muted: oklch(0.91 0.018 172);
|
||||
--color-muted-foreground: oklch(0.42 0.03 180);
|
||||
--color-accent: oklch(0.75 0.105 130);
|
||||
--color-accent-foreground: oklch(0.2 0.02 160);
|
||||
--color-success: oklch(0.6 0.12 155);
|
||||
--color-success-foreground: oklch(0.98 0.006 170);
|
||||
--color-warning: oklch(0.76 0.13 86);
|
||||
--color-warning-foreground: oklch(0.22 0.02 74);
|
||||
--color-destructive: oklch(0.53 0.16 30);
|
||||
--color-destructive-foreground: oklch(0.98 0.01 80);
|
||||
--color-card: color-mix(in oklch, var(--color-surface) 88%, white 12%);
|
||||
--color-card-foreground: var(--color-foreground);
|
||||
--color-overlay: oklch(0.13 0.015 185 / 0.5);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { App } from './app';
|
||||
import './cadence-ui/tokens/styles.css';
|
||||
import './styles.css';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
@@ -34,6 +34,7 @@ As of now:
|
||||
- the Phase 1 web-product skeleton is now in place, including root `pnpm` workspace files, a standalone React app under `apps/web`, an initial OpenAPI/events contract under `api/`, and a new `cmd/orchd` HTTP service backed by `internal/app`, `internal/query`, and `internal/httpapi`
|
||||
- `orchd` now serves a minimal read-only web API with `chi`, including `/health`, runs list/detail, run task list, blocked-task list, and thread detail endpoints backed by the existing SQLite state
|
||||
- HTTP tests now cover the initial read-only `orchd` slice, and the new frontend workspace builds successfully with `pnpm run web:build`
|
||||
- Phase 2 frontend work has now started by bootstrapping `apps/web` with copied-in `cadence-ui` tokens and foundational components for button, input, textarea, dialog, form, tabs, card, badge, and alert, with the shared token stylesheet loaded from the frontend entrypoint
|
||||
- a repo-local `scripts/package_skill_clis.sh` packaging flow now builds bundled skill CLI assets for `inbox`, `orch`, and `council-review`
|
||||
- `orch` now implements `run init/show`, `task add`, `dep add`, `ready`, `dispatch`, `reconcile`, `wait`, `blocked`, `answer`, `retry`, `reassign`, `cancel`, `cleanup`, and `status`
|
||||
- `orch` can create runs, gate tasks through dependencies, dispatch work through `inbox`, reconcile worker thread state back into task state, answer blocked tasks, retry or reassign work, cancel tasks or runs, clean attempt worktrees, and create per-attempt Git worktrees during strict dispatch
|
||||
@@ -92,8 +93,9 @@ Current implementation status:
|
||||
- `Milestone 6: Waiting Primitives` is complete
|
||||
- `Milestone 7: Council Review` is complete
|
||||
- `Milestone 8: Web Product Phase 1 Skeleton` is complete
|
||||
- `Milestone 9: Web Product Phase 2 UI Foundation` is in progress
|
||||
|
||||
The council review v1 surface is complete, and the first web-product skeleton now exists as a separate monorepo workspace plus read-only HTTP backend slice.
|
||||
The council review v1 surface is complete, the first web-product skeleton now exists as a separate monorepo workspace plus read-only HTTP backend slice, and Phase 2 frontend work has started on top of the internal Cadence UI component library.
|
||||
|
||||
### Milestone 1: Go Skeleton
|
||||
|
||||
@@ -414,16 +416,51 @@ Remaining:
|
||||
|
||||
- Phase 2 should turn the frontend shell into actual run, task-board, blocked-queue, and thread-detail pages using the new HTTP contract
|
||||
|
||||
### Milestone 9: Web Product Phase 2 UI Foundation
|
||||
|
||||
Goal:
|
||||
|
||||
- bootstrap the frontend UI layer on top of the Phase 1 shell and read-only backend contract
|
||||
|
||||
Add:
|
||||
|
||||
- copied-in `cadence-ui` primitives and token CSS under `apps/web/src/cadence-ui`
|
||||
- app-wide token style wiring in the frontend entrypoint
|
||||
- any additional component installs needed as real screens land
|
||||
|
||||
Definition of done:
|
||||
|
||||
- `apps/web` imports the shared Cadence token stylesheet from the frontend entrypoint
|
||||
- the initial shared component set builds successfully inside the workspace
|
||||
- future web screens can compose from `cadence-ui` primitives instead of raw one-off HTML controls
|
||||
|
||||
Status:
|
||||
|
||||
- in progress
|
||||
|
||||
Completed so far:
|
||||
|
||||
- `apps/web/src/cadence-ui/` now contains copied-in Cadence UI tokens plus foundational components for button, input, textarea, dialog, form, tabs, card, badge, and alert
|
||||
- `apps/web/package.json` now includes the required Radix, `react-hook-form`, `motion`, and utility dependencies for the copied-in components
|
||||
- `apps/web/src/main.tsx` now imports `./cadence-ui/tokens/styles.css`
|
||||
- `pnpm install` refreshed the workspace lockfile, and `pnpm run web:build` succeeds with the copied-in component slice
|
||||
|
||||
Remaining:
|
||||
|
||||
- build the actual runs list, run detail, blocked queue, and thread timeline screens on top of the Cadence UI primitives
|
||||
- install additional `cadence-ui` components on demand as the product surface expands
|
||||
|
||||
## Immediate Next Task
|
||||
|
||||
If a new agent is taking over now, the next concrete step should be:
|
||||
|
||||
1. treat `Milestone 8: Web Product Phase 1 Skeleton` as complete unless a new user request reopens the backend skeleton itself
|
||||
2. keep the authored inbox test-plan set in `docs/tests/inbox/` synchronized if future `orch` or web work changes shared CLI-visible behavior
|
||||
3. start `Phase 2: Read-Only Web UI` on top of the existing `apps/web` and `orchd` contract, beginning with runs list, run detail, blocked queue, and thread timeline views
|
||||
4. keep `api/openapi.yaml`, `api/events.md`, and `docs/web-product-monorepo.md` synchronized as the web surface expands
|
||||
3. continue `Milestone 9: Web Product Phase 2 UI Foundation` by implementing the first runs list, run detail, blocked queue, and thread timeline screens on top of the existing `apps/web` and `orchd` contract
|
||||
4. install additional `cadence-ui` components on demand when those screens need them, instead of reintroducing bespoke primitives into `apps/web`
|
||||
5. keep `api/openapi.yaml`, `api/events.md`, and `docs/web-product-monorepo.md` synchronized as the web surface expands
|
||||
|
||||
The inbox implementation and its human-readable test-plan set are already in place, `orch` supports the main scheduler loop plus the complete council start/wait/tally/report workflow, and the web-product Phase 1 skeleton now exists, so the next step should be Phase 2 product surface work rather than reopening earlier milestones.
|
||||
The inbox implementation and its human-readable test-plan set are already in place, `orch` supports the main scheduler loop plus the complete council start/wait/tally/report workflow, and the web product is now past the bare frontend shell stage, so the next step should be actual Phase 2 product screens built on top of the Cadence UI foundation rather than reopening earlier milestones.
|
||||
|
||||
## Recommended Driver Choices
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Web Frontend Cadence UI Bootstrap
|
||||
|
||||
## Status
|
||||
|
||||
- `completed`
|
||||
|
||||
## Owner
|
||||
|
||||
- Codex
|
||||
|
||||
## Started At
|
||||
|
||||
- `2026-03-20`
|
||||
|
||||
## Goal
|
||||
|
||||
- Bootstrap the `apps/web` frontend with copied-in `cadence-ui` components and shared token styles so Phase 2 web UI work can build on the project component library instead of the bare Vite scaffold.
|
||||
|
||||
## Scope
|
||||
|
||||
- create an execution trace for this frontend bootstrap slice
|
||||
- install the initial `cadence-ui` component set into `apps/web`
|
||||
- wire the Cadence token stylesheet into the frontend entrypoint
|
||||
- validate the frontend workspace build after the component copy-in
|
||||
- keep `docs/implementation-roadmap.md` synchronized with the new frontend state
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] create the active execution roadmap for the Cadence UI frontend bootstrap workstream
|
||||
- [x] install the initial `cadence-ui` component set into `apps/web`
|
||||
- [x] wire `apps/web/src/main.tsx` to load `cadence-ui` token styles
|
||||
- [x] validate the frontend workspace build and resolve any dependency gaps
|
||||
- [x] update `docs/implementation-roadmap.md`
|
||||
- [x] archive this execution roadmap with a completion summary if the slice is fully complete
|
||||
|
||||
## Files
|
||||
|
||||
- `docs/roadmaps/archive/web-frontend-cadence-ui-bootstrap.md`
|
||||
- `docs/implementation-roadmap.md`
|
||||
- `apps/web/package.json`
|
||||
- `apps/web/src/main.tsx`
|
||||
- `apps/web/src/cadence-ui/`
|
||||
- `pnpm-lock.yaml`
|
||||
|
||||
## Decisions
|
||||
|
||||
- start Phase 2 frontend work by copying in the shared component library instead of building bespoke primitives inside `apps/web`
|
||||
|
||||
## Blockers
|
||||
|
||||
- none
|
||||
|
||||
## Next Step
|
||||
|
||||
- build the first Phase 2 product screens in `apps/web` on top of the copied-in Cadence UI primitives, installing additional registry components only when those screens need them
|
||||
|
||||
## Completion Summary
|
||||
|
||||
- ran the `cadence-ui` registry installer from `/Users/xd/project/cadence-ui` and copied the initial component set into `apps/web/src/cadence-ui`, including shared tokens plus button, input, textarea, dialog, form, tabs, card, badge, and alert primitives
|
||||
- wired `apps/web/src/main.tsx` to import `./cadence-ui/tokens/styles.css` so the copied-in components share the library token base from app startup
|
||||
- installed the newly required frontend dependencies into the workspace, refreshed `pnpm-lock.yaml`, and verified the result with `pnpm run web:build`
|
||||
- synchronized `docs/implementation-roadmap.md` so Phase 2 frontend work now explicitly continues from the Cadence UI foundation
|
||||
Generated
+650
-2
@@ -10,18 +10,42 @@ importers:
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.91.2
|
||||
version: 5.91.2(react@19.2.4)
|
||||
'@tanstack/react-router':
|
||||
specifier: ^1.167.5
|
||||
version: 1.167.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
motion:
|
||||
specifier: ^12.38.0
|
||||
version: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
react-hook-form:
|
||||
specifier: ^7.71.2
|
||||
version: 7.71.2(react@19.2.4)
|
||||
tailwind-merge:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.2.14
|
||||
@@ -56,6 +80,234 @@ packages:
|
||||
'@oxc-project/types@0.120.0':
|
||||
resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==}
|
||||
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-collection@1.1.7':
|
||||
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2':
|
||||
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.1.2':
|
||||
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dialog@1.1.15':
|
||||
resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-direction@1.1.1':
|
||||
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11':
|
||||
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.3':
|
||||
resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7':
|
||||
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-id@1.1.1':
|
||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-portal@1.1.9':
|
||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.5':
|
||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3':
|
||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.11':
|
||||
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.3':
|
||||
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.4':
|
||||
resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tabs@1.1.13':
|
||||
resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.2':
|
||||
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.2':
|
||||
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.10':
|
||||
resolution: {integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -208,6 +460,17 @@ packages:
|
||||
babel-plugin-react-compiler:
|
||||
optional: true
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cookie-es@2.0.0:
|
||||
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
|
||||
|
||||
@@ -218,6 +481,9 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -227,11 +493,29 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
framer-motion@12.38.0:
|
||||
resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
get-nonce@1.0.1:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
isbot@5.1.36:
|
||||
resolution: {integrity: sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -306,6 +590,26 @@ packages:
|
||||
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
motion-dom@12.38.0:
|
||||
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
|
||||
|
||||
motion-utils@12.36.0:
|
||||
resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
|
||||
|
||||
motion@12.38.0:
|
||||
resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -327,6 +631,42 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.2.4
|
||||
|
||||
react-hook-form@7.71.2:
|
||||
resolution: {integrity: sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-remove-scroll-bar@2.3.8:
|
||||
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-remove-scroll@2.7.2:
|
||||
resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-style-singleton@2.2.3:
|
||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react@19.2.4:
|
||||
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -353,6 +693,9 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
tailwind-merge@3.5.0:
|
||||
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
|
||||
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
@@ -371,6 +714,26 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
use-callback-ref@1.3.3:
|
||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-sidecar@1.1.3:
|
||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-sync-external-store@1.6.0:
|
||||
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||
peerDependencies:
|
||||
@@ -446,6 +809,207 @@ snapshots:
|
||||
|
||||
'@oxc-project/types@0.120.0': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
aria-hidden: 1.2.6
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)':
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.10':
|
||||
optional: true
|
||||
|
||||
@@ -554,19 +1118,42 @@ snapshots:
|
||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||
vite: 8.0.1
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
cookie-es@2.0.0: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
motion-dom: 12.38.0
|
||||
motion-utils: 12.36.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
isbot@5.1.36: {}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
@@ -618,6 +1205,20 @@ snapshots:
|
||||
lightningcss-win32-arm64-msvc: 1.32.0
|
||||
lightningcss-win32-x64-msvc: 1.32.0
|
||||
|
||||
motion-dom@12.38.0:
|
||||
dependencies:
|
||||
motion-utils: 12.36.0
|
||||
|
||||
motion-utils@12.36.0: {}
|
||||
|
||||
motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
framer-motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -635,6 +1236,37 @@ snapshots:
|
||||
react: 19.2.4
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-hook-form@7.71.2(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.4)
|
||||
react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4)
|
||||
tslib: 2.8.1
|
||||
use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4)
|
||||
use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
react: 19.2.4
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
react@19.2.4: {}
|
||||
|
||||
rolldown@1.0.0-rc.10:
|
||||
@@ -668,6 +1300,8 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
tailwind-merge@3.5.0: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tiny-warning@1.0.3: {}
|
||||
@@ -677,11 +1311,25 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
tslib@2.8.1: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
react: 19.2.4
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
use-sync-external-store@1.6.0(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
Reference in New Issue
Block a user