diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..eb6ead3 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v24.16.0 diff --git a/biome.json b/biome.json index 2e2770f..7595b9e 100644 --- a/biome.json +++ b/biome.json @@ -1,6 +1,6 @@ { "root": true, - "$schema": "https://biomejs.dev/schemas/2.3.15/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", "vcs": { "enabled": true, "clientKind": "git", diff --git a/full-stack/biome.json b/full-stack/biome.json index 0089060..e4e679f 100644 --- a/full-stack/biome.json +++ b/full-stack/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.15/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json", "root": false, "extends": "//", "files": { @@ -9,7 +9,7 @@ "**/.storybook/**/*", "**/.vscode/**/*", "**/index.html", - "**/vite.config.js", + "**/vite*.config.*", "!**/src/routeTree.gen.ts", "!**/src/_generated/**/*", "!**/src/components/ui/**/*" diff --git a/full-stack/components.json b/full-stack/components.json index 70ec1d7..ff47fe1 100644 --- a/full-stack/components.json +++ b/full-stack/components.json @@ -11,6 +11,7 @@ "prefix": "" }, "iconLibrary": "lucide", + "rtl": false, "aliases": { "components": "@/components", "utils": "@/lib/utils", @@ -21,4 +22,4 @@ "menuColor": "default", "menuAccent": "subtle", "registries": {} -} \ No newline at end of file +} diff --git a/full-stack/package.json b/full-stack/package.json index 3fa4d99..dbae0cd 100644 --- a/full-stack/package.json +++ b/full-stack/package.json @@ -17,115 +17,88 @@ "build-storybook": "storybook build" }, "dependencies": { - "@base-ui/react": "^1.2.0", - "@faker-js/faker": "^10.3.0", - "@fontsource-variable/geist": "^5.2.8", - "@hookform/resolvers": "^5.2.2", - "@modelcontextprotocol/sdk": "^1.26.0", - "@prisma/studio-core": "^0.14.0", - "@radix-ui/react-accordion": "^1.2.12", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-aspect-ratio": "^1.1.8", - "@radix-ui/react-avatar": "^1.1.11", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-context-menu": "^2.2.16", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-menubar": "^1.1.16", - "@radix-ui/react-navigation-menu": "^1.2.14", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-toggle": "^1.1.10", - "@radix-ui/react-toggle-group": "^1.1.11", - "@radix-ui/react-tooltip": "^1.2.8", - "@storybook/react-vite": "^10.2.8", - "@t3-oss/env-core": "^0.13.10", - "@tailwindcss/postcss": "^4.1.18", - "@tailwindcss/vite": "^4.1.18", - "@tanstack/db": "^0.5.26", + "@base-ui/react": "^1.5.0", + "@faker-js/faker": "^10.4.0", + "@fontsource-variable/geist": "^5.2.9", + "@modelcontextprotocol/sdk": "^1.29.0", + "@prisma/studio-core": "^0.30.0", + "@storybook/react-vite": "^10.4.2", + "@t3-oss/env-core": "^0.13.11", + "@tailwindcss/postcss": "^4.3.0", + "@tailwindcss/vite": "^4.3.0", + "@tanstack/db": "^0.6.8", "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/query-db-collection": "^1.0.23", - "@tanstack/react-db": "^0.1.70", - "@tanstack/react-form": "^1.28.2", - "@tanstack/react-query": "^5.90.21", - "@tanstack/react-query-devtools": "^5.91.3", - "@tanstack/react-router": "^1.159.10", - "@tanstack/react-router-devtools": "^1.159.10", - "@tanstack/react-router-ssr-query": "^1.159.10", - "@tanstack/react-start": "^1.159.12", - "@tanstack/react-store": "^0.8.0", + "@tanstack/query-db-collection": "^1.0.39", + "@tanstack/react-db": "^0.1.86", + "@tanstack/react-form": "^1.33.0", + "@tanstack/react-query": "^5.101.0", + "@tanstack/react-query-devtools": "^5.101.0", + "@tanstack/react-router": "^1.170.15", + "@tanstack/react-router-devtools": "^1.167.0", + "@tanstack/react-router-ssr-query": "^1.167.1", + "@tanstack/react-start": "^1.168.25", + "@tanstack/react-store": "^0.11.0", "@tanstack/react-table": "^8.21.3", - "@tanstack/router-plugin": "^1.159.12", - "@tanstack/store": "^0.8.0", - "@trpc/client": "^11.10.0", - "@trpc/server": "^11.10.0", - "@trpc/tanstack-react-query": "^11.10.0", + "@tanstack/router-plugin": "^1.168.18", + "@tanstack/store": "^0.11.0", + "@trpc/client": "^11.17.0", + "@trpc/server": "^11.17.0", + "@trpc/tanstack-react-query": "^11.17.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", - "date-fns": "^4.1.0", + "date-fns": "^4.4.0", "embla-carousel-react": "^8.6.0", "http-errors": "^2.0.1", "input-otp": "^1.4.2", "jsonwebtoken": "^9.0.3", - "lucide-react": "^0.564.0", + "lucide-react": "^1.17.0", "next-themes": "^0.4.6", "nitro": "latest", - "pg": "^8.18.0", - "postcss": "^8.5.6", - "radix-ui": "^1.4.3", - "react": "^19.2.4", - "react-day-picker": "^9.13.2", - "react-dom": "^19.2.4", - "react-hook-form": "^7.71.1", - "react-resizable-panels": "^4.6.4", - "recharts": "^2.15.4", + "pg": "^8.21.0", + "postcss": "^8.5.15", + "radix-ui": "^1.5.0", + "react": "^19.2.7", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.7", + "react-resizable-panels": "^4.11.2", + "recharts": "3.8.1", "reflect-metadata": "^0.2.2", "sonner": "^2.0.7", - "storybook": "^10.2.8", + "storybook": "^10.4.2", "superjson": "^2.2.6", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^4.1.18", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.0", "tsyringe": "^4.10.0", "tw-animate-css": "^1.4.0", "vaul": "^1.1.2", - "vite-tsconfig-paths": "^6.1.1", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { - "@tanstack/devtools-event-client": "^0.4.0", - "@tanstack/devtools-vite": "^0.5.1", - "@tanstack/react-devtools": "^0.9.5", + "@rolldown/plugin-babel": "^0.2.3", + "@tailwindcss/typography": "^0.5.20", + "@tanstack/devtools-event-client": "^0.4.3", + "@tanstack/devtools-vite": "^0.7.0", + "@tanstack/react-devtools": "^0.10.5", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", "@types/http-errors": "^2.0.5", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^22.19.11", - "@types/pg": "^8.16.0", - "@types/react": "^19.2.14", + "@types/node": "^25.9.2", + "@types/pg": "^8.20.0", + "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.4", - "@vitest/coverage-v8": "^4.0.18", + "@vitejs/plugin-react": "^6.0.2", + "@vitest/coverage-v8": "^4.1.8", "babel-plugin-react-compiler": "^1.0.0", "dotenv-cli": "^11.0.0", - "jsdom": "^28.0.0", - "react-doctor": "^0.0.28", - "shadcn": "^3.8.4", + "jsdom": "^29.1.1", + "react-doctor": "^0.4.2", + "shadcn": "^4.11.0", "ts-node": "^10.9.2", - "typescript": "^5.9.3", - "vite": "^7.3.1", - "vitest": "^4.0.18", - "web-vitals": "^5.1.0" + "typescript": "^6.0.3", + "vite": "^8.0.16", + "vitest": "^4.1.8", + "web-vitals": "^5.3.0" } } diff --git a/full-stack/src/components/ui/accordion.tsx b/full-stack/src/components/ui/accordion.tsx index 8b3c257..615e15d 100644 --- a/full-stack/src/components/ui/accordion.tsx +++ b/full-stack/src/components/ui/accordion.tsx @@ -11,7 +11,10 @@ function Accordion({ return ( ) @@ -24,7 +27,7 @@ function AccordionItem({ return ( ) @@ -40,7 +43,7 @@ function AccordionTrigger({
diff --git a/full-stack/src/components/ui/alert-dialog.tsx b/full-stack/src/components/ui/alert-dialog.tsx index 7ea1322..ba42fed 100644 --- a/full-stack/src/components/ui/alert-dialog.tsx +++ b/full-stack/src/components/ui/alert-dialog.tsx @@ -33,7 +33,10 @@ function AlertDialogOverlay({ return ( ) @@ -53,7 +56,7 @@ function AlertDialogContent({ data-slot="alert-dialog-content" data-size={size} className={cn( - "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/5 gap-6 rounded-4xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", + "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl bg-popover p-6 text-popover-foreground ring-1 ring-foreground/5 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )} {...props} @@ -69,7 +72,10 @@ function AlertDialogHeader({ return (
) @@ -98,7 +104,10 @@ function AlertDialogMedia({ return (
) @@ -111,7 +120,10 @@ function AlertDialogTitle({ return ( ) @@ -124,7 +136,10 @@ function AlertDialogDescription({ return ( ) diff --git a/full-stack/src/components/ui/alert.tsx b/full-stack/src/components/ui/alert.tsx index 8068a50..2d8ecac 100644 --- a/full-stack/src/components/ui/alert.tsx +++ b/full-stack/src/components/ui/alert.tsx @@ -3,17 +3,21 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" -const alertVariants = cva("grid gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 w-full relative group/alert", { - variants: { - variant: { - default: "bg-card text-card-foreground", - destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", +const alertVariants = cva( + "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", + }, }, - }, - defaultVariants: { - variant: "default", - }, -}) + defaultVariants: { + variant: "default", + }, + } +) function Alert({ className, @@ -35,7 +39,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3", + "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", className )} {...props} @@ -51,7 +55,7 @@ function AlertDescription({
) { svg]:hidden", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", @@ -76,7 +76,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2", className)} + className={cn( + "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} {...props} /> ) diff --git a/full-stack/src/components/ui/badge.tsx b/full-stack/src/components/ui/badge.tsx index 5785da2..f428bc5 100644 --- a/full-stack/src/components/ui/badge.tsx +++ b/full-stack/src/components/ui/badge.tsx @@ -5,15 +5,19 @@ import { Slot } from "radix-ui" import { cn } from "@/lib/utils" const badgeVariants = cva( - "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge", + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", { variants: { variant: { default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", - secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", - destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20", - outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/30", - ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + secondary: + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border bg-input/30 text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", link: "text-primary underline-offset-4 hover:underline", }, }, diff --git a/full-stack/src/components/ui/breadcrumb.tsx b/full-stack/src/components/ui/breadcrumb.tsx index b4a8873..a48cc1f 100644 --- a/full-stack/src/components/ui/breadcrumb.tsx +++ b/full-stack/src/components/ui/breadcrumb.tsx @@ -20,7 +20,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
    ) { return (
  1. ) @@ -50,7 +50,7 @@ function BreadcrumbLink({ return ( ) @@ -63,7 +63,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) { role="link" aria-disabled="true" aria-current="page" - className={cn("text-foreground font-normal", className)} + className={cn("font-normal text-foreground", className)} {...props} /> ) @@ -99,7 +99,7 @@ function BreadcrumbEllipsis({ role="presentation" aria-hidden="true" className={cn( - "size-5 [&>svg]:size-4 flex items-center justify-center", + "flex size-5 items-center justify-center [&>svg]:size-4", className )} {...props} diff --git a/full-stack/src/components/ui/button-group.tsx b/full-stack/src/components/ui/button-group.tsx index fea57a9..e1c0f5b 100644 --- a/full-stack/src/components/ui/button-group.tsx +++ b/full-stack/src/components/ui/button-group.tsx @@ -5,14 +5,14 @@ import { cn } from "@/lib/utils" import { Separator } from "@/components/ui/separator" const buttonGroupVariants = cva( - "has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl flex w-fit items-stretch *:focus-visible:z-10 *:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", + "group/button-group flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-4xl [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", { variants: { orientation: { horizontal: - "[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl! [&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", + "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-4xl!", vertical: - "[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl! flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", + "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-4xl!", }, }, defaultVariants: { @@ -49,7 +49,7 @@ function ButtonGroupText({ return ( svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className @@ -46,30 +46,30 @@ function Calendar({ classNames={{ root: cn("w-fit", defaultClassNames.root), months: cn( - "flex gap-4 flex-col md:flex-row relative", + "relative flex flex-col gap-4 md:flex-row", defaultClassNames.months ), - month: cn("flex flex-col w-full gap-4", defaultClassNames.month), + month: cn("flex w-full flex-col gap-4", defaultClassNames.month), nav: cn( - "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", + "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", defaultClassNames.nav ), button_previous: cn( buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + "size-(--cell-size) p-0 select-none aria-disabled:opacity-50", defaultClassNames.button_previous ), button_next: cn( buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", + "size-(--cell-size) p-0 select-none aria-disabled:opacity-50", defaultClassNames.button_next ), month_caption: cn( - "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", + "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)", defaultClassNames.month_caption ), dropdowns: cn( - "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", + "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium", defaultClassNames.dropdowns ), dropdown_root: cn( @@ -77,49 +77,49 @@ function Calendar({ defaultClassNames.dropdown_root ), dropdown: cn( - "absolute bg-popover inset-0 opacity-0", + "absolute inset-0 bg-popover opacity-0", defaultClassNames.dropdown ), caption_label: cn( - "select-none font-medium", + "font-medium select-none", captionLayout === "label" ? "text-sm" - : "rounded-(--cell-radius) flex items-center gap-1 text-sm [&>svg]:text-muted-foreground [&>svg]:size-3.5", + : "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground", defaultClassNames.caption_label ), table: "w-full border-collapse", weekdays: cn("flex", defaultClassNames.weekdays), weekday: cn( - "text-muted-foreground rounded-(--cell-radius) flex-1 font-normal text-[0.8rem] select-none", + "flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none", defaultClassNames.weekday ), - week: cn("flex w-full mt-2", defaultClassNames.week), + week: cn("mt-2 flex w-full", defaultClassNames.week), week_number_header: cn( - "select-none w-(--cell-size)", + "w-(--cell-size) select-none", defaultClassNames.week_number_header ), week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", + "text-[0.8rem] text-muted-foreground select-none", defaultClassNames.week_number ), day: cn( - "relative w-full rounded-(--cell-radius) h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius) group/day aspect-square select-none", + "group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)", props.showWeekNumber ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)" : "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)", defaultClassNames.day ), range_start: cn( - "rounded-l-(--cell-radius) bg-muted relative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:right-0 z-0 isolate", + "relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted", defaultClassNames.range_start ), range_middle: cn("rounded-none", defaultClassNames.range_middle), range_end: cn( - "rounded-r-(--cell-radius) bg-muted relative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:left-0 z-0 isolate", + "relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted", defaultClassNames.range_end ), today: cn( - "bg-muted text-foreground rounded-(--cell-radius) data-[selected=true]:rounded-none", + "rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none", defaultClassNames.today ), outside: cn( @@ -210,7 +210,7 @@ function CalendarDayButton({ data-range-end={modifiers.range_end} data-range-middle={modifiers.range_middle} className={cn( - "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-foreground relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) [&>span]:text-xs [&>span]:opacity-70", + "relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className )} diff --git a/full-stack/src/components/ui/card.tsx b/full-stack/src/components/ui/card.tsx index 0cd2cac..a45acf4 100644 --- a/full-stack/src/components/ui/card.tsx +++ b/full-stack/src/components/ui/card.tsx @@ -1,10 +1,6 @@ import * as React from "react" import { cn } from "@/lib/utils" -import { Subtitle, SubtitleProps, Text, TextProps } from "@pds4/typography" - -// # changelog -// 1. CardTitle as Subtitle, CardDescription as Text function Card({ className, @@ -15,7 +11,10 @@ function Card({
    img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)} + className={cn( + "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-2xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + className + )} {...props} /> ) @@ -26,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
    ) { ) } -function CardTitle(props: SubtitleProps) { +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return ( - ) } -function CardDescription(props: TextProps) { +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return ( - ) @@ -70,7 +70,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) { return (
    ) @@ -80,7 +80,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return (
    ) diff --git a/full-stack/src/components/ui/carousel.tsx b/full-stack/src/components/ui/carousel.tsx index 4a51fe6..f8202e4 100644 --- a/full-stack/src/components/ui/carousel.tsx +++ b/full-stack/src/components/ui/carousel.tsx @@ -183,7 +183,7 @@ function CarouselPrevious({ variant={variant} size={size} className={cn( - "rounded-full absolute touch-manipulation", + "absolute touch-manipulation rounded-full", orientation === "horizontal" ? "top-1/2 -left-12 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90", @@ -213,7 +213,7 @@ function CarouselNext({ variant={variant} size={size} className={cn( - "rounded-full absolute touch-manipulation", + "absolute touch-manipulation rounded-full", orientation === "horizontal" ? "top-1/2 -right-12 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", diff --git a/full-stack/src/components/ui/chart.tsx b/full-stack/src/components/ui/chart.tsx index 5f31f72..7c2dc84 100644 --- a/full-stack/src/components/ui/chart.tsx +++ b/full-stack/src/components/ui/chart.tsx @@ -2,21 +2,26 @@ import * as React from "react" import * as RechartsPrimitive from "recharts" +import type { TooltipValueType } from "recharts" import { cn } from "@/lib/utils" // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const -export type ChartConfig = { - [k in string]: { +const INITIAL_DIMENSION = { width: 320, height: 200 } as const +type TooltipNameType = number | string + +export type ChartConfig = Record< + string, + { label?: React.ReactNode icon?: React.ComponentType } & ( | { color?: string; theme?: never } | { color?: never; theme: Record } ) -} +> type ChartContextProps = { config: ChartConfig @@ -39,15 +44,20 @@ function ChartContainer({ className, children, config, + initialDimension = INITIAL_DIMENSION, ...props }: React.ComponentProps<"div"> & { config: ChartConfig children: React.ComponentProps< typeof RechartsPrimitive.ResponsiveContainer >["children"] + initialDimension?: { + width: number + height: number + } }) { const uniqueId = React.useId() - const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}` return ( @@ -55,13 +65,15 @@ function ChartContainer({ data-slot="chart" data-chart={chartId} className={cn( - "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", + "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", className )} {...props} > - + {children}
    @@ -71,7 +83,7 @@ function ChartContainer({ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { const colorConfig = Object.entries(config).filter( - ([, config]) => config.theme || config.color + ([, config]) => config.theme ?? config.color ) if (!colorConfig.length) { @@ -88,7 +100,7 @@ ${prefix} [data-chart=${id}] { ${colorConfig .map(([key, itemConfig]) => { const color = - itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || + itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ?? itemConfig.color return color ? ` --color-${key}: ${color};` : null }) @@ -125,7 +137,13 @@ function ChartTooltipContent({ indicator?: "line" | "dot" | "dashed" nameKey?: string labelKey?: string - }) { + } & Omit< + RechartsPrimitive.DefaultTooltipContentProps< + TooltipValueType, + TooltipNameType + >, + "accessibilityLayer" + >) { const { config } = useChart() const tooltipLabel = React.useMemo(() => { @@ -134,11 +152,11 @@ function ChartTooltipContent({ } const [item] = payload - const key = `${labelKey || item?.dataKey || item?.name || "value"}` + const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}` const itemConfig = getPayloadConfigFromPayload(config, item, key) const value = !labelKey && typeof label === "string" - ? config[label as keyof typeof config]?.label || label + ? (config[label]?.label ?? label) : itemConfig?.label if (labelFormatter) { @@ -172,22 +190,25 @@ function ChartTooltipContent({ return (
    {!nestLabel ? tooltipLabel : null}
    {payload .filter((item) => item.type !== "none") .map((item, index) => { - const key = `${nameKey || item.name || item.dataKey || "value"}` + const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}` const itemConfig = getPayloadConfigFromPayload(config, item, key) - const indicatorColor = color || item.payload.fill || item.color + const indicatorColor = color ?? item.payload?.fill ?? item.color return (
    svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5", + "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center" )} > @@ -228,12 +249,14 @@ function ChartTooltipContent({
    {nestLabel ? tooltipLabel : null} - {itemConfig?.label || item.name} + {itemConfig?.label ?? item.name}
    - {item.value && ( - - {item.value.toLocaleString()} + {item.value != null && ( + + {typeof item.value === "number" + ? item.value.toLocaleString() + : String(item.value)} )}
    @@ -255,11 +278,10 @@ function ChartLegendContent({ payload, verticalAlign = "bottom", nameKey, -}: React.ComponentProps<"div"> & - Pick & { - hideIcon?: boolean - nameKey?: string - }) { +}: React.ComponentProps<"div"> & { + hideIcon?: boolean + nameKey?: string +} & RechartsPrimitive.DefaultLegendContentProps) { const { config } = useChart() if (!payload?.length) { @@ -276,15 +298,15 @@ function ChartLegendContent({ > {payload .filter((item) => item.type !== "none") - .map((item) => { - const key = `${nameKey || item.dataKey || "value"}` + .map((item, index) => { + const key = `${nameKey ?? item.dataKey ?? "value"}` const itemConfig = getPayloadConfigFromPayload(config, item, key) return (
    svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3" + "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground" )} > {itemConfig?.icon && !hideIcon ? ( @@ -338,9 +360,7 @@ function getPayloadConfigFromPayload( ] as string } - return configLabelKey in config - ? config[configLabelKey] - : config[key as keyof typeof config] + return configLabelKey in config ? config[configLabelKey] : config[key] } export { diff --git a/full-stack/src/components/ui/checkbox.tsx b/full-stack/src/components/ui/checkbox.tsx index a5695e3..578a222 100644 --- a/full-stack/src/components/ui/checkbox.tsx +++ b/full-stack/src/components/ui/checkbox.tsx @@ -12,14 +12,14 @@ function Checkbox({ diff --git a/full-stack/src/components/ui/combobox.tsx b/full-stack/src/components/ui/combobox.tsx index c0c4495..103fbc6 100644 --- a/full-stack/src/components/ui/combobox.tsx +++ b/full-stack/src/components/ui/combobox.tsx @@ -31,7 +31,7 @@ function ComboboxTrigger({ {...props} > {children} - + ) } @@ -112,7 +112,7 @@ function ComboboxContent({ @@ -125,7 +125,7 @@ function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { {children} } + render={ + + } > @@ -174,7 +176,7 @@ function ComboboxLabel({ return ( ) @@ -190,7 +192,10 @@ function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { return (