From e5e406e725fa48baf3c3852e09281bec97313ae4 Mon Sep 17 00:00:00 2001
From: Chris Shepherd <1379888+sheepsteak@users.noreply.github.com>
Date: Thu, 6 Aug 2026 11:26:06 +0100
Subject: [PATCH 1/3] feat: update to Next 16
---
nextjs-shadcn/.bolt/ignore | 3 +-
nextjs-shadcn/.bolt/prompt | 4 +
nextjs-shadcn/.eslintrc.json | 3 -
nextjs-shadcn/.gitignore | 18 +-
nextjs-shadcn/app/globals.css | 183 +-
nextjs-shadcn/app/layout.tsx | 40 +-
nextjs-shadcn/app/page.tsx | 13 +-
nextjs-shadcn/components.json | 11 +-
nextjs-shadcn/components/theme-provider.tsx | 21 +
nextjs-shadcn/components/ui/accordion.tsx | 118 +-
nextjs-shadcn/components/ui/alert-dialog.tsx | 264 +-
nextjs-shadcn/components/ui/alert.tsx | 93 +-
nextjs-shadcn/components/ui/aspect-ratio.tsx | 23 +-
nextjs-shadcn/components/ui/attachment.tsx | 207 +
nextjs-shadcn/components/ui/avatar.tsx | 147 +-
nextjs-shadcn/components/ui/badge.tsx | 46 +-
nextjs-shadcn/components/ui/breadcrumb.tsx | 179 +-
nextjs-shadcn/components/ui/bubble.tsx | 128 +
nextjs-shadcn/components/ui/button-group.tsx | 87 +
nextjs-shadcn/components/ui/button.tsx | 64 +-
nextjs-shadcn/components/ui/calendar.tsx | 244 +-
nextjs-shadcn/components/ui/card.tsx | 153 +-
nextjs-shadcn/components/ui/carousel.tsx | 250 +-
nextjs-shadcn/components/ui/chart.tsx | 310 +-
nextjs-shadcn/components/ui/checkbox.tsx | 42 +-
nextjs-shadcn/components/ui/collapsible.tsx | 18 +-
nextjs-shadcn/components/ui/combobox.tsx | 300 +
nextjs-shadcn/components/ui/command.tsx | 258 +-
nextjs-shadcn/components/ui/context-menu.tsx | 394 +-
nextjs-shadcn/components/ui/dialog.tsx | 221 +-
nextjs-shadcn/components/ui/direction.tsx | 6 +
nextjs-shadcn/components/ui/drawer.tsx | 290 +-
nextjs-shadcn/components/ui/dropdown-menu.tsx | 408 +-
nextjs-shadcn/components/ui/empty.tsx | 104 +
nextjs-shadcn/components/ui/field.tsx | 238 +
nextjs-shadcn/components/ui/form.tsx | 179 -
nextjs-shadcn/components/ui/hover-card.tsx | 62 +-
nextjs-shadcn/components/ui/input-group.tsx | 158 +
nextjs-shadcn/components/ui/input-otp.tsx | 99 +-
nextjs-shadcn/components/ui/input.tsx | 33 +-
nextjs-shadcn/components/ui/item.tsx | 201 +
nextjs-shadcn/components/ui/kbd.tsx | 26 +
nextjs-shadcn/components/ui/label.tsx | 30 +-
nextjs-shadcn/components/ui/marker.tsx | 71 +
nextjs-shadcn/components/ui/menubar.tsx | 464 +-
.../components/ui/message-scroller.tsx | 130 +
nextjs-shadcn/components/ui/message.tsx | 92 +
nextjs-shadcn/components/ui/native-select.tsx | 65 +
.../components/ui/navigation-menu.tsx | 249 +-
nextjs-shadcn/components/ui/pagination.tsx | 182 +-
nextjs-shadcn/components/ui/popover.tsx | 103 +-
nextjs-shadcn/components/ui/progress.tsx | 95 +-
nextjs-shadcn/components/ui/radio-group.tsx | 44 +-
nextjs-shadcn/components/ui/resizable.tsx | 65 +-
nextjs-shadcn/components/ui/scroll-area.tsx | 85 +-
nextjs-shadcn/components/ui/select.tsx | 304 +-
nextjs-shadcn/components/ui/separator.tsx | 30 +-
nextjs-shadcn/components/ui/sheet.tsx | 219 +-
nextjs-shadcn/components/ui/sidebar.tsx | 723 ++
nextjs-shadcn/components/ui/skeleton.tsx | 6 +-
nextjs-shadcn/components/ui/slider.tsx | 70 +-
nextjs-shadcn/components/ui/sonner.tsx | 31 -
nextjs-shadcn/components/ui/spinner.tsx | 16 +
nextjs-shadcn/components/ui/switch.tsx | 43 +-
nextjs-shadcn/components/ui/table.tsx | 187 +-
nextjs-shadcn/components/ui/tabs.tsx | 127 +-
nextjs-shadcn/components/ui/textarea.tsx | 30 +-
nextjs-shadcn/components/ui/toast.tsx | 326 +-
nextjs-shadcn/components/ui/toaster.tsx | 35 -
nextjs-shadcn/components/ui/toggle-group.tsx | 88 +-
nextjs-shadcn/components/ui/toggle.tsx | 42 +-
nextjs-shadcn/components/ui/tooltip.tsx | 76 +-
nextjs-shadcn/hooks/use-mobile.ts | 21 +
nextjs-shadcn/hooks/use-toast.ts | 191 -
nextjs-shadcn/netlify.toml | 2 +-
nextjs-shadcn/next.config.js | 9 -
nextjs-shadcn/next.config.ts | 9 +
nextjs-shadcn/package-lock.json | 9596 ++++++++---------
nextjs-shadcn/package.json | 91 +-
nextjs-shadcn/postcss.config.js | 6 -
nextjs-shadcn/postcss.config.mjs | 7 +
nextjs-shadcn/public/.gitkeep | 0
nextjs-shadcn/tailwind.config.ts | 90 -
nextjs-shadcn/tsconfig.json | 14 +-
84 files changed, 10988 insertions(+), 8722 deletions(-)
delete mode 100644 nextjs-shadcn/.eslintrc.json
create mode 100644 nextjs-shadcn/components/theme-provider.tsx
create mode 100644 nextjs-shadcn/components/ui/attachment.tsx
create mode 100644 nextjs-shadcn/components/ui/bubble.tsx
create mode 100644 nextjs-shadcn/components/ui/button-group.tsx
create mode 100644 nextjs-shadcn/components/ui/combobox.tsx
create mode 100644 nextjs-shadcn/components/ui/direction.tsx
create mode 100644 nextjs-shadcn/components/ui/empty.tsx
create mode 100644 nextjs-shadcn/components/ui/field.tsx
delete mode 100644 nextjs-shadcn/components/ui/form.tsx
create mode 100644 nextjs-shadcn/components/ui/input-group.tsx
create mode 100644 nextjs-shadcn/components/ui/item.tsx
create mode 100644 nextjs-shadcn/components/ui/kbd.tsx
create mode 100644 nextjs-shadcn/components/ui/marker.tsx
create mode 100644 nextjs-shadcn/components/ui/message-scroller.tsx
create mode 100644 nextjs-shadcn/components/ui/message.tsx
create mode 100644 nextjs-shadcn/components/ui/native-select.tsx
create mode 100644 nextjs-shadcn/components/ui/sidebar.tsx
delete mode 100644 nextjs-shadcn/components/ui/sonner.tsx
create mode 100644 nextjs-shadcn/components/ui/spinner.tsx
delete mode 100644 nextjs-shadcn/components/ui/toaster.tsx
create mode 100644 nextjs-shadcn/hooks/use-mobile.ts
delete mode 100644 nextjs-shadcn/hooks/use-toast.ts
delete mode 100644 nextjs-shadcn/next.config.js
create mode 100644 nextjs-shadcn/next.config.ts
delete mode 100644 nextjs-shadcn/postcss.config.js
create mode 100644 nextjs-shadcn/postcss.config.mjs
create mode 100644 nextjs-shadcn/public/.gitkeep
delete mode 100644 nextjs-shadcn/tailwind.config.ts
diff --git a/nextjs-shadcn/.bolt/ignore b/nextjs-shadcn/.bolt/ignore
index bbe3a15c..4828c31e 100644
--- a/nextjs-shadcn/.bolt/ignore
+++ b/nextjs-shadcn/.bolt/ignore
@@ -1,2 +1 @@
-components/ui/*
-hooks/use-toast.ts
+components/ui/*
\ No newline at end of file
diff --git a/nextjs-shadcn/.bolt/prompt b/nextjs-shadcn/.bolt/prompt
index 88d020b6..a7c7ad43 100644
--- a/nextjs-shadcn/.bolt/prompt
+++ b/nextjs-shadcn/.bolt/prompt
@@ -1,3 +1,7 @@
+# This is NOT the Next.js you know
+
+This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
+
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
When using client-side hooks (useState and useEffect) in a component that's being treated as a Server Component by Next.js, always add the "use client" directive at the top of the file.
diff --git a/nextjs-shadcn/.eslintrc.json b/nextjs-shadcn/.eslintrc.json
deleted file mode 100644
index bffb357a..00000000
--- a/nextjs-shadcn/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "next/core-web-vitals"
-}
diff --git a/nextjs-shadcn/.gitignore b/nextjs-shadcn/.gitignore
index 9b1913ec..c59e98a0 100644
--- a/nextjs-shadcn/.gitignore
+++ b/nextjs-shadcn/.gitignore
@@ -1,9 +1,12 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
# dependencies
/node_modules
/.pnp
-.pnp.js
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
# testing
/coverage
@@ -23,13 +26,10 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+pnpm-debug.log*
-# local env files
-.env
-.env*.local
-
-# vercel
-.vercel
+# env files (can opt-in for committing if needed)
+.env*
# typescript
*.tsbuildinfo
diff --git a/nextjs-shadcn/app/globals.css b/nextjs-shadcn/app/globals.css
index 20b1c1db..926ee9ba 100644
--- a/nextjs-shadcn/app/globals.css
+++ b/nextjs-shadcn/app/globals.css
@@ -1,82 +1,129 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import 'tailwindcss';
+@import 'tw-animate-css';
+@import 'shadcn/tailwind.css';
-:root {
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --font-heading: var(--font-sans);
+ --font-sans: var(--font-sans);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --color-foreground: var(--foreground);
+ --color-background: var(--background);
+ --radius-sm: calc(var(--radius) * 0.6);
+ --radius-md: calc(var(--radius) * 0.8);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) * 1.4);
+ --radius-2xl: calc(var(--radius) * 1.8);
+ --radius-3xl: calc(var(--radius) * 2.2);
+ --radius-4xl: calc(var(--radius) * 2.6);
}
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
- }
+:root {
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.87 0 0);
+ --chart-2: oklch(0.556 0 0);
+ --chart-3: oklch(0.439 0 0);
+ --chart-4: oklch(0.371 0 0);
+ --chart-5: oklch(0.269 0 0);
+ --radius: 0.625rem;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
}
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 0 0% 3.9%;
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary: 0 0% 9%;
- --primary-foreground: 0 0% 98%;
- --secondary: 0 0% 96.1%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 89.8%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
- --radius: 0.5rem;
- }
- .dark {
- --background: 0 0% 3.9%;
- --foreground: 0 0% 98%;
- --card: 0 0% 3.9%;
- --card-foreground: 0 0% 98%;
- --popover: 0 0% 3.9%;
- --popover-foreground: 0 0% 98%;
- --primary: 0 0% 98%;
- --primary-foreground: 0 0% 9%;
- --secondary: 0 0% 14.9%;
- --secondary-foreground: 0 0% 98%;
- --muted: 0 0% 14.9%;
- --muted-foreground: 0 0% 63.9%;
- --accent: 0 0% 14.9%;
- --accent-foreground: 0 0% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 14.9%;
- --input: 0 0% 14.9%;
- --ring: 0 0% 83.1%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%;
- }
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.87 0 0);
+ --chart-2: oklch(0.556 0 0);
+ --chart-3: oklch(0.439 0 0);
+ --chart-4: oklch(0.371 0 0);
+ --chart-5: oklch(0.269 0 0);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
- @apply border-border;
+ @apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
+ html {
+ @apply font-sans;
+ }
}
diff --git a/nextjs-shadcn/app/layout.tsx b/nextjs-shadcn/app/layout.tsx
index 9259b547..dc77b5cd 100644
--- a/nextjs-shadcn/app/layout.tsx
+++ b/nextjs-shadcn/app/layout.tsx
@@ -1,8 +1,18 @@
import './globals.css';
+import { Geist_Mono, Inter } from 'next/font/google';
+import { ThemeProvider } from '@/components/theme-provider';
+import { TooltipProvider } from '@/components/ui/tooltip';
+import { cn } from '@/lib/utils';
import type { Metadata } from 'next';
-import { Inter } from 'next/font/google';
+import { DirectionProvider } from '@/components/ui/direction';
+import { Toaster } from '@/components/ui/toast';
-const inter = Inter({ subsets: ['latin'] });
+const inter = Inter({ subsets: ['latin'], variable: '--font-sans' });
+
+const fontMono = Geist_Mono({
+ subsets: ['latin'],
+ variable: '--font-mono',
+});
export const metadata: Metadata = {
title: 'Create Next App',
@@ -26,12 +36,30 @@ export const metadata: Metadata = {
export default function RootLayout({
children,
-}: {
+}: Readonly<{
children: React.ReactNode;
-}) {
+}>) {
return (
-
-
{children}
+
+
+
+
+
+ {children}
+
+
+
+
+
);
}
diff --git a/nextjs-shadcn/app/page.tsx b/nextjs-shadcn/app/page.tsx
index 4228abd2..39dab6d8 100644
--- a/nextjs-shadcn/app/page.tsx
+++ b/nextjs-shadcn/app/page.tsx
@@ -1,14 +1,7 @@
-export default function Home() {
+export default function Page() {
return (
-
- Start prompting.
+
+
Start prompting (or editing) to see magic happen :)
);
}
diff --git a/nextjs-shadcn/components.json b/nextjs-shadcn/components.json
index c5974621..f382eb79 100644
--- a/nextjs-shadcn/components.json
+++ b/nextjs-shadcn/components.json
@@ -1,20 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
- "style": "default",
+ "style": "base-nova",
"rsc": true,
"tsx": true,
"tailwind": {
- "config": "tailwind.config.ts",
+ "config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
+ "iconLibrary": "lucide",
+ "rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
- }
+ },
+ "menuColor": "default",
+ "menuAccent": "subtle",
+ "registries": {}
}
diff --git a/nextjs-shadcn/components/theme-provider.tsx b/nextjs-shadcn/components/theme-provider.tsx
new file mode 100644
index 00000000..44f28c5b
--- /dev/null
+++ b/nextjs-shadcn/components/theme-provider.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+import { ThemeProvider as NextThemesProvider } from 'next-themes';
+import * as React from 'react';
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps
) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/nextjs-shadcn/components/ui/accordion.tsx b/nextjs-shadcn/components/ui/accordion.tsx
index 84bf2ebe..8f4042d5 100644
--- a/nextjs-shadcn/components/ui/accordion.tsx
+++ b/nextjs-shadcn/components/ui/accordion.tsx
@@ -1,58 +1,78 @@
-'use client';
-
-import * as React from 'react';
-import * as AccordionPrimitive from '@radix-ui/react-accordion';
-import { ChevronDown } from 'lucide-react';
+import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';
import { cn } from '@/lib/utils';
+import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
-const Accordion = AccordionPrimitive.Root;
-
-const AccordionItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AccordionItem.displayName = 'AccordionItem';
+function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
+ return (
+
+ );
+}
-const AccordionTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- svg]:rotate-180',
- className
- )}
+function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
+ return (
+
- {children}
-
-
-
-));
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
+ />
+ );
+}
-const AccordionContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- {children}
-
-));
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: AccordionPrimitive.Trigger.Props) {
+ return (
+
+
+ {children}
+
+
+
+
+ );
+}
-AccordionContent.displayName = AccordionPrimitive.Content.displayName;
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: AccordionPrimitive.Panel.Props) {
+ return (
+
+
+ {children}
+
+
+ );
+}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/nextjs-shadcn/components/ui/alert-dialog.tsx b/nextjs-shadcn/components/ui/alert-dialog.tsx
index 5cba559b..1d152826 100644
--- a/nextjs-shadcn/components/ui/alert-dialog.tsx
+++ b/nextjs-shadcn/components/ui/alert-dialog.tsx
@@ -1,141 +1,187 @@
'use client';
import * as React from 'react';
-import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
+import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';
import { cn } from '@/lib/utils';
-import { buttonVariants } from '@/components/ui/button';
+import { Button } from '@/components/ui/button';
-const AlertDialog = AlertDialogPrimitive.Root;
+function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
+ return ;
+}
-const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
+function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
+ return (
+
+ );
+}
-const AlertDialogPortal = AlertDialogPrimitive.Portal;
+function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
+ return (
+
+ );
+}
-const AlertDialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
+function AlertDialogOverlay({
+ className,
+ ...props
+}: AlertDialogPrimitive.Backdrop.Props) {
+ return (
+
+ );
+}
-const AlertDialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
+
+
+
+ );
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
-
-));
-AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
+ );
+}
-const AlertDialogHeader = ({
+function AlertDialogFooter({
className,
...props
-}: React.HTMLAttributes) => (
-
-);
-AlertDialogHeader.displayName = 'AlertDialogHeader';
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
-const AlertDialogFooter = ({
+function AlertDialogMedia({
className,
...props
-}: React.HTMLAttributes) => (
-
-);
-AlertDialogFooter.displayName = 'AlertDialogFooter';
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
-const AlertDialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
-const AlertDialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogDescription.displayName =
- AlertDialogPrimitive.Description.displayName;
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
-const AlertDialogAction = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
-const AlertDialogCancel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
+function AlertDialogCancel({
+ className,
+ variant = 'outline',
+ size = 'default',
+ ...props
+}: AlertDialogPrimitive.Close.Props &
+ Pick, 'variant' | 'size'>) {
+ return (
+ }
+ {...props}
+ />
+ );
+}
export {
AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
+ AlertDialogAction,
+ AlertDialogCancel,
AlertDialogContent,
- AlertDialogHeader,
+ AlertDialogDescription,
AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogMedia,
+ AlertDialogOverlay,
+ AlertDialogPortal,
AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
+ AlertDialogTrigger,
};
diff --git a/nextjs-shadcn/components/ui/alert.tsx b/nextjs-shadcn/components/ui/alert.tsx
index d2b59ccd..04f88b52 100644
--- a/nextjs-shadcn/components/ui/alert.tsx
+++ b/nextjs-shadcn/components/ui/alert.tsx
@@ -4,13 +4,13 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const alertVariants = cva(
- 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 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 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
- default: 'bg-background text-foreground',
+ default: 'bg-card text-card-foreground',
destructive:
- 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
+ 'bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',
},
},
defaultVariants: {
@@ -19,41 +19,58 @@ const alertVariants = cva(
}
);
-const Alert = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & VariantProps
->(({ className, variant, ...props }, ref) => (
-
-));
-Alert.displayName = 'Alert';
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ );
+}
-const AlertTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-AlertTitle.displayName = 'AlertTitle';
+function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+ svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground',
+ className
+ )}
+ {...props}
+ />
+ );
+}
-const AlertDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-AlertDescription.displayName = 'AlertDescription';
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
-export { Alert, AlertTitle, AlertDescription };
+function AlertAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export { Alert, AlertTitle, AlertDescription, AlertAction };
diff --git a/nextjs-shadcn/components/ui/aspect-ratio.tsx b/nextjs-shadcn/components/ui/aspect-ratio.tsx
index aaabffbc..95d97f8e 100644
--- a/nextjs-shadcn/components/ui/aspect-ratio.tsx
+++ b/nextjs-shadcn/components/ui/aspect-ratio.tsx
@@ -1,7 +1,22 @@
-'use client';
+import { cn } from '@/lib/utils';
-import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio';
-
-const AspectRatio = AspectRatioPrimitive.Root;
+function AspectRatio({
+ ratio,
+ className,
+ ...props
+}: React.ComponentProps<'div'> & { ratio: number }) {
+ return (
+
+ );
+}
export { AspectRatio };
diff --git a/nextjs-shadcn/components/ui/attachment.tsx b/nextjs-shadcn/components/ui/attachment.tsx
new file mode 100644
index 00000000..cfcaa0d9
--- /dev/null
+++ b/nextjs-shadcn/components/ui/attachment.tsx
@@ -0,0 +1,207 @@
+import * as React from 'react';
+import { mergeProps } from '@base-ui/react/merge-props';
+import { useRender } from '@base-ui/react/use-render';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+
+const attachmentVariants = cva(
+ 'group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed',
+ {
+ variants: {
+ size: {
+ default:
+ 'gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2',
+ sm: 'gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5',
+ xs: 'gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1',
+ },
+ orientation: {
+ horizontal: 'min-w-40 items-center',
+ vertical: 'w-24 flex-col has-data-[slot=attachment-content]:w-30',
+ },
+ },
+ }
+);
+
+function Attachment({
+ className,
+ state = 'done',
+ size = 'default',
+ orientation = 'horizontal',
+ ...props
+}: React.ComponentProps<'div'> &
+ VariantProps & {
+ state?: 'idle' | 'uploading' | 'processing' | 'error' | 'done';
+ }) {
+ return (
+
+ );
+}
+
+const attachmentMediaVariants = cva(
+ "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
+ {
+ variants: {
+ variant: {
+ icon: '',
+ image:
+ 'opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover',
+ },
+ },
+ defaultVariants: {
+ variant: 'icon',
+ },
+ }
+);
+
+function AttachmentMedia({
+ className,
+ variant = 'icon',
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ );
+}
+
+function AttachmentContent({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function AttachmentTitle({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+
+ );
+}
+
+function AttachmentDescription({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+
+ );
+}
+
+function AttachmentActions({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function AttachmentAction({
+ className,
+ variant,
+ size = 'icon-xs',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AttachmentTrigger({
+ className,
+ render,
+ type,
+ ...props
+}: useRender.ComponentProps<'button'>) {
+ return useRender({
+ defaultTagName: 'button',
+ props: mergeProps<'button'>(
+ {
+ type: render ? type : type ?? 'button',
+ className: cn('absolute inset-0 z-10 outline-none', className),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: 'attachment-trigger',
+ },
+ });
+}
+
+function AttachmentGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export {
+ Attachment,
+ AttachmentGroup,
+ AttachmentMedia,
+ AttachmentContent,
+ AttachmentTitle,
+ AttachmentDescription,
+ AttachmentActions,
+ AttachmentAction,
+ AttachmentTrigger,
+};
diff --git a/nextjs-shadcn/components/ui/avatar.tsx b/nextjs-shadcn/components/ui/avatar.tsx
index 1346957c..ac3040ba 100644
--- a/nextjs-shadcn/components/ui/avatar.tsx
+++ b/nextjs-shadcn/components/ui/avatar.tsx
@@ -1,50 +1,109 @@
'use client';
import * as React from 'react';
-import * as AvatarPrimitive from '@radix-ui/react-avatar';
+import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';
import { cn } from '@/lib/utils';
-const Avatar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-Avatar.displayName = AvatarPrimitive.Root.displayName;
-
-const AvatarImage = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AvatarImage.displayName = AvatarPrimitive.Image.displayName;
-
-const AvatarFallback = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
-
-export { Avatar, AvatarImage, AvatarFallback };
+function Avatar({
+ className,
+ size = 'default',
+ ...props
+}: AvatarPrimitive.Root.Props & {
+ size?: 'default' | 'sm' | 'lg';
+}) {
+ return (
+
+ );
+}
+
+function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: AvatarPrimitive.Fallback.Props) {
+ return (
+
+ );
+}
+
+function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+ 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',
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function AvatarGroupCount({
+ className,
+ ...props
+}: React.ComponentProps<'div'>) {
+ return (
+ 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}
+ />
+ );
+}
+
+export {
+ Avatar,
+ AvatarImage,
+ AvatarFallback,
+ AvatarGroup,
+ AvatarGroupCount,
+ AvatarBadge,
+};
diff --git a/nextjs-shadcn/components/ui/badge.tsx b/nextjs-shadcn/components/ui/badge.tsx
index 2eb790a6..f3244591 100644
--- a/nextjs-shadcn/components/ui/badge.tsx
+++ b/nextjs-shadcn/components/ui/badge.tsx
@@ -1,20 +1,24 @@
-import * as React from 'react';
+import { mergeProps } from '@base-ui/react/merge-props';
+import { useRender } from '@base-ui/react/use-render';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const badgeVariants = cva(
- 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
+ '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:
- 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
+ default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
secondary:
- 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
destructive:
- 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
- outline: 'text-foreground',
+ '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 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',
},
},
defaultVariants: {
@@ -23,14 +27,26 @@ const badgeVariants = cva(
}
);
-export interface BadgeProps
- extends React.HTMLAttributes
,
- VariantProps {}
-
-function Badge({ className, variant, ...props }: BadgeProps) {
- return (
-
- );
+function Badge({
+ className,
+ variant = 'default',
+ render,
+ ...props
+}: useRender.ComponentProps<'span'> & VariantProps) {
+ return useRender({
+ defaultTagName: 'span',
+ props: mergeProps<'span'>(
+ {
+ className: cn(badgeVariants({ variant }), className),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: 'badge',
+ variant,
+ },
+ });
}
export { Badge, badgeVariants };
diff --git a/nextjs-shadcn/components/ui/breadcrumb.tsx b/nextjs-shadcn/components/ui/breadcrumb.tsx
index 8b621973..f6fefd3e 100644
--- a/nextjs-shadcn/components/ui/breadcrumb.tsx
+++ b/nextjs-shadcn/components/ui/breadcrumb.tsx
@@ -1,108 +1,115 @@
import * as React from 'react';
-import { Slot } from '@radix-ui/react-slot';
-import { ChevronRight, MoreHorizontal } from 'lucide-react';
+import { mergeProps } from '@base-ui/react/merge-props';
+import { useRender } from '@base-ui/react/use-render';
import { cn } from '@/lib/utils';
+import { ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react';
-const Breadcrumb = React.forwardRef<
- HTMLElement,
- React.ComponentPropsWithoutRef<'nav'> & {
- separator?: React.ReactNode;
- }
->(({ ...props }, ref) => );
-Breadcrumb.displayName = 'Breadcrumb';
+function Breadcrumb({ className, ...props }: React.ComponentProps<'nav'>) {
+ return (
+
+ );
+}
-const BreadcrumbList = React.forwardRef<
- HTMLOListElement,
- React.ComponentPropsWithoutRef<'ol'>
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbList.displayName = 'BreadcrumbList';
+function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
+ return (
+
+ );
+}
-const BreadcrumbItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentPropsWithoutRef<'li'>
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbItem.displayName = 'BreadcrumbItem';
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
-const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<'a'> & {
- asChild?: boolean;
- }
->(({ asChild, className, ...props }, ref) => {
- const Comp = asChild ? Slot : 'a';
+function BreadcrumbLink({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<'a'>) {
+ return useRender({
+ defaultTagName: 'a',
+ props: mergeProps<'a'>(
+ {
+ className: cn('transition-colors hover:text-foreground', className),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: 'breadcrumb-link',
+ },
+ });
+}
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
return (
-
);
-});
-BreadcrumbLink.displayName = 'BreadcrumbLink';
+}
-const BreadcrumbPage = React.forwardRef<
- HTMLSpanElement,
- React.ComponentPropsWithoutRef<'span'>
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbPage.displayName = 'BreadcrumbPage';
-
-const BreadcrumbSeparator = ({
+function BreadcrumbSeparator({
children,
className,
...props
-}: React.ComponentProps<'li'>) => (
- svg]:size-3.5', className)}
- {...props}
- >
- {children ?? }
-
-);
-BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
+}: React.ComponentProps<'li'>) {
+ return (
+ svg]:size-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
-const BreadcrumbEllipsis = ({
+function BreadcrumbEllipsis({
className,
...props
-}: React.ComponentProps<'span'>) => (
-
-
- More
-
-);
-BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis';
+}: React.ComponentProps<'span'>) {
+ return (
+ svg]:size-4',
+ className
+ )}
+ {...props}
+ >
+
+ More
+
+ );
+}
export {
Breadcrumb,
diff --git a/nextjs-shadcn/components/ui/bubble.tsx b/nextjs-shadcn/components/ui/bubble.tsx
new file mode 100644
index 00000000..2e28677a
--- /dev/null
+++ b/nextjs-shadcn/components/ui/bubble.tsx
@@ -0,0 +1,128 @@
+import * as React from 'react';
+import { mergeProps } from '@base-ui/react/merge-props';
+import { useRender } from '@base-ui/react/use-render';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+
+function BubbleGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+const bubbleVariants = cva(
+ 'group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full',
+ {
+ variants: {
+ variant: {
+ default:
+ '*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80',
+ secondary:
+ '*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]',
+ muted:
+ '*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]',
+ tinted:
+ '*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]',
+ outline:
+ '*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30',
+ ghost:
+ 'border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50',
+ destructive:
+ '*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ }
+);
+
+function Bubble({
+ variant = 'default',
+ align = 'start',
+ className,
+ ...props
+}: React.ComponentProps<'div'> &
+ VariantProps & {
+ align?: 'start' | 'end';
+ }) {
+ return (
+
+ );
+}
+
+function BubbleContent({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<'div'>) {
+ return useRender({
+ defaultTagName: 'div',
+ props: mergeProps<'div'>(
+ {
+ className: cn(
+ 'w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50',
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: 'bubble-content',
+ },
+ });
+}
+
+const bubbleReactionsVariants = cva(
+ 'absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0',
+ {
+ variants: {
+ side: {
+ top: 'top-0 -translate-y-3/4',
+ bottom: 'bottom-0 translate-y-3/4',
+ },
+ align: {
+ start: 'left-3',
+ end: 'right-3',
+ },
+ },
+ defaultVariants: {
+ side: 'bottom',
+ align: 'end',
+ },
+ }
+);
+
+function BubbleReactions({
+ side = 'bottom',
+ align = 'end',
+ className,
+ ...props
+}: React.ComponentProps<'div'> & {
+ align?: 'start' | 'end';
+ side?: 'top' | 'bottom';
+}) {
+ return (
+
+ );
+}
+
+export { BubbleGroup, Bubble, BubbleContent, BubbleReactions };
diff --git a/nextjs-shadcn/components/ui/button-group.tsx b/nextjs-shadcn/components/ui/button-group.tsx
new file mode 100644
index 00000000..f873c683
--- /dev/null
+++ b/nextjs-shadcn/components/ui/button-group.tsx
@@ -0,0 +1,87 @@
+import { mergeProps } from '@base-ui/react/merge-props';
+import { useRender } from '@base-ui/react/use-render';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+import { Separator } from '@/components/ui/separator';
+
+const buttonGroupVariants = cva(
+ "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-lg [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ '*:data-slot:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0',
+ vertical:
+ 'flex-col *:data-slot:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0',
+ },
+ },
+ defaultVariants: {
+ orientation: 'horizontal',
+ },
+ }
+);
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ );
+}
+
+function ButtonGroupText({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<'div'>) {
+ return useRender({
+ defaultTagName: 'div',
+ props: mergeProps<'div'>(
+ {
+ className: cn(
+ "flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
+ className
+ ),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: 'button-group-text',
+ },
+ });
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = 'vertical',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+};
diff --git a/nextjs-shadcn/components/ui/button.tsx b/nextjs-shadcn/components/ui/button.tsx
index 81e2e6ee..68a1e3ca 100644
--- a/nextjs-shadcn/components/ui/button.tsx
+++ b/nextjs-shadcn/components/ui/button.tsx
@@ -1,29 +1,36 @@
-import * as React from 'react';
-import { Slot } from '@radix-ui/react-slot';
+import { Button as ButtonPrimitive } from '@base-ui/react/button';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
- 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
+ "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{
variants: {
variant: {
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
- destructive:
- 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
+ default: 'bg-primary text-primary-foreground hover:bg-primary/80',
outline:
- 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
+ 'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
secondary:
- 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
- ghost: 'hover:bg-accent hover:text-accent-foreground',
+ 'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
+ ghost:
+ 'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',
+ destructive:
+ 'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
- default: 'h-10 px-4 py-2',
- sm: 'h-9 rounded-md px-3',
- lg: 'h-11 rounded-md px-8',
- icon: 'h-10 w-10',
+ default:
+ 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
+ lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
+ icon: 'size-8',
+ 'icon-xs':
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
+ 'icon-sm':
+ 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
+ 'icon-lg': 'size-9',
},
},
defaultVariants: {
@@ -33,24 +40,19 @@ const buttonVariants = cva(
}
);
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean;
+function Button({
+ className,
+ variant = 'default',
+ size = 'default',
+ ...props
+}: ButtonPrimitive.Props & VariantProps) {
+ return (
+
+ );
}
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : 'button';
- return (
-
- );
- }
-);
-Button.displayName = 'Button';
-
export { Button, buttonVariants };
diff --git a/nextjs-shadcn/components/ui/calendar.tsx b/nextjs-shadcn/components/ui/calendar.tsx
index 7ee4f822..9c382c82 100644
--- a/nextjs-shadcn/components/ui/calendar.tsx
+++ b/nextjs-shadcn/components/ui/calendar.tsx
@@ -1,66 +1,228 @@
'use client';
import * as React from 'react';
-import { ChevronLeft, ChevronRight } from 'lucide-react';
-import { DayPicker } from 'react-day-picker';
+import {
+ DayPicker,
+ getDefaultClassNames,
+ type DayButton,
+ type Locale,
+} from 'react-day-picker';
import { cn } from '@/lib/utils';
-import { buttonVariants } from '@/components/ui/button';
-
-export type CalendarProps = React.ComponentProps;
+import { Button, buttonVariants } from '@/components/ui/button';
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ ChevronDownIcon,
+} from 'lucide-react';
function Calendar({
className,
classNames,
showOutsideDays = true,
+ captionLayout = 'label',
+ buttonVariant = 'ghost',
+ locale,
+ formatters,
+ components,
...props
-}: CalendarProps) {
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps['variant'];
+}) {
+ const defaultClassNames = getDefaultClassNames();
+
return (
svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ locale={locale}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString(locale?.code, { month: 'short' }),
+ ...formatters,
+ }}
classNames={{
- months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
- month: 'space-y-4',
- caption: 'flex justify-center pt-1 relative items-center',
- caption_label: 'text-sm font-medium',
- nav: 'space-x-1 flex items-center',
- nav_button: cn(
- buttonVariants({ variant: 'outline' }),
- 'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
- ),
- nav_button_previous: 'absolute left-1',
- nav_button_next: 'absolute right-1',
- table: 'w-full border-collapse space-y-1',
- head_row: 'flex',
- head_cell:
- 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
- row: 'flex w-full mt-2',
- cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
+ root: cn('w-fit', defaultClassNames.root),
+ months: cn(
+ 'relative flex flex-col gap-4 md:flex-row',
+ defaultClassNames.months
+ ),
+ month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
+ nav: cn(
+ '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) p-0 select-none aria-disabled:opacity-50',
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ 'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ 'flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium',
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ 'relative rounded-(--cell-radius)',
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ 'absolute inset-0 bg-popover opacity-0',
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ 'font-medium select-none',
+ captionLayout === 'label'
+ ? 'text-sm'
+ : 'flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',
+ defaultClassNames.caption_label
+ ),
+ month_grid: cn('w-full border-collapse', defaultClassNames.month_grid),
+ weekdays: cn('flex', defaultClassNames.weekdays),
+ weekday: cn(
+ 'flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none',
+ defaultClassNames.weekday
+ ),
+ week: cn('mt-2 flex w-full', defaultClassNames.week),
+ week_number_header: cn(
+ 'w-(--cell-size) select-none',
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ 'text-[0.8rem] text-muted-foreground select-none',
+ defaultClassNames.week_number
+ ),
day: cn(
- buttonVariants({ variant: 'ghost' }),
- 'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
- ),
- day_range_end: 'day-range-end',
- day_selected:
- 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
- day_today: 'bg-accent text-accent-foreground',
- day_outside:
- 'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
- day_disabled: 'text-muted-foreground opacity-50',
- day_range_middle:
- 'aria-selected:bg-accent aria-selected:text-accent-foreground',
- day_hidden: 'invisible',
+ '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(
+ '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(
+ '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(
+ 'rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none',
+ defaultClassNames.today
+ ),
+ outside: cn(
+ 'text-muted-foreground aria-selected:text-muted-foreground',
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ 'text-muted-foreground opacity-50',
+ defaultClassNames.disabled
+ ),
+ hidden: cn('invisible', defaultClassNames.hidden),
...classNames,
}}
components={{
- IconLeft: ({ ...props }) => ,
- IconRight: ({ ...props }) => ,
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ );
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === 'left') {
+ return (
+
+ );
+ }
+
+ if (orientation === 'right') {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ },
+ DayButton: ({ ...props }) => (
+
+ ),
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+ |
+ );
+ },
+ ...components,
}}
{...props}
/>
);
}
-Calendar.displayName = 'Calendar';
-export { Calendar };
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ locale,
+ ...props
+}: React.ComponentProps & { locale?: Partial }) {
+ const defaultClassNames = getDefaultClassNames();
+
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus();
+ }, [modifiers.focused]);
+
+ return (
+