(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+ span]:text-xs [&>span]:opacity-70",
+ defaultClassNames.day,
+ className
+ )}
+ {...props}
+ />
+ )
+}
diff --git a/apps/v4/examples/base/card-demo.tsx b/apps/v4/examples/base/card-demo.tsx
new file mode 100644
index 0000000000..ad56445691
--- /dev/null
+++ b/apps/v4/examples/base/card-demo.tsx
@@ -0,0 +1,63 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Card,
+ CardAction,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/examples/base/ui/card"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+export default function CardDemo() {
+ return (
+
+
+ Login to your account
+
+ Enter your email below to login to your account
+
+
+ Sign Up
+
+
+
+
+
+
+
+ Login
+
+
+ Login with Google
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/carousel-api.tsx b/apps/v4/examples/base/carousel-api.tsx
new file mode 100644
index 0000000000..233d0c6e79
--- /dev/null
+++ b/apps/v4/examples/base/carousel-api.tsx
@@ -0,0 +1,54 @@
+"use client"
+
+import * as React from "react"
+import { Card, CardContent } from "@/examples/base/ui/card"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+ type CarouselApi,
+} from "@/examples/base/ui/carousel"
+
+export default function CarouselDApiDemo() {
+ const [api, setApi] = React.useState()
+ const [current, setCurrent] = React.useState(0)
+ const [count, setCount] = React.useState(0)
+
+ React.useEffect(() => {
+ if (!api) {
+ return
+ }
+
+ setCount(api.scrollSnapList().length)
+ setCurrent(api.selectedScrollSnap() + 1)
+
+ api.on("select", () => {
+ setCurrent(api.selectedScrollSnap() + 1)
+ })
+ }, [api])
+
+ return (
+
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+ {index + 1}
+
+
+
+ ))}
+
+
+
+
+
+ Slide {current} of {count}
+
+
+ )
+}
diff --git a/apps/v4/examples/base/carousel-demo.tsx b/apps/v4/examples/base/carousel-demo.tsx
new file mode 100644
index 0000000000..64a74f5cc7
--- /dev/null
+++ b/apps/v4/examples/base/carousel-demo.tsx
@@ -0,0 +1,31 @@
+import * as React from "react"
+import { Card, CardContent } from "@/examples/base/ui/card"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/examples/base/ui/carousel"
+
+export default function CarouselDemo() {
+ return (
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+
+ {index + 1}
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/carousel-orientation.tsx b/apps/v4/examples/base/carousel-orientation.tsx
new file mode 100644
index 0000000000..8fe8f809f1
--- /dev/null
+++ b/apps/v4/examples/base/carousel-orientation.tsx
@@ -0,0 +1,37 @@
+import * as React from "react"
+import { Card, CardContent } from "@/examples/base/ui/card"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/examples/base/ui/carousel"
+
+export default function CarouselOrientation() {
+ return (
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+
+ {index + 1}
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/carousel-plugin.tsx b/apps/v4/examples/base/carousel-plugin.tsx
new file mode 100644
index 0000000000..869af0cfec
--- /dev/null
+++ b/apps/v4/examples/base/carousel-plugin.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import * as React from "react"
+import { Card, CardContent } from "@/examples/base/ui/card"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/examples/base/ui/carousel"
+import Autoplay from "embla-carousel-autoplay"
+
+export default function CarouselPlugin() {
+ const plugin = React.useRef(
+ Autoplay({ delay: 2000, stopOnInteraction: true })
+ )
+
+ return (
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+
+ {index + 1}
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/carousel-size.tsx b/apps/v4/examples/base/carousel-size.tsx
new file mode 100644
index 0000000000..b62761e36c
--- /dev/null
+++ b/apps/v4/examples/base/carousel-size.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { Card, CardContent } from "@/examples/base/ui/card"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/examples/base/ui/carousel"
+
+export default function CarouselSize() {
+ return (
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+
+ {index + 1}
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/carousel-spacing.tsx b/apps/v4/examples/base/carousel-spacing.tsx
new file mode 100644
index 0000000000..7deb7f942c
--- /dev/null
+++ b/apps/v4/examples/base/carousel-spacing.tsx
@@ -0,0 +1,31 @@
+import * as React from "react"
+import { Card, CardContent } from "@/examples/base/ui/card"
+import {
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselNext,
+ CarouselPrevious,
+} from "@/examples/base/ui/carousel"
+
+export default function CarouselSpacing() {
+ return (
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+
+
+
+ {index + 1}
+
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/chart-bar-demo-axis.tsx b/apps/v4/examples/base/chart-bar-demo-axis.tsx
new file mode 100644
index 0000000000..5a725c93c7
--- /dev/null
+++ b/apps/v4/examples/base/chart-bar-demo-axis.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import { ChartContainer, type ChartConfig } from "@/examples/base/ui/chart"
+import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+
+const chartData = [
+ { month: "January", desktop: 186, mobile: 80 },
+ { month: "February", desktop: 305, mobile: 200 },
+ { month: "March", desktop: 237, mobile: 120 },
+ { month: "April", desktop: 73, mobile: 190 },
+ { month: "May", desktop: 209, mobile: 130 },
+ { month: "June", desktop: 214, mobile: 140 },
+]
+
+const chartConfig = {
+ desktop: {
+ label: "Desktop",
+ color: "#2563eb",
+ },
+ mobile: {
+ label: "Mobile",
+ color: "#60a5fa",
+ },
+} satisfies ChartConfig
+
+export function ChartBarDemoAxis() {
+ return (
+
+
+
+ value.slice(0, 3)}
+ />
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/chart-bar-demo-grid.tsx b/apps/v4/examples/base/chart-bar-demo-grid.tsx
new file mode 100644
index 0000000000..23677eccb5
--- /dev/null
+++ b/apps/v4/examples/base/chart-bar-demo-grid.tsx
@@ -0,0 +1,36 @@
+"use client"
+
+import { ChartContainer, type ChartConfig } from "@/examples/base/ui/chart"
+import { Bar, BarChart, CartesianGrid } from "recharts"
+
+const chartData = [
+ { month: "January", desktop: 186, mobile: 80 },
+ { month: "February", desktop: 305, mobile: 200 },
+ { month: "March", desktop: 237, mobile: 120 },
+ { month: "April", desktop: 73, mobile: 190 },
+ { month: "May", desktop: 209, mobile: 130 },
+ { month: "June", desktop: 214, mobile: 140 },
+]
+
+const chartConfig = {
+ desktop: {
+ label: "Desktop",
+ color: "#2563eb",
+ },
+ mobile: {
+ label: "Mobile",
+ color: "#60a5fa",
+ },
+} satisfies ChartConfig
+
+export function ChartBarDemoGrid() {
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/chart-bar-demo-legend.tsx b/apps/v4/examples/base/chart-bar-demo-legend.tsx
new file mode 100644
index 0000000000..4f33e5934c
--- /dev/null
+++ b/apps/v4/examples/base/chart-bar-demo-legend.tsx
@@ -0,0 +1,52 @@
+"use client"
+
+import {
+ ChartContainer,
+ ChartLegend,
+ ChartLegendContent,
+ ChartTooltip,
+ ChartTooltipContent,
+ type ChartConfig,
+} from "@/examples/base/ui/chart"
+import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+
+const chartData = [
+ { month: "January", desktop: 186, mobile: 80 },
+ { month: "February", desktop: 305, mobile: 200 },
+ { month: "March", desktop: 237, mobile: 120 },
+ { month: "April", desktop: 73, mobile: 190 },
+ { month: "May", desktop: 209, mobile: 130 },
+ { month: "June", desktop: 214, mobile: 140 },
+]
+
+const chartConfig = {
+ desktop: {
+ label: "Desktop",
+ color: "#2563eb",
+ },
+ mobile: {
+ label: "Mobile",
+ color: "#60a5fa",
+ },
+} satisfies ChartConfig
+
+export function ChartBarDemoLegend() {
+ return (
+
+
+
+ value.slice(0, 3)}
+ />
+ } />
+ } />
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/chart-bar-demo-tooltip.tsx b/apps/v4/examples/base/chart-bar-demo-tooltip.tsx
new file mode 100644
index 0000000000..7701431422
--- /dev/null
+++ b/apps/v4/examples/base/chart-bar-demo-tooltip.tsx
@@ -0,0 +1,49 @@
+"use client"
+
+import {
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+ type ChartConfig,
+} from "@/examples/base/ui/chart"
+import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
+
+const chartData = [
+ { month: "January", desktop: 186, mobile: 80 },
+ { month: "February", desktop: 305, mobile: 200 },
+ { month: "March", desktop: 237, mobile: 120 },
+ { month: "April", desktop: 73, mobile: 190 },
+ { month: "May", desktop: 209, mobile: 130 },
+ { month: "June", desktop: 214, mobile: 140 },
+]
+
+const chartConfig = {
+ desktop: {
+ label: "Desktop",
+ color: "#2563eb",
+ },
+ mobile: {
+ label: "Mobile",
+ color: "#60a5fa",
+ },
+} satisfies ChartConfig
+
+export function ChartBarDemoTooltip() {
+ return (
+
+
+
+ value.slice(0, 3)}
+ />
+ } />
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/chart-bar-demo.tsx b/apps/v4/examples/base/chart-bar-demo.tsx
new file mode 100644
index 0000000000..959094d724
--- /dev/null
+++ b/apps/v4/examples/base/chart-bar-demo.tsx
@@ -0,0 +1,35 @@
+"use client"
+
+import { ChartContainer, type ChartConfig } from "@/examples/base/ui/chart"
+import { Bar, BarChart } from "recharts"
+
+const chartData = [
+ { month: "January", desktop: 186, mobile: 80 },
+ { month: "February", desktop: 305, mobile: 200 },
+ { month: "March", desktop: 237, mobile: 120 },
+ { month: "April", desktop: 73, mobile: 190 },
+ { month: "May", desktop: 209, mobile: 130 },
+ { month: "June", desktop: 214, mobile: 140 },
+]
+
+const chartConfig = {
+ desktop: {
+ label: "Desktop",
+ color: "#2563eb",
+ },
+ mobile: {
+ label: "Mobile",
+ color: "#60a5fa",
+ },
+} satisfies ChartConfig
+
+export function ChartBarDemo() {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/registry/base-nova/demo/chart-tooltip-demo.tsx b/apps/v4/examples/base/chart-tooltip-demo.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/chart-tooltip-demo.tsx
rename to apps/v4/examples/base/chart-tooltip-demo.tsx
diff --git a/apps/v4/examples/base/checkbox-demo.tsx b/apps/v4/examples/base/checkbox-demo.tsx
new file mode 100644
index 0000000000..af8b793336
--- /dev/null
+++ b/apps/v4/examples/base/checkbox-demo.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import { Checkbox } from "@/examples/base/ui/checkbox"
+import { Label } from "@/examples/base/ui/label"
+
+export default function CheckboxDemo() {
+ return (
+
+
+
+ Accept terms and conditions
+
+
+
+
+
Accept terms and conditions
+
+ By clicking this checkbox, you agree to the terms and conditions.
+
+
+
+
+
+ Enable notifications
+
+
+
+
+
+ Enable notifications
+
+
+ You can enable or disable notifications at any time.
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/checkbox-disabled.tsx b/apps/v4/examples/base/checkbox-disabled.tsx
new file mode 100644
index 0000000000..54c1010124
--- /dev/null
+++ b/apps/v4/examples/base/checkbox-disabled.tsx
@@ -0,0 +1,15 @@
+import { Checkbox } from "@/examples/base/ui/checkbox"
+
+export default function CheckboxDisabled() {
+ return (
+
+
+
+ Accept terms and conditions
+
+
+ )
+}
diff --git a/apps/v4/examples/base/checkbox-with-text.tsx b/apps/v4/examples/base/checkbox-with-text.tsx
new file mode 100644
index 0000000000..711516150b
--- /dev/null
+++ b/apps/v4/examples/base/checkbox-with-text.tsx
@@ -0,0 +1,22 @@
+"use client"
+
+import { Checkbox } from "@/examples/base/ui/checkbox"
+
+export default function CheckboxWithText() {
+ return (
+
+
+
+
+ Accept terms and conditions
+
+
+ You agree to our Terms of Service and Privacy Policy.
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/collapsible-demo.tsx b/apps/v4/examples/base/collapsible-demo.tsx
new file mode 100644
index 0000000000..7a27ceab4e
--- /dev/null
+++ b/apps/v4/examples/base/collapsible-demo.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/examples/base/ui/collapsible"
+import { ChevronsUpDown } from "lucide-react"
+
+export default function CollapsibleDemo() {
+ const [isOpen, setIsOpen] = React.useState(false)
+
+ return (
+
+
+
+ @peduarte starred 3 repositories
+
+ }
+ >
+
+ Toggle
+
+
+
+ @radix-ui/primitives
+
+
+
+ @radix-ui/colors
+
+
+ @stitches/react
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/combobox-demo.tsx b/apps/v4/examples/base/combobox-demo.tsx
new file mode 100644
index 0000000000..37a8e6a4aa
--- /dev/null
+++ b/apps/v4/examples/base/combobox-demo.tsx
@@ -0,0 +1,96 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/examples/base/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+import { Check, ChevronsUpDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const frameworks = [
+ {
+ value: "next.js",
+ label: "Next.js",
+ },
+ {
+ value: "sveltekit",
+ label: "SvelteKit",
+ },
+ {
+ value: "nuxt.js",
+ label: "Nuxt.js",
+ },
+ {
+ value: "remix",
+ label: "Remix",
+ },
+ {
+ value: "astro",
+ label: "Astro",
+ },
+]
+
+export default function ComboboxDemo() {
+ const [open, setOpen] = React.useState(false)
+ const [value, setValue] = React.useState("")
+
+ return (
+
+
+ }
+ >
+ {value
+ ? frameworks.find((framework) => framework.value === value)?.label
+ : "Select framework..."}
+
+
+
+
+
+
+ No framework found.
+
+ {frameworks.map((framework) => (
+ {
+ setValue(currentValue === value ? "" : currentValue)
+ setOpen(false)
+ }}
+ >
+ {framework.label}
+
+
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/combobox-dropdown-menu.tsx b/apps/v4/examples/base/combobox-dropdown-menu.tsx
new file mode 100644
index 0000000000..3522f36050
--- /dev/null
+++ b/apps/v4/examples/base/combobox-dropdown-menu.tsx
@@ -0,0 +1,99 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/examples/base/ui/command"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+import { MoreHorizontal } from "lucide-react"
+
+const labels = [
+ "feature",
+ "bug",
+ "enhancement",
+ "documentation",
+ "design",
+ "question",
+ "maintenance",
+]
+
+export default function ComboboxDropdownMenu() {
+ const [label, setLabel] = React.useState("feature")
+ const [open, setOpen] = React.useState(false)
+
+ return (
+
+
+
+ {label}
+
+ Create a new project
+
+
+ }>
+
+
+
+
+ Actions
+ Assign to...
+ Set due date...
+
+
+ Apply label
+
+
+
+
+ No label found.
+
+ {labels.map((label) => (
+ {
+ setLabel(value)
+ setOpen(false)
+ }}
+ >
+ {label}
+
+ ))}
+
+
+
+
+
+
+
+ Delete
+ ⌘⌫
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/combobox-popover.tsx b/apps/v4/examples/base/combobox-popover.tsx
new file mode 100644
index 0000000000..14da8b32cc
--- /dev/null
+++ b/apps/v4/examples/base/combobox-popover.tsx
@@ -0,0 +1,92 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/examples/base/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+
+type Status = {
+ value: string
+ label: string
+}
+
+const statuses: Status[] = [
+ {
+ value: "backlog",
+ label: "Backlog",
+ },
+ {
+ value: "todo",
+ label: "Todo",
+ },
+ {
+ value: "in progress",
+ label: "In Progress",
+ },
+ {
+ value: "done",
+ label: "Done",
+ },
+ {
+ value: "canceled",
+ label: "Canceled",
+ },
+]
+
+export default function ComboboxPopover() {
+ const [open, setOpen] = React.useState(false)
+ const [selectedStatus, setSelectedStatus] = React.useState(
+ null
+ )
+
+ return (
+
+
Status
+
+
+ }
+ >
+ {selectedStatus ? <>{selectedStatus.label}> : <>+ Set status>}
+
+
+
+
+
+ No results found.
+
+ {statuses.map((status) => (
+ {
+ setSelectedStatus(
+ statuses.find((priority) => priority.value === value) ||
+ null
+ )
+ setOpen(false)
+ }}
+ >
+ {status.label}
+
+ ))}
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/combobox-responsive.tsx b/apps/v4/examples/base/combobox-responsive.tsx
new file mode 100644
index 0000000000..911784f0a9
--- /dev/null
+++ b/apps/v4/examples/base/combobox-responsive.tsx
@@ -0,0 +1,121 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/examples/base/ui/command"
+import { Drawer, DrawerContent, DrawerTrigger } from "@/examples/base/ui/drawer"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+
+import { useMediaQuery } from "@/hooks/use-media-query"
+
+type Status = {
+ value: string
+ label: string
+}
+
+const statuses: Status[] = [
+ {
+ value: "backlog",
+ label: "Backlog",
+ },
+ {
+ value: "todo",
+ label: "Todo",
+ },
+ {
+ value: "in progress",
+ label: "In Progress",
+ },
+ {
+ value: "done",
+ label: "Done",
+ },
+ {
+ value: "canceled",
+ label: "Canceled",
+ },
+]
+
+export default function ComboBoxResponsive() {
+ const [open, setOpen] = React.useState(false)
+ const isDesktop = useMediaQuery("(min-width: 768px)")
+ const [selectedStatus, setSelectedStatus] = React.useState(
+ null
+ )
+
+ if (isDesktop) {
+ return (
+
+
+ }
+ >
+ {selectedStatus ? <>{selectedStatus.label}> : <>+ Set status>}
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ {selectedStatus ? <>{selectedStatus.label}> : <>+ Set status>}
+
+
+
+
+
+
+
+
+ )
+}
+
+function StatusList({
+ setOpen,
+ setSelectedStatus,
+}: {
+ setOpen: (open: boolean) => void
+ setSelectedStatus: (status: Status | null) => void
+}) {
+ return (
+
+
+
+ No results found.
+
+ {statuses.map((status) => (
+ {
+ setSelectedStatus(
+ statuses.find((priority) => priority.value === value) || null
+ )
+ setOpen(false)
+ }}
+ >
+ {status.label}
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/command-demo.tsx b/apps/v4/examples/base/command-demo.tsx
new file mode 100644
index 0000000000..ce4940e142
--- /dev/null
+++ b/apps/v4/examples/base/command-demo.tsx
@@ -0,0 +1,61 @@
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+ CommandShortcut,
+} from "@/examples/base/ui/command"
+import {
+ Calculator,
+ Calendar,
+ CreditCard,
+ Settings,
+ Smile,
+ User,
+} from "lucide-react"
+
+export function CommandDemo() {
+ return (
+
+
+
+ No results found.
+
+
+
+ Calendar
+
+
+
+ Search Emoji
+
+
+
+ Calculator
+
+
+
+
+
+
+ Profile
+ ⌘P
+
+
+
+ Billing
+ ⌘B
+
+
+
+ Settings
+ ⌘S
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/command-dialog.tsx b/apps/v4/examples/base/command-dialog.tsx
new file mode 100644
index 0000000000..5aef898896
--- /dev/null
+++ b/apps/v4/examples/base/command-dialog.tsx
@@ -0,0 +1,86 @@
+"use client"
+
+import * as React from "react"
+import {
+ CommandDialog,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+ CommandShortcut,
+} from "@/examples/base/ui/command"
+import {
+ Calculator,
+ Calendar,
+ CreditCard,
+ Settings,
+ Smile,
+ User,
+} from "lucide-react"
+
+export function CommandDialogDemo() {
+ const [open, setOpen] = React.useState(false)
+
+ React.useEffect(() => {
+ const down = (e: KeyboardEvent) => {
+ if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault()
+ setOpen((open) => !open)
+ }
+ }
+
+ document.addEventListener("keydown", down)
+ return () => document.removeEventListener("keydown", down)
+ }, [])
+
+ return (
+ <>
+
+ Press{" "}
+
+ ⌘ J
+
+
+
+
+
+ No results found.
+
+
+
+ Calendar
+
+
+
+ Search Emoji
+
+
+
+ Calculator
+
+
+
+
+
+
+ Profile
+ ⌘P
+
+
+
+ Billing
+ ⌘B
+
+
+
+ Settings
+ ⌘S
+
+
+
+
+ >
+ )
+}
diff --git a/apps/v4/examples/base/context-menu-demo.tsx b/apps/v4/examples/base/context-menu-demo.tsx
new file mode 100644
index 0000000000..b6aa0c0fc2
--- /dev/null
+++ b/apps/v4/examples/base/context-menu-demo.tsx
@@ -0,0 +1,77 @@
+import {
+ ContextMenu,
+ ContextMenuCheckboxItem,
+ ContextMenuContent,
+ ContextMenuGroup,
+ ContextMenuItem,
+ ContextMenuLabel,
+ ContextMenuRadioGroup,
+ ContextMenuRadioItem,
+ ContextMenuSeparator,
+ ContextMenuShortcut,
+ ContextMenuSub,
+ ContextMenuSubContent,
+ ContextMenuSubTrigger,
+ ContextMenuTrigger,
+} from "@/examples/base/ui/context-menu"
+
+export default function ContextMenuDemo() {
+ return (
+
+
+ Right click here
+
+
+
+
+ Back
+ ⌘[
+
+
+ Forward
+ ⌘]
+
+
+ Reload
+ ⌘R
+
+
+ More Tools
+
+
+ Save Page...
+ Create Shortcut...
+ Name Window...
+
+
+
+ Developer Tools
+
+
+
+ Delete
+
+
+
+
+
+
+
+ Show Bookmarks
+
+ Show Full URLs
+
+
+
+
+ People
+
+ Pedro Duarte
+
+ Colm Tuite
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/data-table-demo.tsx b/apps/v4/examples/base/data-table-demo.tsx
new file mode 100644
index 0000000000..c146555e3b
--- /dev/null
+++ b/apps/v4/examples/base/data-table-demo.tsx
@@ -0,0 +1,318 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import { Checkbox } from "@/examples/base/ui/checkbox"
+import {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+import { Input } from "@/examples/base/ui/input"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/examples/base/ui/table"
+import {
+ flexRender,
+ getCoreRowModel,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+ type ColumnDef,
+ type ColumnFiltersState,
+ type SortingState,
+ type VisibilityState,
+} from "@tanstack/react-table"
+import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-react"
+
+const data: Payment[] = [
+ {
+ id: "m5gr84i9",
+ amount: 316,
+ status: "success",
+ email: "ken99@example.com",
+ },
+ {
+ id: "3u1reuv4",
+ amount: 242,
+ status: "success",
+ email: "Abe45@example.com",
+ },
+ {
+ id: "derv1ws0",
+ amount: 837,
+ status: "processing",
+ email: "Monserrat44@example.com",
+ },
+ {
+ id: "5kma53ae",
+ amount: 874,
+ status: "success",
+ email: "Silas22@example.com",
+ },
+ {
+ id: "bhqecj4p",
+ amount: 721,
+ status: "failed",
+ email: "carmella@example.com",
+ },
+]
+
+export type Payment = {
+ id: string
+ amount: number
+ status: "pending" | "processing" | "success" | "failed"
+ email: string
+}
+
+export const columns: ColumnDef[] = [
+ {
+ id: "select",
+ header: ({ table }) => (
+ table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ />
+ ),
+ cell: ({ row }) => (
+ row.toggleSelected(!!value)}
+ aria-label="Select row"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "status",
+ header: "Status",
+ cell: ({ row }) => (
+ {row.getValue("status")}
+ ),
+ },
+ {
+ accessorKey: "email",
+ header: ({ column }) => {
+ return (
+ column.toggleSorting(column.getIsSorted() === "asc")}
+ >
+ Email
+
+
+ )
+ },
+ cell: ({ row }) => {row.getValue("email")}
,
+ },
+ {
+ accessorKey: "amount",
+ header: () => Amount
,
+ cell: ({ row }) => {
+ const amount = parseFloat(row.getValue("amount"))
+
+ // Format the amount as a dollar amount.
+ const formatted = new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ }).format(amount)
+
+ return {formatted}
+ },
+ },
+ {
+ id: "actions",
+ enableHiding: false,
+ cell: ({ row }) => {
+ const payment = row.original
+
+ return (
+
+ }
+ >
+ Open menu
+
+
+
+
+ Actions
+ navigator.clipboard.writeText(payment.id)}
+ >
+ Copy payment ID
+
+
+
+
+ View customer
+ View payment details
+
+
+
+ )
+ },
+ },
+]
+
+export function DataTableDemo() {
+ const [sorting, setSorting] = React.useState([])
+ const [columnFilters, setColumnFilters] = React.useState(
+ []
+ )
+ const [columnVisibility, setColumnVisibility] =
+ React.useState({})
+ const [rowSelection, setRowSelection] = React.useState({})
+
+ const table = useReactTable({
+ data,
+ columns,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ onColumnVisibilityChange: setColumnVisibility,
+ onRowSelectionChange: setRowSelection,
+ state: {
+ sorting,
+ columnFilters,
+ columnVisibility,
+ rowSelection,
+ },
+ })
+
+ return (
+
+
+
+ table.getColumn("email")?.setFilterValue(event.target.value)
+ }
+ className="max-w-sm"
+ />
+
+ }
+ >
+ Columns
+
+
+
+ {table
+ .getAllColumns()
+ .filter((column) => column.getCanHide())
+ .map((column) => {
+ return (
+
+ column.toggleVisibility(!!value)
+ }
+ >
+ {column.id}
+
+ )
+ })}
+
+
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ )
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
+ {table.getFilteredRowModel().rows.length} row(s) selected.
+
+
+ table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ Previous
+
+ table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ Next
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/date-picker-demo.tsx b/apps/v4/examples/base/date-picker-demo.tsx
new file mode 100644
index 0000000000..bdb3469f16
--- /dev/null
+++ b/apps/v4/examples/base/date-picker-demo.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import { Calendar } from "@/examples/base/ui/calendar"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+import { format } from "date-fns"
+import { CalendarIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+export function DatePickerDemo() {
+ const [date, setDate] = React.useState()
+
+ return (
+
+
+ }
+ >
+
+ {date ? format(date, "PPP") : Pick a date }
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/date-picker-with-presets.tsx b/apps/v4/examples/base/date-picker-with-presets.tsx
new file mode 100644
index 0000000000..47d4322876
--- /dev/null
+++ b/apps/v4/examples/base/date-picker-with-presets.tsx
@@ -0,0 +1,80 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import { Calendar } from "@/examples/base/ui/calendar"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/examples/base/ui/select"
+import { addDays, format } from "date-fns"
+import { CalendarIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const items = [
+ { label: "Select", value: null },
+ { label: "Today", value: "0" },
+ { label: "Tomorrow", value: "1" },
+ { label: "In 3 days", value: "3" },
+ { label: "In a week", value: "7" },
+]
+
+export function DatePickerWithPresets() {
+ const [date, setDate] = React.useState()
+
+ return (
+
+
+ }
+ >
+
+ {date ? format(date, "PPP") : Pick a date }
+
+
+
+ setDate(addDays(new Date(), parseInt(value as string)))
+ }
+ >
+
+
+
+
+
+ {items.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/date-picker-with-range.tsx b/apps/v4/examples/base/date-picker-with-range.tsx
new file mode 100644
index 0000000000..26c515e283
--- /dev/null
+++ b/apps/v4/examples/base/date-picker-with-range.tsx
@@ -0,0 +1,67 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import { Calendar } from "@/examples/base/ui/calendar"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+import { addDays, format } from "date-fns"
+import { CalendarIcon } from "lucide-react"
+import { type DateRange } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+
+export function DatePickerWithRange({
+ className,
+}: React.HTMLAttributes) {
+ const [date, setDate] = React.useState({
+ from: new Date(2022, 0, 20),
+ to: addDays(new Date(2022, 0, 20), 20),
+ })
+
+ return (
+
+
+
+ }
+ >
+
+ {date?.from ? (
+ date.to ? (
+ <>
+ {format(date.from, "LLL dd, y")} -{" "}
+ {format(date.to, "LLL dd, y")}
+ >
+ ) : (
+ format(date.from, "LLL dd, y")
+ )
+ ) : (
+ Pick a date
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/dialog-close-button.tsx b/apps/v4/examples/base/dialog-close-button.tsx
new file mode 100644
index 0000000000..bd07895554
--- /dev/null
+++ b/apps/v4/examples/base/dialog-close-button.tsx
@@ -0,0 +1,46 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/examples/base/ui/dialog"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+export function DialogCloseButton() {
+ return (
+
+ }>Share
+
+
+ Share link
+
+ Anyone who has this link will be able to view this.
+
+
+
+
+ }>
+ Close
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/dialog-demo.tsx b/apps/v4/examples/base/dialog-demo.tsx
new file mode 100644
index 0000000000..96a7ed2fb6
--- /dev/null
+++ b/apps/v4/examples/base/dialog-demo.tsx
@@ -0,0 +1,50 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/examples/base/ui/dialog"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+export function DialogDemo() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/drawer-demo.tsx b/apps/v4/examples/base/drawer-demo.tsx
new file mode 100644
index 0000000000..36a7b99493
--- /dev/null
+++ b/apps/v4/examples/base/drawer-demo.tsx
@@ -0,0 +1,135 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from "@/examples/base/ui/drawer"
+import { Minus, Plus } from "lucide-react"
+import { Bar, BarChart, ResponsiveContainer } from "recharts"
+
+const data = [
+ {
+ goal: 400,
+ },
+ {
+ goal: 300,
+ },
+ {
+ goal: 200,
+ },
+ {
+ goal: 300,
+ },
+ {
+ goal: 200,
+ },
+ {
+ goal: 278,
+ },
+ {
+ goal: 189,
+ },
+ {
+ goal: 239,
+ },
+ {
+ goal: 300,
+ },
+ {
+ goal: 200,
+ },
+ {
+ goal: 278,
+ },
+ {
+ goal: 189,
+ },
+ {
+ goal: 349,
+ },
+]
+
+export function DrawerDemo() {
+ const [goal, setGoal] = React.useState(350)
+
+ function onClick(adjustment: number) {
+ setGoal(Math.max(200, Math.min(400, goal + adjustment)))
+ }
+
+ return (
+
+
+ Open Drawer
+
+
+
+
+ Move Goal
+ Set your daily activity goal.
+
+
+
+
onClick(-10)}
+ disabled={goal <= 200}
+ >
+
+ Decrease
+
+
+
+ {goal}
+
+
+ Calories/day
+
+
+
onClick(10)}
+ disabled={goal >= 400}
+ >
+
+ Increase
+
+
+
+
+
+
+
+
+
+
+
+ Submit
+
+ Cancel
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/drawer-dialog.tsx b/apps/v4/examples/base/drawer-dialog.tsx
new file mode 100644
index 0000000000..e4a0b4ce9f
--- /dev/null
+++ b/apps/v4/examples/base/drawer-dialog.tsx
@@ -0,0 +1,90 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/examples/base/ui/dialog"
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from "@/examples/base/ui/drawer"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+import { cn } from "@/lib/utils"
+import { useMediaQuery } from "@/hooks/use-media-query"
+
+export function DrawerDialogDemo() {
+ const [open, setOpen] = React.useState(false)
+ const isDesktop = useMediaQuery("(min-width: 768px)")
+
+ if (isDesktop) {
+ return (
+
+ }>
+ Edit Profile
+
+
+
+ Edit profile
+
+ Make changes to your profile here. Click save when you're
+ done.
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+ Edit Profile
+
+
+
+ Edit profile
+
+ Make changes to your profile here. Click save when you're done.
+
+
+
+
+
+ Cancel
+
+
+
+
+ )
+}
+
+function ProfileForm({ className }: React.ComponentProps<"form">) {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/dropdown-menu-checkboxes.tsx b/apps/v4/examples/base/dropdown-menu-checkboxes.tsx
new file mode 100644
index 0000000000..d1a2ed8d12
--- /dev/null
+++ b/apps/v4/examples/base/dropdown-menu-checkboxes.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+
+export function DropdownMenuCheckboxes() {
+ const [showStatusBar, setShowStatusBar] = React.useState(true)
+ const [showActivityBar, setShowActivityBar] = React.useState(false)
+ const [showPanel, setShowPanel] = React.useState(false)
+
+ return (
+
+ }>
+ Open
+
+
+
+ Appearance
+
+ Status Bar
+
+
+ Activity Bar
+
+
+ Panel
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/dropdown-menu-demo.tsx b/apps/v4/examples/base/dropdown-menu-demo.tsx
new file mode 100644
index 0000000000..ab2cd6194a
--- /dev/null
+++ b/apps/v4/examples/base/dropdown-menu-demo.tsx
@@ -0,0 +1,78 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuPortal,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+
+export function DropdownMenuDemo() {
+ return (
+
+ }>
+ Open
+
+
+
+ My Account
+
+ Profile
+ ⇧⌘P
+
+
+ Billing
+ ⌘B
+
+
+ Settings
+ ⌘S
+
+
+ Keyboard shortcuts
+ ⌘K
+
+
+
+
+ Team
+
+ Invite users
+
+
+ Email
+ Message
+
+ More...
+
+
+
+
+ New Team
+ ⌘+T
+
+
+
+
+ GitHub
+ Support
+ API
+
+
+
+
+ Log out
+ ⇧⌘Q
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/dropdown-menu-dialog.tsx b/apps/v4/examples/base/dropdown-menu-dialog.tsx
new file mode 100644
index 0000000000..3179326342
--- /dev/null
+++ b/apps/v4/examples/base/dropdown-menu-dialog.tsx
@@ -0,0 +1,116 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/examples/base/ui/dialog"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+import { Field, FieldGroup, FieldLabel } from "@/examples/base/ui/field"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+import { Textarea } from "@/examples/base/ui/textarea"
+import { MoreHorizontalIcon } from "lucide-react"
+
+export function DropdownMenuDialog() {
+ const [showNewDialog, setShowNewDialog] = useState(false)
+ const [showShareDialog, setShowShareDialog] = useState(false)
+
+ return (
+ <>
+
+
+ }
+ >
+
+
+
+
+ File Actions
+ setShowNewDialog(true)}>
+ New File...
+
+ setShowShareDialog(true)}>
+ Share...
+
+ Download
+
+
+
+
+
+
+ Create New File
+
+ Provide a name for your new file. Click create when you're
+ done.
+
+
+
+
+ File Name
+
+
+
+
+ }>
+ Cancel
+
+ Create
+
+
+
+
+
+
+ Share File
+
+ Anyone with the link will be able to view this file.
+
+
+
+
+ Email Address
+
+
+
+ Message (Optional)
+
+
+
+
+ }>
+ Cancel
+
+ Send Invite
+
+
+
+ >
+ )
+}
diff --git a/apps/v4/examples/base/dropdown-menu-radio-group.tsx b/apps/v4/examples/base/dropdown-menu-radio-group.tsx
new file mode 100644
index 0000000000..3615cf4356
--- /dev/null
+++ b/apps/v4/examples/base/dropdown-menu-radio-group.tsx
@@ -0,0 +1,35 @@
+"use client"
+
+import * as React from "react"
+import { Button } from "@/examples/base/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+
+export function DropdownMenuRadioGroupDemo() {
+ const [position, setPosition] = React.useState("bottom")
+
+ return (
+
+ }>
+ Open
+
+
+
+ Panel Position
+
+ Top
+ Bottom
+ Right
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-avatar-group.tsx b/apps/v4/examples/base/empty-avatar-group.tsx
new file mode 100644
index 0000000000..3813c2de31
--- /dev/null
+++ b/apps/v4/examples/base/empty-avatar-group.tsx
@@ -0,0 +1,52 @@
+import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import { PlusIcon } from "lucide-react"
+
+export default function EmptyAvatarGroup() {
+ return (
+
+
+
+
+
+
+ CN
+
+
+
+ LR
+
+
+
+ ER
+
+
+
+ No Team Members
+
+ Invite your team to collaborate on this project.
+
+
+
+
+
+ Invite Members
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-avatar.tsx b/apps/v4/examples/base/empty-avatar.tsx
new file mode 100644
index 0000000000..7484e38e6c
--- /dev/null
+++ b/apps/v4/examples/base/empty-avatar.tsx
@@ -0,0 +1,36 @@
+import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+
+export default function EmptyAvatar() {
+ return (
+
+
+
+
+
+ LR
+
+
+ User Offline
+
+ This user is currently offline. You can leave a message to notify them
+ or try again later.
+
+
+
+ Leave Message
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-background.tsx b/apps/v4/examples/base/empty-background.tsx
new file mode 100644
index 0000000000..4265a22aab
--- /dev/null
+++ b/apps/v4/examples/base/empty-background.tsx
@@ -0,0 +1,33 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import { IconBell } from "@tabler/icons-react"
+import { RefreshCcwIcon } from "lucide-react"
+
+export default function EmptyMuted() {
+ return (
+
+
+
+
+
+ No Notifications
+
+ You're all caught up. New notifications will appear here.
+
+
+
+
+
+ Refresh
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-demo.tsx b/apps/v4/examples/base/empty-demo.tsx
new file mode 100644
index 0000000000..c127e26e31
--- /dev/null
+++ b/apps/v4/examples/base/empty-demo.tsx
@@ -0,0 +1,42 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import { IconFolderCode } from "@tabler/icons-react"
+import { ArrowUpRightIcon } from "lucide-react"
+
+export default function EmptyDemo() {
+ return (
+
+
+
+
+
+ No Projects Yet
+
+ You haven't created any projects yet. Get started by creating
+ your first project.
+
+
+
+
+ Create Project
+ Import Project
+
+
+ }
+ className="text-muted-foreground"
+ size="sm"
+ >
+ Learn More
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-icon.tsx b/apps/v4/examples/base/empty-icon.tsx
new file mode 100644
index 0000000000..1dd2a78b91
--- /dev/null
+++ b/apps/v4/examples/base/empty-icon.tsx
@@ -0,0 +1,67 @@
+import {
+ Empty,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import {
+ IconBookmark,
+ IconHeart,
+ IconInbox,
+ IconStar,
+} from "@tabler/icons-react"
+
+export default function EmptyIcon() {
+ return (
+
+
+
+
+
+
+ No messages
+
+ Your inbox is empty. New messages will appear here.
+
+
+
+
+
+
+
+
+
+ No favorites
+
+ Items you mark as favorites will appear here.
+
+
+
+
+
+
+
+
+
+ No likes yet
+
+ Content you like will be saved here for easy access.
+
+
+
+
+
+
+
+
+
+ No bookmarks
+
+ Save interesting content by bookmarking it.
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-input-group.tsx b/apps/v4/examples/base/empty-input-group.tsx
new file mode 100644
index 0000000000..6e1a452bf9
--- /dev/null
+++ b/apps/v4/examples/base/empty-input-group.tsx
@@ -0,0 +1,42 @@
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import { Kbd } from "@/examples/base/ui/kbd"
+import { SearchIcon } from "lucide-react"
+
+export default function EmptyInputGroup() {
+ return (
+
+
+ 404 - Not Found
+
+ The page you're looking for doesn't exist. Try searching for
+ what you need below.
+
+
+
+
+
+
+
+
+
+ /
+
+
+
+ Need help? Contact support
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/empty-outline.tsx b/apps/v4/examples/base/empty-outline.tsx
new file mode 100644
index 0000000000..ee676054e6
--- /dev/null
+++ b/apps/v4/examples/base/empty-outline.tsx
@@ -0,0 +1,31 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import { IconCloud } from "@tabler/icons-react"
+
+export default function EmptyOutline() {
+ return (
+
+
+
+
+
+ Cloud Storage Empty
+
+ Upload files to your cloud storage to access them anywhere.
+
+
+
+
+ Upload Files
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-checkbox.tsx b/apps/v4/examples/base/field-checkbox.tsx
new file mode 100644
index 0000000000..83ca05da8c
--- /dev/null
+++ b/apps/v4/examples/base/field-checkbox.tsx
@@ -0,0 +1,80 @@
+import { Checkbox } from "@/examples/base/ui/checkbox"
+import {
+ Field,
+ FieldContent,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldLegend,
+ FieldSeparator,
+ FieldSet,
+} from "@/examples/base/ui/field"
+
+export default function FieldCheckbox() {
+ return (
+
+
+
+
+ Show these items on the desktop
+
+
+ Select the items you want to show on the desktop.
+
+
+
+
+
+ Hard disks
+
+
+
+
+
+ External disks
+
+
+
+
+
+ CDs, DVDs, and iPods
+
+
+
+
+
+ Connected servers
+
+
+
+
+
+
+
+
+
+ Sync Desktop & Documents folders
+
+
+ Your Desktop & Documents folders are being synced with iCloud
+ Drive. You can access them from other devices.
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-choice-card.tsx b/apps/v4/examples/base/field-choice-card.tsx
new file mode 100644
index 0000000000..03dfe16df8
--- /dev/null
+++ b/apps/v4/examples/base/field-choice-card.tsx
@@ -0,0 +1,51 @@
+import {
+ Field,
+ FieldContent,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldSet,
+ FieldTitle,
+} from "@/examples/base/ui/field"
+import { RadioGroup, RadioGroupItem } from "@/examples/base/ui/radio-group"
+
+export default function FieldChoiceCard() {
+ return (
+
+
+
+
+ Compute Environment
+
+
+ Select the compute environment for your cluster.
+
+
+
+
+
+ Kubernetes
+
+ Run GPU workloads on a K8s configured cluster.
+
+
+
+
+
+
+
+
+ Virtual Machine
+
+ Access a VM configured cluster to run GPU workloads.
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-demo.tsx b/apps/v4/examples/base/field-demo.tsx
new file mode 100644
index 0000000000..e4d1c6cae9
--- /dev/null
+++ b/apps/v4/examples/base/field-demo.tsx
@@ -0,0 +1,174 @@
+import { Button } from "@/examples/base/ui/button"
+import { Checkbox } from "@/examples/base/ui/checkbox"
+import {
+ Field,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldLegend,
+ FieldSeparator,
+ FieldSet,
+} from "@/examples/base/ui/field"
+import { Input } from "@/examples/base/ui/input"
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/examples/base/ui/select"
+import { Textarea } from "@/examples/base/ui/textarea"
+
+const months = [
+ { label: "MM", value: null },
+ { label: "01", value: "01" },
+ { label: "02", value: "02" },
+ { label: "03", value: "03" },
+ { label: "04", value: "04" },
+ { label: "05", value: "05" },
+ { label: "06", value: "06" },
+ { label: "07", value: "07" },
+ { label: "08", value: "08" },
+ { label: "09", value: "09" },
+ { label: "10", value: "10" },
+ { label: "11", value: "11" },
+ { label: "12", value: "12" },
+]
+
+const years = [
+ { label: "YYYY", value: null },
+ { label: "2024", value: "2024" },
+ { label: "2025", value: "2025" },
+ { label: "2026", value: "2026" },
+ { label: "2027", value: "2027" },
+ { label: "2028", value: "2028" },
+ { label: "2029", value: "2029" },
+]
+
+export default function FieldDemo() {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/field-fieldset.tsx b/apps/v4/examples/base/field-fieldset.tsx
new file mode 100644
index 0000000000..8f2372f2ff
--- /dev/null
+++ b/apps/v4/examples/base/field-fieldset.tsx
@@ -0,0 +1,38 @@
+import {
+ Field,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldLegend,
+ FieldSet,
+} from "@/examples/base/ui/field"
+import { Input } from "@/examples/base/ui/input"
+
+export default function FieldFieldset() {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/field-group.tsx b/apps/v4/examples/base/field-group.tsx
new file mode 100644
index 0000000000..672d6ed76e
--- /dev/null
+++ b/apps/v4/examples/base/field-group.tsx
@@ -0,0 +1,55 @@
+import { Checkbox } from "@/examples/base/ui/checkbox"
+import {
+ Field,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldSeparator,
+ FieldSet,
+} from "@/examples/base/ui/field"
+
+export default function FieldGroupExample() {
+ return (
+
+
+
+ Responses
+
+ Get notified when ChatGPT responds to requests that take time, like
+ research or image generation.
+
+
+
+
+
+ Push notifications
+
+
+
+
+
+
+ Tasks
+
+ Get notified when tasks you've created have updates.{" "}
+ Manage tasks
+
+
+
+
+
+ Push notifications
+
+
+
+
+
+ Email notifications
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-input.tsx b/apps/v4/examples/base/field-input.tsx
new file mode 100644
index 0000000000..11ba3f0da5
--- /dev/null
+++ b/apps/v4/examples/base/field-input.tsx
@@ -0,0 +1,33 @@
+import {
+ Field,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldSet,
+} from "@/examples/base/ui/field"
+import { Input } from "@/examples/base/ui/input"
+
+export default function FieldInput() {
+ return (
+
+
+
+
+ Username
+
+
+ Choose a unique username for your account.
+
+
+
+ Password
+
+ Must be at least 8 characters long.
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-radio.tsx b/apps/v4/examples/base/field-radio.tsx
new file mode 100644
index 0000000000..491fe1cfd8
--- /dev/null
+++ b/apps/v4/examples/base/field-radio.tsx
@@ -0,0 +1,40 @@
+import {
+ Field,
+ FieldDescription,
+ FieldLabel,
+ FieldSet,
+} from "@/examples/base/ui/field"
+import { RadioGroup, RadioGroupItem } from "@/examples/base/ui/radio-group"
+
+export default function FieldRadio() {
+ return (
+
+
+ Subscription Plan
+
+ Yearly and lifetime plans offer significant savings.
+
+
+
+
+
+ Monthly ($9.99/month)
+
+
+
+
+
+ Yearly ($99.99/year)
+
+
+
+
+
+ Lifetime ($299.99)
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-responsive.tsx b/apps/v4/examples/base/field-responsive.tsx
new file mode 100644
index 0000000000..9b8b74afac
--- /dev/null
+++ b/apps/v4/examples/base/field-responsive.tsx
@@ -0,0 +1,61 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Field,
+ FieldContent,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldLegend,
+ FieldSeparator,
+ FieldSet,
+} from "@/examples/base/ui/field"
+import { Input } from "@/examples/base/ui/input"
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function FieldResponsive() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-select.tsx b/apps/v4/examples/base/field-select.tsx
new file mode 100644
index 0000000000..4727e6581e
--- /dev/null
+++ b/apps/v4/examples/base/field-select.tsx
@@ -0,0 +1,48 @@
+import { Field, FieldDescription, FieldLabel } from "@/examples/base/ui/field"
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/examples/base/ui/select"
+
+const items = [
+ { label: "Choose department", value: null },
+ { label: "Engineering", value: "engineering" },
+ { label: "Design", value: "design" },
+ { label: "Marketing", value: "marketing" },
+ { label: "Sales", value: "sales" },
+ { label: "Customer Support", value: "support" },
+ { label: "Human Resources", value: "hr" },
+ { label: "Finance", value: "finance" },
+ { label: "Operations", value: "operations" },
+]
+
+export default function FieldSelect() {
+ return (
+
+
+ Department
+
+
+
+
+
+
+ {items.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+
+
+ Select your department or area of work.
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-slider.tsx b/apps/v4/examples/base/field-slider.tsx
new file mode 100644
index 0000000000..39aecfbc6b
--- /dev/null
+++ b/apps/v4/examples/base/field-slider.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import { Field, FieldDescription, FieldTitle } from "@/examples/base/ui/field"
+import { Slider } from "@/examples/base/ui/slider"
+
+export default function FieldSlider() {
+ const [value, setValue] = React.useState([200, 800])
+
+ return (
+
+
+ Price Range
+
+ Set your budget range ($
+ {value[0]} -{" "}
+ {value[1]} ).
+
+ setValue(value as [number, number])}
+ max={1000}
+ min={0}
+ step={10}
+ className="mt-2 w-full"
+ aria-label="Price Range"
+ />
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-switch.tsx b/apps/v4/examples/base/field-switch.tsx
new file mode 100644
index 0000000000..065e480fd2
--- /dev/null
+++ b/apps/v4/examples/base/field-switch.tsx
@@ -0,0 +1,24 @@
+import {
+ Field,
+ FieldContent,
+ FieldDescription,
+ FieldLabel,
+} from "@/examples/base/ui/field"
+import { Switch } from "@/examples/base/ui/switch"
+
+export default function FieldSwitch() {
+ return (
+
+
+
+ Multi-factor authentication
+
+ Enable multi-factor authentication. If you do not have a two-factor
+ device, you can use a one-time code sent to your email.
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/field-textarea.tsx b/apps/v4/examples/base/field-textarea.tsx
new file mode 100644
index 0000000000..520088f88e
--- /dev/null
+++ b/apps/v4/examples/base/field-textarea.tsx
@@ -0,0 +1,30 @@
+import {
+ Field,
+ FieldDescription,
+ FieldGroup,
+ FieldLabel,
+ FieldSet,
+} from "@/examples/base/ui/field"
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function FieldTextarea() {
+ return (
+
+
+
+
+ Feedback
+
+
+ Share your thoughts about our service.
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/registry/base-nova/hooks/use-mobile.ts b/apps/v4/examples/base/hooks/use-mobile.ts
similarity index 100%
rename from apps/v4/registry/base-nova/hooks/use-mobile.ts
rename to apps/v4/examples/base/hooks/use-mobile.ts
diff --git a/apps/v4/examples/base/hover-card-demo.tsx b/apps/v4/examples/base/hover-card-demo.tsx
new file mode 100644
index 0000000000..fef4e54027
--- /dev/null
+++ b/apps/v4/examples/base/hover-card-demo.tsx
@@ -0,0 +1,34 @@
+import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
+import { Button } from "@/examples/base/ui/button"
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/examples/base/ui/hover-card"
+
+export default function HoverCardDemo() {
+ return (
+
+ }>
+ @nextjs
+
+
+
+
+
+ VC
+
+
+
@nextjs
+
+ The React Framework – created and maintained by @vercel.
+
+
+ Joined December 2021
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-demo.tsx b/apps/v4/examples/base/input-demo.tsx
new file mode 100644
index 0000000000..ec9b38151d
--- /dev/null
+++ b/apps/v4/examples/base/input-demo.tsx
@@ -0,0 +1,5 @@
+import { Input } from "@/examples/base/ui/input"
+
+export default function InputDemo() {
+ return
+}
diff --git a/apps/v4/examples/base/input-disabled.tsx b/apps/v4/examples/base/input-disabled.tsx
new file mode 100644
index 0000000000..c3c01cfbf0
--- /dev/null
+++ b/apps/v4/examples/base/input-disabled.tsx
@@ -0,0 +1,5 @@
+import { Input } from "@/examples/base/ui/input"
+
+export default function InputDisabled() {
+ return
+}
diff --git a/apps/v4/examples/base/input-file.tsx b/apps/v4/examples/base/input-file.tsx
new file mode 100644
index 0000000000..a93e5f1ea3
--- /dev/null
+++ b/apps/v4/examples/base/input-file.tsx
@@ -0,0 +1,11 @@
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+export default function InputFile() {
+ return (
+
+ Picture
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-button-group.tsx b/apps/v4/examples/base/input-group-button-group.tsx
new file mode 100644
index 0000000000..6945bdedba
--- /dev/null
+++ b/apps/v4/examples/base/input-group-button-group.tsx
@@ -0,0 +1,27 @@
+import { ButtonGroup, ButtonGroupText } from "@/examples/base/ui/button-group"
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import { Label } from "@/examples/base/ui/label"
+import { Link2Icon } from "lucide-react"
+
+export default function InputGroupButtonGroup() {
+ return (
+
+
+ }>
+ https://
+
+
+
+
+
+
+
+ .com
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-button.tsx b/apps/v4/examples/base/input-group-button.tsx
new file mode 100644
index 0000000000..098019f7c3
--- /dev/null
+++ b/apps/v4/examples/base/input-group-button.tsx
@@ -0,0 +1,84 @@
+"use client"
+
+import * as React from "react"
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+import {
+ IconCheck,
+ IconCopy,
+ IconInfoCircle,
+ IconStar,
+} from "@tabler/icons-react"
+
+import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
+
+export default function InputGroupButtonExample() {
+ const { copyToClipboard, isCopied } = useCopyToClipboard()
+ const [isFavorite, setIsFavorite] = React.useState(false)
+
+ return (
+
+
+
+
+ {
+ copyToClipboard("https://x.com/shadcn")
+ }}
+ >
+ {isCopied ? : }
+
+
+
+
+
+ }>
+
+
+
+
+
+ Your connection is not secure.
+ You should not enter any sensitive information on this site.
+
+
+
+ https://
+
+
+
+ setIsFavorite(!isFavorite)}
+ size="icon-xs"
+ >
+
+
+
+
+
+
+
+ Search
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-custom.tsx b/apps/v4/examples/base/input-group-custom.tsx
new file mode 100644
index 0000000000..826d8758af
--- /dev/null
+++ b/apps/v4/examples/base/input-group-custom.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+} from "@/examples/base/ui/input-group"
+import TextareaAutosize from "react-textarea-autosize"
+
+export default function InputGroupCustom() {
+ return (
+
+
+
+
+
+ Submit
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-demo.tsx b/apps/v4/examples/base/input-group-demo.tsx
new file mode 100644
index 0000000000..d00803d988
--- /dev/null
+++ b/apps/v4/examples/base/input-group-demo.tsx
@@ -0,0 +1,102 @@
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+ InputGroupText,
+ InputGroupTextarea,
+} from "@/examples/base/ui/input-group"
+import { Separator } from "@/examples/base/ui/separator"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/examples/base/ui/tooltip"
+import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
+import { ArrowUpIcon, Search } from "lucide-react"
+
+export default function InputGroupDemo() {
+ return (
+
+
+
+
+
+
+ 12 results
+
+
+
+
+ https://
+
+
+
+
+ }
+ >
+
+
+ This is content in a tooltip.
+
+
+
+
+
+
+
+
+
+
+ }>
+ Auto
+
+
+
+ Auto
+ Agent
+ Manual
+
+
+
+ 52% used
+
+
+
+ Send
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-dropdown.tsx b/apps/v4/examples/base/input-group-dropdown.tsx
new file mode 100644
index 0000000000..479bc65ff5
--- /dev/null
+++ b/apps/v4/examples/base/input-group-dropdown.tsx
@@ -0,0 +1,67 @@
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import { ChevronDownIcon, MoreHorizontal } from "lucide-react"
+
+export default function InputGroupDropdown() {
+ return (
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+ Settings
+ Copy path
+ Open location
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+ Search In...
+
+
+
+ Documentation
+ Blog Posts
+ Changelog
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-icon.tsx b/apps/v4/examples/base/input-group-icon.tsx
new file mode 100644
index 0000000000..2b62ff3785
--- /dev/null
+++ b/apps/v4/examples/base/input-group-icon.tsx
@@ -0,0 +1,48 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import {
+ CheckIcon,
+ CreditCardIcon,
+ InfoIcon,
+ MailIcon,
+ SearchIcon,
+ StarIcon,
+} from "lucide-react"
+
+export default function InputGroupIcon() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-label.tsx b/apps/v4/examples/base/input-group-label.tsx
new file mode 100644
index 0000000000..095f641f2d
--- /dev/null
+++ b/apps/v4/examples/base/input-group-label.tsx
@@ -0,0 +1,51 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import { Label } from "@/examples/base/ui/label"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/examples/base/ui/tooltip"
+import { InfoIcon } from "lucide-react"
+
+export default function InputGroupLabel() {
+ return (
+
+
+
+
+ @
+
+
+
+
+
+
+ Email
+
+
+
+ }
+ >
+
+
+
+ We'll use this to send you notifications
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-spinner.tsx b/apps/v4/examples/base/input-group-spinner.tsx
new file mode 100644
index 0000000000..d847ac7503
--- /dev/null
+++ b/apps/v4/examples/base/input-group-spinner.tsx
@@ -0,0 +1,45 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+ InputGroupText,
+} from "@/examples/base/ui/input-group"
+import { Spinner } from "@/examples/base/ui/spinner"
+import { LoaderIcon } from "lucide-react"
+
+export default function InputGroupSpinner() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Saving...
+
+
+
+
+
+
+
+
+
+
+ Please wait...
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-text.tsx b/apps/v4/examples/base/input-group-text.tsx
new file mode 100644
index 0000000000..bd16ce6024
--- /dev/null
+++ b/apps/v4/examples/base/input-group-text.tsx
@@ -0,0 +1,46 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+ InputGroupText,
+ InputGroupTextarea,
+} from "@/examples/base/ui/input-group"
+
+export default function InputGroupTextExample() {
+ return (
+
+
+
+ $
+
+
+
+ USD
+
+
+
+
+ https://
+
+
+
+ .com
+
+
+
+
+
+ @company.com
+
+
+
+
+
+
+ 120 characters left
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-textarea.tsx b/apps/v4/examples/base/input-group-textarea.tsx
new file mode 100644
index 0000000000..bc304d4445
--- /dev/null
+++ b/apps/v4/examples/base/input-group-textarea.tsx
@@ -0,0 +1,45 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupText,
+ InputGroupTextarea,
+} from "@/examples/base/ui/input-group"
+import {
+ IconBrandJavascript,
+ IconCopy,
+ IconCornerDownLeft,
+ IconRefresh,
+} from "@tabler/icons-react"
+
+export default function InputGroupTextareaExample() {
+ return (
+
+
+
+
+ Line 1, Column 1
+
+ Run
+
+
+
+
+
+ script.js
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-group-tooltip.tsx b/apps/v4/examples/base/input-group-tooltip.tsx
new file mode 100644
index 0000000000..e75adef6a7
--- /dev/null
+++ b/apps/v4/examples/base/input-group-tooltip.tsx
@@ -0,0 +1,74 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/examples/base/ui/tooltip"
+import { HelpCircle, InfoIcon } from "lucide-react"
+
+export default function InputGroupTooltip() {
+ return (
+
+
+
+
+
+
+ }
+ >
+
+
+
+ Password must be at least 8 characters
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+ We'll use this to send you notifications
+
+
+
+
+
+
+
+ }>
+
+
+
+
+
+ Click for help with API keys
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-otp-controlled.tsx b/apps/v4/examples/base/input-otp-controlled.tsx
new file mode 100644
index 0000000000..9741863e67
--- /dev/null
+++ b/apps/v4/examples/base/input-otp-controlled.tsx
@@ -0,0 +1,38 @@
+"use client"
+
+import * as React from "react"
+import {
+ InputOTP,
+ InputOTPGroup,
+ InputOTPSlot,
+} from "@/examples/base/ui/input-otp"
+
+export default function InputOTPControlled() {
+ const [value, setValue] = React.useState("")
+
+ return (
+
+
setValue(value)}
+ >
+
+
+
+
+
+
+
+
+
+
+ {value === "" ? (
+ <>Enter your one-time password.>
+ ) : (
+ <>You entered: {value}>
+ )}
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-otp-demo.tsx b/apps/v4/examples/base/input-otp-demo.tsx
new file mode 100644
index 0000000000..304d67964b
--- /dev/null
+++ b/apps/v4/examples/base/input-otp-demo.tsx
@@ -0,0 +1,24 @@
+import {
+ InputOTP,
+ InputOTPGroup,
+ InputOTPSeparator,
+ InputOTPSlot,
+} from "@/examples/base/ui/input-otp"
+
+export default function InputOTPDemo() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-otp-pattern.tsx b/apps/v4/examples/base/input-otp-pattern.tsx
new file mode 100644
index 0000000000..0ea47c3064
--- /dev/null
+++ b/apps/v4/examples/base/input-otp-pattern.tsx
@@ -0,0 +1,23 @@
+"use client"
+
+import {
+ InputOTP,
+ InputOTPGroup,
+ InputOTPSlot,
+} from "@/examples/base/ui/input-otp"
+import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
+
+export default function InputOTPPattern() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-otp-separator.tsx b/apps/v4/examples/base/input-otp-separator.tsx
new file mode 100644
index 0000000000..7cff67fbd6
--- /dev/null
+++ b/apps/v4/examples/base/input-otp-separator.tsx
@@ -0,0 +1,27 @@
+import {
+ InputOTP,
+ InputOTPGroup,
+ InputOTPSeparator,
+ InputOTPSlot,
+} from "@/examples/base/ui/input-otp"
+
+export default function InputOTPWithSeparator() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-with-button.tsx b/apps/v4/examples/base/input-with-button.tsx
new file mode 100644
index 0000000000..e9495da383
--- /dev/null
+++ b/apps/v4/examples/base/input-with-button.tsx
@@ -0,0 +1,13 @@
+import { Button } from "@/examples/base/ui/button"
+import { Input } from "@/examples/base/ui/input"
+
+export default function InputWithButton() {
+ return (
+
+
+
+ Subscribe
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-with-label.tsx b/apps/v4/examples/base/input-with-label.tsx
new file mode 100644
index 0000000000..b644273f55
--- /dev/null
+++ b/apps/v4/examples/base/input-with-label.tsx
@@ -0,0 +1,11 @@
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+export default function InputWithLabel() {
+ return (
+
+ Email
+
+
+ )
+}
diff --git a/apps/v4/examples/base/input-with-text.tsx b/apps/v4/examples/base/input-with-text.tsx
new file mode 100644
index 0000000000..9fb53e4950
--- /dev/null
+++ b/apps/v4/examples/base/input-with-text.tsx
@@ -0,0 +1,12 @@
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+
+export default function InputWithText() {
+ return (
+
+
Email
+
+
Enter your email address.
+
+ )
+}
diff --git a/apps/v4/examples/base/item-avatar.tsx b/apps/v4/examples/base/item-avatar.tsx
new file mode 100644
index 0000000000..4a22e4f628
--- /dev/null
+++ b/apps/v4/examples/base/item-avatar.tsx
@@ -0,0 +1,75 @@
+import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { Plus } from "lucide-react"
+
+export function ItemAvatar() {
+ return (
+
+
-
+
+
+
+ ER
+
+
+
+ Evil Rabbit
+ Last seen 5 months ago
+
+
+
+
+
+
+
+
-
+
+
+
+
+ CN
+
+
+
+ LR
+
+
+
+ ER
+
+
+
+
+ No Team Members
+
+ Invite your team to collaborate on this project.
+
+
+
+
+ Invite
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-demo.tsx b/apps/v4/examples/base/item-demo.tsx
new file mode 100644
index 0000000000..6702b1bfff
--- /dev/null
+++ b/apps/v4/examples/base/item-demo.tsx
@@ -0,0 +1,41 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
+
+export function ItemDemo() {
+ return (
+
+ -
+
+ Basic Item
+
+ A simple item with title and description.
+
+
+
+
+ Action
+
+
+
+ }>
+
+
+
+
+ Your profile has been verified.
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-dropdown.tsx b/apps/v4/examples/base/item-dropdown.tsx
new file mode 100644
index 0000000000..c911dbd62d
--- /dev/null
+++ b/apps/v4/examples/base/item-dropdown.tsx
@@ -0,0 +1,73 @@
+"use client"
+
+import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
+import { Button } from "@/examples/base/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/examples/base/ui/dropdown-menu"
+import {
+ Item,
+ ItemContent,
+ ItemDescription,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { ChevronDownIcon } from "lucide-react"
+
+const people = [
+ {
+ username: "shadcn",
+ avatar: "https://github.com/shadcn.png",
+ email: "shadcn@vercel.com",
+ },
+ {
+ username: "maxleiter",
+ avatar: "https://github.com/maxleiter.png",
+ email: "maxleiter@vercel.com",
+ },
+ {
+ username: "evilrabbit",
+ avatar: "https://github.com/evilrabbit.png",
+ email: "evilrabbit@vercel.com",
+ },
+]
+
+export function ItemDropdown() {
+ return (
+
+
+ }
+ >
+ Select
+
+
+
+ {people.map((person) => (
+
+ -
+
+
+
+
+ {person.username.charAt(0)}
+
+
+
+
+ {person.username}
+ {person.email}
+
+
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-group.tsx b/apps/v4/examples/base/item-group.tsx
new file mode 100644
index 0000000000..1b3c8c894d
--- /dev/null
+++ b/apps/v4/examples/base/item-group.tsx
@@ -0,0 +1,63 @@
+import * as React from "react"
+import { Avatar, AvatarFallback, AvatarImage } from "@/examples/base/ui/avatar"
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemGroup,
+ ItemMedia,
+ ItemSeparator,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { PlusIcon } from "lucide-react"
+
+const people = [
+ {
+ username: "shadcn",
+ avatar: "https://github.com/shadcn.png",
+ email: "shadcn@vercel.com",
+ },
+ {
+ username: "maxleiter",
+ avatar: "https://github.com/maxleiter.png",
+ email: "maxleiter@vercel.com",
+ },
+ {
+ username: "evilrabbit",
+ avatar: "https://github.com/evilrabbit.png",
+ email: "evilrabbit@vercel.com",
+ },
+]
+
+export function ItemGroupExample() {
+ return (
+
+
+ {people.map((person, index) => (
+
+ -
+
+
+
+ {person.username.charAt(0)}
+
+
+
+ {person.username}
+ {person.email}
+
+
+
+
+
+
+
+ {index !== people.length - 1 && }
+
+ ))}
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-header.tsx b/apps/v4/examples/base/item-header.tsx
new file mode 100644
index 0000000000..b9170c6bd7
--- /dev/null
+++ b/apps/v4/examples/base/item-header.tsx
@@ -0,0 +1,59 @@
+import Image from "next/image"
+import {
+ Item,
+ ItemContent,
+ ItemDescription,
+ ItemGroup,
+ ItemHeader,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+
+const models = [
+ {
+ name: "v0-1.5-sm",
+ description: "Everyday tasks and UI generation.",
+ image:
+ "https://images.unsplash.com/photo-1650804068570-7fb2e3dbf888?q=80&w=640&auto=format&fit=crop",
+ credit: "Valeria Reverdo on Unsplash",
+ },
+ {
+ name: "v0-1.5-lg",
+ description: "Advanced thinking or reasoning.",
+ image:
+ "https://images.unsplash.com/photo-1610280777472-54133d004c8c?q=80&w=640&auto=format&fit=crop",
+ credit: "Michael Oeser on Unsplash",
+ },
+ {
+ name: "v0-2.0-mini",
+ description: "Open Source model for everyone.",
+ image:
+ "https://images.unsplash.com/photo-1602146057681-08560aee8cde?q=80&w=640&auto=format&fit=crop",
+ credit: "Cherry Laithang on Unsplash",
+ },
+]
+
+export function ItemHeaderDemo() {
+ return (
+
+
+ {models.map((model) => (
+ -
+
+
+
+
+ {model.name}
+ {model.description}
+
+
+ ))}
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-icon.tsx b/apps/v4/examples/base/item-icon.tsx
new file mode 100644
index 0000000000..efc712d43f
--- /dev/null
+++ b/apps/v4/examples/base/item-icon.tsx
@@ -0,0 +1,33 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { ShieldAlertIcon } from "lucide-react"
+
+export function ItemIcon() {
+ return (
+
+ -
+
+
+
+
+ Security Alert
+
+ New login detected from unknown device.
+
+
+
+
+ Review
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-image.tsx b/apps/v4/examples/base/item-image.tsx
new file mode 100644
index 0000000000..781cfc3519
--- /dev/null
+++ b/apps/v4/examples/base/item-image.tsx
@@ -0,0 +1,67 @@
+import Image from "next/image"
+import {
+ Item,
+ ItemContent,
+ ItemDescription,
+ ItemGroup,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+
+const music = [
+ {
+ title: "Midnight City Lights",
+ artist: "Neon Dreams",
+ album: "Electric Nights",
+ duration: "3:45",
+ },
+ {
+ title: "Coffee Shop Conversations",
+ artist: "The Morning Brew",
+ album: "Urban Stories",
+ duration: "4:05",
+ },
+ {
+ title: "Digital Rain",
+ artist: "Cyber Symphony",
+ album: "Binary Beats",
+ duration: "3:30",
+ },
+]
+
+export function ItemImage() {
+ return (
+
+
+ {music.map((song) => (
+ }
+ role="listitem"
+ >
+
+
+
+
+
+ {song.title} -{" "}
+ {song.album}
+
+ {song.artist}
+
+
+ {song.duration}
+
+
+ ))}
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-link.tsx b/apps/v4/examples/base/item-link.tsx
new file mode 100644
index 0000000000..f1270bd5a0
--- /dev/null
+++ b/apps/v4/examples/base/item-link.tsx
@@ -0,0 +1,40 @@
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react"
+
+export function ItemLink() {
+ return (
+
+ }>
+
+ Visit our documentation
+
+ Learn how to get started with our components.
+
+
+
+
+
+
+ }
+ >
+
+ External resource
+
+ Opens in a new tab with security attributes.
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-size.tsx b/apps/v4/examples/base/item-size.tsx
new file mode 100644
index 0000000000..ab67ab8db5
--- /dev/null
+++ b/apps/v4/examples/base/item-size.tsx
@@ -0,0 +1,41 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
+
+export function ItemSizeDemo() {
+ return (
+
+ -
+
+ Basic Item
+
+ A simple item with title and description.
+
+
+
+
+ Action
+
+
+
+ }>
+
+
+
+
+ Your profile has been verified.
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/item-variant.tsx b/apps/v4/examples/base/item-variant.tsx
new file mode 100644
index 0000000000..8bcb7522cc
--- /dev/null
+++ b/apps/v4/examples/base/item-variant.tsx
@@ -0,0 +1,54 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+
+export function ItemVariant() {
+ return (
+
+ -
+
+ Default Variant
+
+ Standard styling with subtle background and borders.
+
+
+
+
+ Open
+
+
+
+ -
+
+ Outline Variant
+
+ Outlined style with clear borders and transparent background.
+
+
+
+
+ Open
+
+
+
+ -
+
+ Muted Variant
+
+ Subdued appearance with muted colors for secondary content.
+
+
+
+
+ Open
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/kbd-button.tsx b/apps/v4/examples/base/kbd-button.tsx
new file mode 100644
index 0000000000..91640a2b6e
--- /dev/null
+++ b/apps/v4/examples/base/kbd-button.tsx
@@ -0,0 +1,15 @@
+import { Button } from "@/examples/base/ui/button"
+import { Kbd } from "@/examples/base/ui/kbd"
+
+export default function KbdButton() {
+ return (
+
+
+ Accept ⏎
+
+
+ Cancel Esc
+
+
+ )
+}
diff --git a/apps/v4/examples/base/kbd-demo.tsx b/apps/v4/examples/base/kbd-demo.tsx
new file mode 100644
index 0000000000..7520dcb2b6
--- /dev/null
+++ b/apps/v4/examples/base/kbd-demo.tsx
@@ -0,0 +1,19 @@
+import { Kbd, KbdGroup } from "@/examples/base/ui/kbd"
+
+export default function KbdDemo() {
+ return (
+
+
+ ⌘
+ ⇧
+ ⌥
+ ⌃
+
+
+ Ctrl
+ +
+ B
+
+
+ )
+}
diff --git a/apps/v4/examples/base/kbd-group.tsx b/apps/v4/examples/base/kbd-group.tsx
new file mode 100644
index 0000000000..02a02dd661
--- /dev/null
+++ b/apps/v4/examples/base/kbd-group.tsx
@@ -0,0 +1,16 @@
+import { Kbd, KbdGroup } from "@/examples/base/ui/kbd"
+
+export default function KbdGroupExample() {
+ return (
+
+
+ Use{" "}
+
+ Ctrl + B
+ Ctrl + K
+ {" "}
+ to open the command palette
+
+
+ )
+}
diff --git a/apps/v4/examples/base/kbd-input-group.tsx b/apps/v4/examples/base/kbd-input-group.tsx
new file mode 100644
index 0000000000..ba9f759a65
--- /dev/null
+++ b/apps/v4/examples/base/kbd-input-group.tsx
@@ -0,0 +1,24 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupInput,
+} from "@/examples/base/ui/input-group"
+import { Kbd } from "@/examples/base/ui/kbd"
+import { SearchIcon } from "lucide-react"
+
+export default function KbdInputGroup() {
+ return (
+
+
+
+
+
+
+
+ ⌘
+ K
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/kbd-tooltip.tsx b/apps/v4/examples/base/kbd-tooltip.tsx
new file mode 100644
index 0000000000..89ba8adcca
--- /dev/null
+++ b/apps/v4/examples/base/kbd-tooltip.tsx
@@ -0,0 +1,41 @@
+import { Button } from "@/examples/base/ui/button"
+import { ButtonGroup } from "@/examples/base/ui/button-group"
+import { Kbd, KbdGroup } from "@/examples/base/ui/kbd"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/examples/base/ui/tooltip"
+
+export default function KbdTooltip() {
+ return (
+
+
+
+ }>
+ Save
+
+
+
+ Save Changes S
+
+
+
+
+ }>
+ Print
+
+
+
+ Print Document{" "}
+
+ Ctrl
+ P
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/label-demo.tsx b/apps/v4/examples/base/label-demo.tsx
new file mode 100644
index 0000000000..a8d60ec1b6
--- /dev/null
+++ b/apps/v4/examples/base/label-demo.tsx
@@ -0,0 +1,13 @@
+import { Checkbox } from "@/examples/base/ui/checkbox"
+import { Label } from "@/examples/base/ui/label"
+
+export default function LabelDemo() {
+ return (
+
+
+
+ Accept terms and conditions
+
+
+ )
+}
diff --git a/apps/v4/registry/base-nova/lib/utils.ts b/apps/v4/examples/base/lib/utils.ts
similarity index 100%
rename from apps/v4/registry/base-nova/lib/utils.ts
rename to apps/v4/examples/base/lib/utils.ts
diff --git a/apps/v4/examples/base/menubar-demo.tsx b/apps/v4/examples/base/menubar-demo.tsx
new file mode 100644
index 0000000000..8feaaffc4f
--- /dev/null
+++ b/apps/v4/examples/base/menubar-demo.tsx
@@ -0,0 +1,138 @@
+import {
+ Menubar,
+ MenubarCheckboxItem,
+ MenubarContent,
+ MenubarGroup,
+ MenubarItem,
+ MenubarMenu,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarSeparator,
+ MenubarShortcut,
+ MenubarSub,
+ MenubarSubContent,
+ MenubarSubTrigger,
+ MenubarTrigger,
+} from "@/examples/base/ui/menubar"
+
+export default function MenubarDemo() {
+ return (
+
+
+ File
+
+
+
+ New Tab ⌘T
+
+
+ New Window ⌘N
+
+ New Incognito Window
+
+
+
+
+ Share
+
+
+ Email link
+ Messages
+ Notes
+
+
+
+
+
+
+
+ Print... ⌘P
+
+
+
+
+
+ Edit
+
+
+
+ Undo ⌘Z
+
+
+ Redo ⇧⌘Z
+
+
+
+
+
+ Find
+
+
+ Search the web
+
+
+
+ Find...
+ Find Next
+ Find Previous
+
+
+
+
+
+
+ Cut
+ Copy
+ Paste
+
+
+
+
+ View
+
+
+ Always Show Bookmarks Bar
+
+ Always Show Full URLs
+
+
+
+
+
+ Reload ⌘R
+
+
+ Force Reload ⇧⌘R
+
+
+
+
+ Toggle Fullscreen
+
+
+
+ Hide Sidebar
+
+
+
+
+ Profiles
+
+
+ Andy
+ Benoit
+ Luis
+
+
+
+ Edit...
+
+
+
+ Add Profile...
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/native-select-demo.tsx b/apps/v4/examples/base/native-select-demo.tsx
new file mode 100644
index 0000000000..96ac327e91
--- /dev/null
+++ b/apps/v4/examples/base/native-select-demo.tsx
@@ -0,0 +1,16 @@
+import {
+ NativeSelect,
+ NativeSelectOption,
+} from "@/examples/base/ui/native-select"
+
+export default function NativeSelectDemo() {
+ return (
+
+ Select status
+ Todo
+ In Progress
+ Done
+ Cancelled
+
+ )
+}
diff --git a/apps/v4/examples/base/native-select-disabled.tsx b/apps/v4/examples/base/native-select-disabled.tsx
new file mode 100644
index 0000000000..698af00d5a
--- /dev/null
+++ b/apps/v4/examples/base/native-select-disabled.tsx
@@ -0,0 +1,16 @@
+import {
+ NativeSelect,
+ NativeSelectOption,
+} from "@/examples/base/ui/native-select"
+
+export default function NativeSelectDisabled() {
+ return (
+
+ Select priority
+ Low
+ Medium
+ High
+ Critical
+
+ )
+}
diff --git a/apps/v4/examples/base/native-select-groups.tsx b/apps/v4/examples/base/native-select-groups.tsx
new file mode 100644
index 0000000000..fe055528cb
--- /dev/null
+++ b/apps/v4/examples/base/native-select-groups.tsx
@@ -0,0 +1,38 @@
+import {
+ NativeSelect,
+ NativeSelectOptGroup,
+ NativeSelectOption,
+} from "@/examples/base/ui/native-select"
+
+export default function NativeSelectGroups() {
+ return (
+
+ Select department
+
+ Frontend
+ Backend
+ DevOps
+
+
+ Sales Rep
+
+ Account Manager
+
+
+ Sales Director
+
+
+
+
+ Customer Support
+
+
+ Product Manager
+
+
+ Operations Manager
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/native-select-invalid.tsx b/apps/v4/examples/base/native-select-invalid.tsx
new file mode 100644
index 0000000000..79741484a5
--- /dev/null
+++ b/apps/v4/examples/base/native-select-invalid.tsx
@@ -0,0 +1,16 @@
+import {
+ NativeSelect,
+ NativeSelectOption,
+} from "@/examples/base/ui/native-select"
+
+export default function NativeSelectInvalid() {
+ return (
+
+ Select role
+ Admin
+ Editor
+ Viewer
+ Guest
+
+ )
+}
diff --git a/apps/v4/examples/base/navigation-menu-demo.tsx b/apps/v4/examples/base/navigation-menu-demo.tsx
new file mode 100644
index 0000000000..eb4f258099
--- /dev/null
+++ b/apps/v4/examples/base/navigation-menu-demo.tsx
@@ -0,0 +1,217 @@
+"use client"
+
+import * as React from "react"
+import Link from "next/link"
+import { useIsMobile } from "@/examples/base/hooks/use-mobile"
+import {
+ NavigationMenu,
+ NavigationMenuContent,
+ NavigationMenuItem,
+ NavigationMenuLink,
+ NavigationMenuList,
+ NavigationMenuTrigger,
+ navigationMenuTriggerStyle,
+} from "@/examples/base/ui/navigation-menu"
+import { CircleCheckIcon, CircleHelpIcon, CircleIcon } from "lucide-react"
+
+const components: { title: string; href: string; description: string }[] = [
+ {
+ title: "Alert Dialog",
+ href: "/docs/primitives/alert-dialog",
+ description:
+ "A modal dialog that interrupts the user with important content and expects a response.",
+ },
+ {
+ title: "Hover Card",
+ href: "/docs/primitives/hover-card",
+ description:
+ "For sighted users to preview content available behind a link.",
+ },
+ {
+ title: "Progress",
+ href: "/docs/primitives/progress",
+ description:
+ "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
+ },
+ {
+ title: "Scroll-area",
+ href: "/docs/primitives/scroll-area",
+ description: "Visually or semantically separates content.",
+ },
+ {
+ title: "Tabs",
+ href: "/docs/primitives/tabs",
+ description:
+ "A set of layered sections of content—known as tab panels—that are displayed one at a time.",
+ },
+ {
+ title: "Tooltip",
+ href: "/docs/primitives/tooltip",
+ description:
+ "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
+ },
+]
+
+export default function NavigationMenuDemo() {
+ const isMobile = useIsMobile()
+
+ return (
+
+
+
+ Home
+
+
+
+
+
+ Components
+
+
+ {components.map((component) => (
+
+ {component.description}
+
+ ))}
+
+
+
+
+ }
+ >
+ Docs
+
+
+
+ List
+
+
+
+ }>
+ Components
+
+ Browse all components in the library.
+
+
+ }>
+ Documentation
+
+ Learn how to use the library.
+
+
+ }>
+ Blog
+
+ Read our latest blog posts.
+
+
+
+
+
+
+
+ Simple
+
+
+
+ }>
+ Components
+
+ }>
+ Documentation
+
+ }>
+ Blocks
+
+
+
+
+
+
+ With Icon
+
+
+
+
+ }
+ >
+
+ Backlog
+
+
+ }
+ >
+
+ To Do
+
+
+ }
+ >
+
+ Done
+
+
+
+
+
+
+
+ )
+}
+
+function ListItem({
+ title,
+ children,
+ href,
+ ...props
+}: React.ComponentPropsWithoutRef<"li"> & { href: string }) {
+ return (
+
+ }>
+ {title}
+
+ {children}
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/pagination-demo.tsx b/apps/v4/examples/base/pagination-demo.tsx
new file mode 100644
index 0000000000..fa24bfca35
--- /dev/null
+++ b/apps/v4/examples/base/pagination-demo.tsx
@@ -0,0 +1,38 @@
+import {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/examples/base/ui/pagination"
+
+export default function PaginationDemo() {
+ return (
+
+
+
+
+
+
+ 1
+
+
+
+ 2
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/popover-demo.tsx b/apps/v4/examples/base/popover-demo.tsx
new file mode 100644
index 0000000000..9a67fdc302
--- /dev/null
+++ b/apps/v4/examples/base/popover-demo.tsx
@@ -0,0 +1,62 @@
+import { Button } from "@/examples/base/ui/button"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/examples/base/ui/popover"
+
+export default function PopoverDemo() {
+ return (
+
+ }>
+ Open popover
+
+
+
+
+
Dimensions
+
+ Set the dimensions for the layer.
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/progress-demo.tsx b/apps/v4/examples/base/progress-demo.tsx
new file mode 100644
index 0000000000..22b0833a26
--- /dev/null
+++ b/apps/v4/examples/base/progress-demo.tsx
@@ -0,0 +1,15 @@
+"use client"
+
+import * as React from "react"
+import { Progress } from "@/examples/base/ui/progress"
+
+export default function ProgressDemo() {
+ const [progress, setProgress] = React.useState(13)
+
+ React.useEffect(() => {
+ const timer = setTimeout(() => setProgress(66), 500)
+ return () => clearTimeout(timer)
+ }, [])
+
+ return
+}
diff --git a/apps/v4/examples/base/radio-group-demo.tsx b/apps/v4/examples/base/radio-group-demo.tsx
new file mode 100644
index 0000000000..cd729f6340
--- /dev/null
+++ b/apps/v4/examples/base/radio-group-demo.tsx
@@ -0,0 +1,21 @@
+import { Label } from "@/examples/base/ui/label"
+import { RadioGroup, RadioGroupItem } from "@/examples/base/ui/radio-group"
+
+export default function RadioGroupDemo() {
+ return (
+
+
+
+ Default
+
+
+
+ Comfortable
+
+
+
+ Compact
+
+
+ )
+}
diff --git a/apps/v4/examples/base/resizable-demo-with-handle.tsx b/apps/v4/examples/base/resizable-demo-with-handle.tsx
new file mode 100644
index 0000000000..abcfc95f9c
--- /dev/null
+++ b/apps/v4/examples/base/resizable-demo-with-handle.tsx
@@ -0,0 +1,36 @@
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/examples/base/ui/resizable"
+
+export default function ResizableDemo() {
+ return (
+
+
+
+ One
+
+
+
+
+
+
+
+ Two
+
+
+
+
+
+ Three
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/resizable-demo.tsx b/apps/v4/examples/base/resizable-demo.tsx
new file mode 100644
index 0000000000..3a51727531
--- /dev/null
+++ b/apps/v4/examples/base/resizable-demo.tsx
@@ -0,0 +1,36 @@
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/examples/base/ui/resizable"
+
+export default function ResizableDemo() {
+ return (
+
+
+
+ One
+
+
+
+
+
+
+
+ Two
+
+
+
+
+
+ Three
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/resizable-handle.tsx b/apps/v4/examples/base/resizable-handle.tsx
new file mode 100644
index 0000000000..f62fc7b1be
--- /dev/null
+++ b/apps/v4/examples/base/resizable-handle.tsx
@@ -0,0 +1,26 @@
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/examples/base/ui/resizable"
+
+export default function ResizableHandleDemo() {
+ return (
+
+
+
+ Sidebar
+
+
+
+
+
+ Content
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/resizable-vertical.tsx b/apps/v4/examples/base/resizable-vertical.tsx
new file mode 100644
index 0000000000..d02c19c454
--- /dev/null
+++ b/apps/v4/examples/base/resizable-vertical.tsx
@@ -0,0 +1,26 @@
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/examples/base/ui/resizable"
+
+export default function ResizableDemo() {
+ return (
+
+
+
+ Header
+
+
+
+
+
+ Content
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/scroll-area-demo.tsx b/apps/v4/examples/base/scroll-area-demo.tsx
new file mode 100644
index 0000000000..ca12bc82bc
--- /dev/null
+++ b/apps/v4/examples/base/scroll-area-demo.tsx
@@ -0,0 +1,23 @@
+import * as React from "react"
+import { ScrollArea } from "@/examples/base/ui/scroll-area"
+import { Separator } from "@/examples/base/ui/separator"
+
+const tags = Array.from({ length: 50 }).map(
+ (_, i, a) => `v1.2.0-beta.${a.length - i}`
+)
+
+export function ScrollAreaDemo() {
+ return (
+
+
+
Tags
+ {tags.map((tag) => (
+
+ {tag}
+
+
+ ))}
+
+
+ )
+}
diff --git a/apps/v4/examples/base/scroll-area-horizontal-demo.tsx b/apps/v4/examples/base/scroll-area-horizontal-demo.tsx
new file mode 100644
index 0000000000..af6e765d8f
--- /dev/null
+++ b/apps/v4/examples/base/scroll-area-horizontal-demo.tsx
@@ -0,0 +1,52 @@
+import * as React from "react"
+import Image from "next/image"
+import { ScrollArea, ScrollBar } from "@/examples/base/ui/scroll-area"
+
+export interface Artwork {
+ artist: string
+ art: string
+}
+
+export const works: Artwork[] = [
+ {
+ artist: "Ornella Binni",
+ art: "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80",
+ },
+ {
+ artist: "Tom Byrom",
+ art: "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80",
+ },
+ {
+ artist: "Vladimir Malyavko",
+ art: "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80",
+ },
+]
+
+export function ScrollAreaHorizontalDemo() {
+ return (
+
+
+ {works.map((artwork) => (
+
+
+
+
+
+ Photo by{" "}
+
+ {artwork.artist}
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/select-demo.tsx b/apps/v4/examples/base/select-demo.tsx
new file mode 100644
index 0000000000..3d8e2ba9b8
--- /dev/null
+++ b/apps/v4/examples/base/select-demo.tsx
@@ -0,0 +1,38 @@
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/examples/base/ui/select"
+
+const items = [
+ { label: "Select a fruit", value: null },
+ { label: "Apple", value: "apple" },
+ { label: "Banana", value: "banana" },
+ { label: "Blueberry", value: "blueberry" },
+ { label: "Grapes", value: "grapes" },
+ { label: "Pineapple", value: "pineapple" },
+]
+
+export function SelectDemo() {
+ return (
+
+
+
+
+
+
+ Fruits
+ {items.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/select-scrollable.tsx b/apps/v4/examples/base/select-scrollable.tsx
new file mode 100644
index 0000000000..e605c3989c
--- /dev/null
+++ b/apps/v4/examples/base/select-scrollable.tsx
@@ -0,0 +1,112 @@
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/examples/base/ui/select"
+
+const northAmerica = [
+ { label: "Eastern Standard Time (EST)", value: "est" },
+ { label: "Central Standard Time (CST)", value: "cst" },
+ { label: "Mountain Standard Time (MST)", value: "mst" },
+ { label: "Pacific Standard Time (PST)", value: "pst" },
+ { label: "Alaska Standard Time (AKST)", value: "akst" },
+ { label: "Hawaii Standard Time (HST)", value: "hst" },
+]
+
+const europeAfrica = [
+ { label: "Greenwich Mean Time (GMT)", value: "gmt" },
+ { label: "Central European Time (CET)", value: "cet" },
+ { label: "Eastern European Time (EET)", value: "eet" },
+ { label: "Western European Summer Time (WEST)", value: "west" },
+ { label: "Central Africa Time (CAT)", value: "cat" },
+ { label: "East Africa Time (EAT)", value: "eat" },
+]
+
+const asia = [
+ { label: "Moscow Time (MSK)", value: "msk" },
+ { label: "India Standard Time (IST)", value: "ist" },
+ { label: "China Standard Time (CST)", value: "cst_china" },
+ { label: "Japan Standard Time (JST)", value: "jst" },
+ { label: "Korea Standard Time (KST)", value: "kst" },
+ { label: "Indonesia Central Standard Time (WITA)", value: "ist_indonesia" },
+]
+
+const australiaPacific = [
+ { label: "Australian Western Standard Time (AWST)", value: "awst" },
+ { label: "Australian Central Standard Time (ACST)", value: "acst" },
+ { label: "Australian Eastern Standard Time (AEST)", value: "aest" },
+ { label: "New Zealand Standard Time (NZST)", value: "nzst" },
+ { label: "Fiji Time (FJT)", value: "fjt" },
+]
+
+const southAmerica = [
+ { label: "Argentina Time (ART)", value: "art" },
+ { label: "Bolivia Time (BOT)", value: "bot" },
+ { label: "Brasilia Time (BRT)", value: "brt" },
+ { label: "Chile Standard Time (CLT)", value: "clt" },
+]
+
+const items = [
+ { label: "Select a timezone", value: null },
+ ...northAmerica,
+ ...europeAfrica,
+ ...asia,
+ ...australiaPacific,
+ ...southAmerica,
+]
+
+export function SelectScrollable() {
+ return (
+
+
+
+
+
+
+ North America
+ {northAmerica.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ Europe & Africa
+ {europeAfrica.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ Asia
+ {asia.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ Australia & Pacific
+ {australiaPacific.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ South America
+ {southAmerica.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/separator-demo.tsx b/apps/v4/examples/base/separator-demo.tsx
new file mode 100644
index 0000000000..35c5737d0c
--- /dev/null
+++ b/apps/v4/examples/base/separator-demo.tsx
@@ -0,0 +1,22 @@
+import { Separator } from "@/examples/base/ui/separator"
+
+export default function SeparatorDemo() {
+ return (
+
+
+
Radix Primitives
+
+ An open-source UI component library.
+
+
+
+
+
Blog
+
+
Docs
+
+
Source
+
+
+ )
+}
diff --git a/apps/v4/examples/base/sheet-demo.tsx b/apps/v4/examples/base/sheet-demo.tsx
new file mode 100644
index 0000000000..3b7e1f166c
--- /dev/null
+++ b/apps/v4/examples/base/sheet-demo.tsx
@@ -0,0 +1,43 @@
+import { Button } from "@/examples/base/ui/button"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+import {
+ Sheet,
+ SheetClose,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+ SheetTrigger,
+} from "@/examples/base/ui/sheet"
+
+export default function SheetDemo() {
+ return (
+
+ }>Open
+
+
+ Edit profile
+
+ Make changes to your profile here. Click save when you're done.
+
+
+
+
+ Save changes
+ }>Close
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/sheet-side.tsx b/apps/v4/examples/base/sheet-side.tsx
new file mode 100644
index 0000000000..350b4a067c
--- /dev/null
+++ b/apps/v4/examples/base/sheet-side.tsx
@@ -0,0 +1,61 @@
+"use client"
+
+import { Button } from "@/examples/base/ui/button"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+import {
+ Sheet,
+ SheetClose,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+ SheetTrigger,
+} from "@/examples/base/ui/sheet"
+
+const SHEET_SIDES = ["top", "right", "bottom", "left"] as const
+
+type SheetSide = (typeof SHEET_SIDES)[number]
+
+export default function SheetSide() {
+ return (
+
+ {SHEET_SIDES.map((side) => (
+
+ }>
+ {side}
+
+
+
+ Edit profile
+
+ Make changes to your profile here. Click save when you're
+ done.
+
+
+
+
+ }>
+ Save changes
+
+
+
+
+ ))}
+
+ )
+}
diff --git a/apps/v4/examples/base/skeleton-card.tsx b/apps/v4/examples/base/skeleton-card.tsx
new file mode 100644
index 0000000000..6f605157d5
--- /dev/null
+++ b/apps/v4/examples/base/skeleton-card.tsx
@@ -0,0 +1,13 @@
+import { Skeleton } from "@/examples/base/ui/skeleton"
+
+export function SkeletonCard() {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/skeleton-demo.tsx b/apps/v4/examples/base/skeleton-demo.tsx
new file mode 100644
index 0000000000..e4575b79e3
--- /dev/null
+++ b/apps/v4/examples/base/skeleton-demo.tsx
@@ -0,0 +1,13 @@
+import { Skeleton } from "@/examples/base/ui/skeleton"
+
+export function SkeletonDemo() {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/slider-demo.tsx b/apps/v4/examples/base/slider-demo.tsx
new file mode 100644
index 0000000000..8c9de95809
--- /dev/null
+++ b/apps/v4/examples/base/slider-demo.tsx
@@ -0,0 +1,17 @@
+import { Slider } from "@/examples/base/ui/slider"
+
+import { cn } from "@/lib/utils"
+
+type SliderProps = React.ComponentProps
+
+export function SliderDemo({ className, ...props }: SliderProps) {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/sonner-demo.tsx b/apps/v4/examples/base/sonner-demo.tsx
new file mode 100644
index 0000000000..c042261093
--- /dev/null
+++ b/apps/v4/examples/base/sonner-demo.tsx
@@ -0,0 +1,23 @@
+"use client"
+
+import { Button } from "@/examples/base/ui/button"
+import { toast } from "sonner"
+
+export function SonnerDemo() {
+ return (
+
+ toast("Event has been created", {
+ description: "Sunday, December 03, 2023 at 9:00 AM",
+ action: {
+ label: "Undo",
+ onClick: () => console.log("Undo"),
+ },
+ })
+ }
+ >
+ Show Toast
+
+ )
+}
diff --git a/apps/v4/examples/base/sonner-types.tsx b/apps/v4/examples/base/sonner-types.tsx
new file mode 100644
index 0000000000..5a435823cc
--- /dev/null
+++ b/apps/v4/examples/base/sonner-types.tsx
@@ -0,0 +1,60 @@
+"use client"
+
+import { Button } from "@/examples/base/ui/button"
+import { toast } from "sonner"
+
+export function SonnerTypes() {
+ return (
+
+ toast("Event has been created")}>
+ Default
+
+ toast.success("Event has been created")}
+ >
+ Success
+
+
+ toast.info("Be at the area 10 minutes before the event time")
+ }
+ >
+ Info
+
+
+ toast.warning("Event start time cannot be earlier than 8am")
+ }
+ >
+ Warning
+
+ toast.error("Event has not been created")}
+ >
+ Error
+
+ {
+ toast.promise<{ name: string }>(
+ () =>
+ new Promise((resolve) =>
+ setTimeout(() => resolve({ name: "Event" }), 2000)
+ ),
+ {
+ loading: "Loading...",
+ success: (data) => `${data.name} has been created`,
+ error: "Error",
+ }
+ )
+ }}
+ >
+ Promise
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-badge.tsx b/apps/v4/examples/base/spinner-badge.tsx
new file mode 100644
index 0000000000..a55d6840ac
--- /dev/null
+++ b/apps/v4/examples/base/spinner-badge.tsx
@@ -0,0 +1,21 @@
+import { Badge } from "@/examples/base/ui/badge"
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerBadge() {
+ return (
+
+
+
+ Syncing
+
+
+
+ Updating
+
+
+
+ Processing
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-basic.tsx b/apps/v4/examples/base/spinner-basic.tsx
new file mode 100644
index 0000000000..e4fce26203
--- /dev/null
+++ b/apps/v4/examples/base/spinner-basic.tsx
@@ -0,0 +1,9 @@
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerBasic() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-button.tsx b/apps/v4/examples/base/spinner-button.tsx
new file mode 100644
index 0000000000..f9857a5cf2
--- /dev/null
+++ b/apps/v4/examples/base/spinner-button.tsx
@@ -0,0 +1,21 @@
+import { Button } from "@/examples/base/ui/button"
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerButton() {
+ return (
+
+
+
+ Loading...
+
+
+
+ Please wait
+
+
+
+ Processing
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-color.tsx b/apps/v4/examples/base/spinner-color.tsx
new file mode 100644
index 0000000000..8e077d9a45
--- /dev/null
+++ b/apps/v4/examples/base/spinner-color.tsx
@@ -0,0 +1,13 @@
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerColor() {
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/registry/base-nova/demo/spinner-custom.tsx b/apps/v4/examples/base/spinner-custom.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/spinner-custom.tsx
rename to apps/v4/examples/base/spinner-custom.tsx
diff --git a/apps/v4/examples/base/spinner-demo.tsx b/apps/v4/examples/base/spinner-demo.tsx
new file mode 100644
index 0000000000..183498142c
--- /dev/null
+++ b/apps/v4/examples/base/spinner-demo.tsx
@@ -0,0 +1,25 @@
+import {
+ Item,
+ ItemContent,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerDemo() {
+ return (
+
+ -
+
+
+
+
+ Processing payment...
+
+
+ $100.00
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-empty.tsx b/apps/v4/examples/base/spinner-empty.tsx
new file mode 100644
index 0000000000..a30b7b00f7
--- /dev/null
+++ b/apps/v4/examples/base/spinner-empty.tsx
@@ -0,0 +1,31 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Empty,
+ EmptyContent,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/examples/base/ui/empty"
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerEmpty() {
+ return (
+
+
+
+
+
+ Processing your request
+
+ Please wait while we process your request. Do not refresh the page.
+
+
+
+
+ Cancel
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-input-group.tsx b/apps/v4/examples/base/spinner-input-group.tsx
new file mode 100644
index 0000000000..8bf8751590
--- /dev/null
+++ b/apps/v4/examples/base/spinner-input-group.tsx
@@ -0,0 +1,32 @@
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupInput,
+ InputGroupTextarea,
+} from "@/examples/base/ui/input-group"
+import { Spinner } from "@/examples/base/ui/spinner"
+import { ArrowUpIcon } from "lucide-react"
+
+export function SpinnerInputGroup() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ Validating...
+
+
+ Send
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-item.tsx b/apps/v4/examples/base/spinner-item.tsx
new file mode 100644
index 0000000000..3404843a4c
--- /dev/null
+++ b/apps/v4/examples/base/spinner-item.tsx
@@ -0,0 +1,36 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemFooter,
+ ItemMedia,
+ ItemTitle,
+} from "@/examples/base/ui/item"
+import { Progress } from "@/examples/base/ui/progress"
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerItem() {
+ return (
+
+
-
+
+
+
+
+ Downloading...
+ 129 MB / 1000 MB
+
+
+
+ Cancel
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/spinner-size.tsx b/apps/v4/examples/base/spinner-size.tsx
new file mode 100644
index 0000000000..f64ce64104
--- /dev/null
+++ b/apps/v4/examples/base/spinner-size.tsx
@@ -0,0 +1,12 @@
+import { Spinner } from "@/examples/base/ui/spinner"
+
+export function SpinnerSize() {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/switch-demo.tsx b/apps/v4/examples/base/switch-demo.tsx
new file mode 100644
index 0000000000..26e4d4d7f4
--- /dev/null
+++ b/apps/v4/examples/base/switch-demo.tsx
@@ -0,0 +1,11 @@
+import { Label } from "@/examples/base/ui/label"
+import { Switch } from "@/examples/base/ui/switch"
+
+export function SwitchDemo() {
+ return (
+
+
+ Airplane Mode
+
+ )
+}
diff --git a/apps/v4/examples/base/table-demo.tsx b/apps/v4/examples/base/table-demo.tsx
new file mode 100644
index 0000000000..7a2c3c9a52
--- /dev/null
+++ b/apps/v4/examples/base/table-demo.tsx
@@ -0,0 +1,87 @@
+import {
+ Table,
+ TableBody,
+ TableCaption,
+ TableCell,
+ TableFooter,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/examples/base/ui/table"
+
+const invoices = [
+ {
+ invoice: "INV001",
+ paymentStatus: "Paid",
+ totalAmount: "$250.00",
+ paymentMethod: "Credit Card",
+ },
+ {
+ invoice: "INV002",
+ paymentStatus: "Pending",
+ totalAmount: "$150.00",
+ paymentMethod: "PayPal",
+ },
+ {
+ invoice: "INV003",
+ paymentStatus: "Unpaid",
+ totalAmount: "$350.00",
+ paymentMethod: "Bank Transfer",
+ },
+ {
+ invoice: "INV004",
+ paymentStatus: "Paid",
+ totalAmount: "$450.00",
+ paymentMethod: "Credit Card",
+ },
+ {
+ invoice: "INV005",
+ paymentStatus: "Paid",
+ totalAmount: "$550.00",
+ paymentMethod: "PayPal",
+ },
+ {
+ invoice: "INV006",
+ paymentStatus: "Pending",
+ totalAmount: "$200.00",
+ paymentMethod: "Bank Transfer",
+ },
+ {
+ invoice: "INV007",
+ paymentStatus: "Unpaid",
+ totalAmount: "$300.00",
+ paymentMethod: "Credit Card",
+ },
+]
+
+export function TableDemo() {
+ return (
+
+ A list of your recent invoices.
+
+
+ Invoice
+ Status
+ Method
+ Amount
+
+
+
+ {invoices.map((invoice) => (
+
+ {invoice.invoice}
+ {invoice.paymentStatus}
+ {invoice.paymentMethod}
+ {invoice.totalAmount}
+
+ ))}
+
+
+
+ Total
+ $2,500.00
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/tabs-demo.tsx b/apps/v4/examples/base/tabs-demo.tsx
new file mode 100644
index 0000000000..4621072846
--- /dev/null
+++ b/apps/v4/examples/base/tabs-demo.tsx
@@ -0,0 +1,78 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/examples/base/ui/card"
+import { Input } from "@/examples/base/ui/input"
+import { Label } from "@/examples/base/ui/label"
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/examples/base/ui/tabs"
+
+export function TabsDemo() {
+ return (
+
+ )
+}
diff --git a/apps/v4/examples/base/textarea-demo.tsx b/apps/v4/examples/base/textarea-demo.tsx
new file mode 100644
index 0000000000..05241952b4
--- /dev/null
+++ b/apps/v4/examples/base/textarea-demo.tsx
@@ -0,0 +1,5 @@
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function TextareaDemo() {
+ return
+}
diff --git a/apps/v4/examples/base/textarea-disabled.tsx b/apps/v4/examples/base/textarea-disabled.tsx
new file mode 100644
index 0000000000..4ae0b44edc
--- /dev/null
+++ b/apps/v4/examples/base/textarea-disabled.tsx
@@ -0,0 +1,5 @@
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function TextareaDisabled() {
+ return
+}
diff --git a/apps/v4/examples/base/textarea-with-button.tsx b/apps/v4/examples/base/textarea-with-button.tsx
new file mode 100644
index 0000000000..985153caf4
--- /dev/null
+++ b/apps/v4/examples/base/textarea-with-button.tsx
@@ -0,0 +1,11 @@
+import { Button } from "@/examples/base/ui/button"
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function TextareaWithButton() {
+ return (
+
+
+ Send message
+
+ )
+}
diff --git a/apps/v4/examples/base/textarea-with-label.tsx b/apps/v4/examples/base/textarea-with-label.tsx
new file mode 100644
index 0000000000..a49765040e
--- /dev/null
+++ b/apps/v4/examples/base/textarea-with-label.tsx
@@ -0,0 +1,11 @@
+import { Label } from "@/examples/base/ui/label"
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function TextareaWithLabel() {
+ return (
+
+ Your message
+
+
+ )
+}
diff --git a/apps/v4/examples/base/textarea-with-text.tsx b/apps/v4/examples/base/textarea-with-text.tsx
new file mode 100644
index 0000000000..1358b27aa6
--- /dev/null
+++ b/apps/v4/examples/base/textarea-with-text.tsx
@@ -0,0 +1,14 @@
+import { Label } from "@/examples/base/ui/label"
+import { Textarea } from "@/examples/base/ui/textarea"
+
+export default function TextareaWithText() {
+ return (
+
+
Your Message
+
+
+ Your message will be copied to the support team.
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-demo.tsx b/apps/v4/examples/base/toggle-demo.tsx
new file mode 100644
index 0000000000..60983c6552
--- /dev/null
+++ b/apps/v4/examples/base/toggle-demo.tsx
@@ -0,0 +1,16 @@
+import { Toggle } from "@/examples/base/ui/toggle"
+import { BookmarkIcon } from "lucide-react"
+
+export function ToggleDemo() {
+ return (
+
+
+ Bookmark
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-disabled.tsx b/apps/v4/examples/base/toggle-disabled.tsx
new file mode 100644
index 0000000000..3cba27b694
--- /dev/null
+++ b/apps/v4/examples/base/toggle-disabled.tsx
@@ -0,0 +1,10 @@
+import { Toggle } from "@/examples/base/ui/toggle"
+import { Underline } from "lucide-react"
+
+export function ToggleDisabled() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-demo.tsx b/apps/v4/examples/base/toggle-group-demo.tsx
new file mode 100644
index 0000000000..e285bb9d0a
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-demo.tsx
@@ -0,0 +1,18 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { Bold, Italic, Underline } from "lucide-react"
+
+export function ToggleGroupDemo() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-disabled.tsx b/apps/v4/examples/base/toggle-group-disabled.tsx
new file mode 100644
index 0000000000..4b059906c5
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-disabled.tsx
@@ -0,0 +1,18 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { Bold, Italic, Underline } from "lucide-react"
+
+export function ToggleGroupDisabled() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-lg.tsx b/apps/v4/examples/base/toggle-group-lg.tsx
new file mode 100644
index 0000000000..299e7acb75
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-lg.tsx
@@ -0,0 +1,18 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { Bold, Italic, Underline } from "lucide-react"
+
+export function ToggleGroupLg() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-outline.tsx b/apps/v4/examples/base/toggle-group-outline.tsx
new file mode 100644
index 0000000000..009c353a8c
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-outline.tsx
@@ -0,0 +1,18 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { Bold, Italic, Underline } from "lucide-react"
+
+export function ToggleGroupOutline() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-single.tsx b/apps/v4/examples/base/toggle-group-single.tsx
new file mode 100644
index 0000000000..bad01c67c0
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-single.tsx
@@ -0,0 +1,18 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { Bold, Italic, Underline } from "lucide-react"
+
+export function ToggleGroupSingle() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-sm.tsx b/apps/v4/examples/base/toggle-group-sm.tsx
new file mode 100644
index 0000000000..f1faad38f7
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-sm.tsx
@@ -0,0 +1,18 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { Bold, Italic, Underline } from "lucide-react"
+
+export function ToggleGroupSm() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-group-spacing.tsx b/apps/v4/examples/base/toggle-group-spacing.tsx
new file mode 100644
index 0000000000..8b8020d545
--- /dev/null
+++ b/apps/v4/examples/base/toggle-group-spacing.tsx
@@ -0,0 +1,33 @@
+import { ToggleGroup, ToggleGroupItem } from "@/examples/base/ui/toggle-group"
+import { BookmarkIcon, HeartIcon, StarIcon } from "lucide-react"
+
+export function ToggleGroupSpacing() {
+ return (
+
+
+
+ Star
+
+
+
+ Heart
+
+
+
+ Bookmark
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-lg.tsx b/apps/v4/examples/base/toggle-lg.tsx
new file mode 100644
index 0000000000..c8a9f2977b
--- /dev/null
+++ b/apps/v4/examples/base/toggle-lg.tsx
@@ -0,0 +1,10 @@
+import { Toggle } from "@/examples/base/ui/toggle"
+import { Italic } from "lucide-react"
+
+export function ToggleLg() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-outline.tsx b/apps/v4/examples/base/toggle-outline.tsx
new file mode 100644
index 0000000000..9a7d253ae9
--- /dev/null
+++ b/apps/v4/examples/base/toggle-outline.tsx
@@ -0,0 +1,10 @@
+import { Toggle } from "@/examples/base/ui/toggle"
+import { Italic } from "lucide-react"
+
+export function ToggleOutline() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-sm.tsx b/apps/v4/examples/base/toggle-sm.tsx
new file mode 100644
index 0000000000..7c4c3d17d6
--- /dev/null
+++ b/apps/v4/examples/base/toggle-sm.tsx
@@ -0,0 +1,10 @@
+import { Toggle } from "@/examples/base/ui/toggle"
+import { Italic } from "lucide-react"
+
+export function ToggleSm() {
+ return (
+
+
+
+ )
+}
diff --git a/apps/v4/examples/base/toggle-with-text.tsx b/apps/v4/examples/base/toggle-with-text.tsx
new file mode 100644
index 0000000000..f6f9397756
--- /dev/null
+++ b/apps/v4/examples/base/toggle-with-text.tsx
@@ -0,0 +1,11 @@
+import { Toggle } from "@/examples/base/ui/toggle"
+import { Italic } from "lucide-react"
+
+export function ToggleWithText() {
+ return (
+
+
+ Italic
+
+ )
+}
diff --git a/apps/v4/examples/base/tooltip-demo.tsx b/apps/v4/examples/base/tooltip-demo.tsx
new file mode 100644
index 0000000000..fe85d19898
--- /dev/null
+++ b/apps/v4/examples/base/tooltip-demo.tsx
@@ -0,0 +1,19 @@
+import { Button } from "@/examples/base/ui/button"
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/examples/base/ui/tooltip"
+
+export function TooltipDemo() {
+ return (
+
+ }>
+ Hover
+
+
+ Add to library
+
+
+ )
+}
diff --git a/apps/v4/registry/base-nova/demo/typography-blockquote.tsx b/apps/v4/examples/base/typography-blockquote.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-blockquote.tsx
rename to apps/v4/examples/base/typography-blockquote.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-demo.tsx b/apps/v4/examples/base/typography-demo.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-demo.tsx
rename to apps/v4/examples/base/typography-demo.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-h1.tsx b/apps/v4/examples/base/typography-h1.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-h1.tsx
rename to apps/v4/examples/base/typography-h1.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-h2.tsx b/apps/v4/examples/base/typography-h2.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-h2.tsx
rename to apps/v4/examples/base/typography-h2.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-h3.tsx b/apps/v4/examples/base/typography-h3.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-h3.tsx
rename to apps/v4/examples/base/typography-h3.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-h4.tsx b/apps/v4/examples/base/typography-h4.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-h4.tsx
rename to apps/v4/examples/base/typography-h4.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-inline-code.tsx b/apps/v4/examples/base/typography-inline-code.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-inline-code.tsx
rename to apps/v4/examples/base/typography-inline-code.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-large.tsx b/apps/v4/examples/base/typography-large.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-large.tsx
rename to apps/v4/examples/base/typography-large.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-lead.tsx b/apps/v4/examples/base/typography-lead.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-lead.tsx
rename to apps/v4/examples/base/typography-lead.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-list.tsx b/apps/v4/examples/base/typography-list.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-list.tsx
rename to apps/v4/examples/base/typography-list.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-muted.tsx b/apps/v4/examples/base/typography-muted.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-muted.tsx
rename to apps/v4/examples/base/typography-muted.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-p.tsx b/apps/v4/examples/base/typography-p.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-p.tsx
rename to apps/v4/examples/base/typography-p.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-small.tsx b/apps/v4/examples/base/typography-small.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-small.tsx
rename to apps/v4/examples/base/typography-small.tsx
diff --git a/apps/v4/registry/base-nova/demo/typography-table.tsx b/apps/v4/examples/base/typography-table.tsx
similarity index 100%
rename from apps/v4/registry/base-nova/demo/typography-table.tsx
rename to apps/v4/examples/base/typography-table.tsx
diff --git a/apps/v4/examples/base/ui/accordion.tsx b/apps/v4/examples/base/ui/accordion.tsx
new file mode 100644
index 0000000000..eab9a05c04
--- /dev/null
+++ b/apps/v4/examples/base/ui/accordion.tsx
@@ -0,0 +1,88 @@
+"use client"
+
+import { cn } from "@/examples/base/lib/utils"
+import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
+
+import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
+
+function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
+ return (
+
+ )
+}
+
+function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: AccordionPrimitive.Trigger.Props) {
+ return (
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: AccordionPrimitive.Panel.Props) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/apps/v4/examples/base/ui/alert-dialog.tsx b/apps/v4/examples/base/ui/alert-dialog.tsx
new file mode 100644
index 0000000000..ab230c8d25
--- /dev/null
+++ b/apps/v4/examples/base/ui/alert-dialog.tsx
@@ -0,0 +1,186 @@
+"use client"
+
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import { Button } from "@/examples/base/ui/button"
+import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
+
+function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
+ return
+}
+
+function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: AlertDialogPrimitive.Backdrop.Props) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ size = "default",
+ ...props
+}: AlertDialogPrimitive.Popup.Props & {
+ size?: "default" | "sm"
+}) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogMedia({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ variant = "outline",
+ size = "default",
+ ...props
+}: AlertDialogPrimitive.Close.Props &
+ Pick, "variant" | "size">) {
+ return (
+ }
+ {...props}
+ />
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogMedia,
+ AlertDialogOverlay,
+ AlertDialogPortal,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+}
diff --git a/apps/v4/examples/base/ui/alert.tsx b/apps/v4/examples/base/ui/alert.tsx
new file mode 100644
index 0000000000..141eac8686
--- /dev/null
+++ b/apps/v4/examples/base/ui/alert.tsx
@@ -0,0 +1,75 @@
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import { cva, type VariantProps } from "class-variance-authority"
+
+const alertVariants = cva(
+ "grid 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 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",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+ svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription, AlertAction }
diff --git a/apps/v4/registry/base-nova/ui/aspect-ratio.tsx b/apps/v4/examples/base/ui/aspect-ratio.tsx
similarity index 87%
rename from apps/v4/registry/base-nova/ui/aspect-ratio.tsx
rename to apps/v4/examples/base/ui/aspect-ratio.tsx
index 2cb47c72a4..1463c139ce 100644
--- a/apps/v4/registry/base-nova/ui/aspect-ratio.tsx
+++ b/apps/v4/examples/base/ui/aspect-ratio.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/registry/base-nova/lib/utils"
+import { cn } from "@/examples/base/lib/utils"
function AspectRatio({
ratio,
diff --git a/apps/v4/examples/base/ui/avatar.tsx b/apps/v4/examples/base/ui/avatar.tsx
new file mode 100644
index 0000000000..08999e94a9
--- /dev/null
+++ b/apps/v4/examples/base/ui/avatar.tsx
@@ -0,0 +1,108 @@
+"use client"
+
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
+
+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/apps/v4/examples/base/ui/badge.tsx b/apps/v4/examples/base/ui/badge.tsx
new file mode 100644
index 0000000000..e9316146cd
--- /dev/null
+++ b/apps/v4/examples/base/ui/badge.tsx
@@ -0,0 +1,51 @@
+import { cn } from "@/examples/base/lib/utils"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+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 transition-colors overflow-hidden group/badge",
+ {
+ 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",
+ ghost:
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant = "default",
+ render,
+ ...props
+}: useRender.ComponentProps<"span"> & VariantProps
) {
+ return useRender({
+ defaultTagName: "span",
+ props: mergeProps<"span">(
+ {
+ className: cn(badgeVariants({ className, variant })),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "badge",
+ variant,
+ },
+ })
+}
+
+export { Badge, badgeVariants }
diff --git a/apps/v4/examples/base/ui/breadcrumb.tsx b/apps/v4/examples/base/ui/breadcrumb.tsx
new file mode 100644
index 0000000000..91aacfed37
--- /dev/null
+++ b/apps/v4/examples/base/ui/breadcrumb.tsx
@@ -0,0 +1,134 @@
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+
+import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
+
+function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) {
+ return (
+
+ )
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function BreadcrumbLink({
+ className,
+ render,
+ ...props
+}: useRender.ComponentProps<"a">) {
+ return useRender({
+ defaultTagName: "a",
+ props: mergeProps<"a">(
+ {
+ className: cn("hover:text-foreground transition-colors", className),
+ },
+ props
+ ),
+ render,
+ state: {
+ slot: "breadcrumb-link",
+ },
+ })
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? (
+
+ )}
+
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+ svg]:size-4",
+ className
+ )}
+ {...props}
+ >
+
+ More
+
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/apps/v4/examples/base/ui/button-group.tsx b/apps/v4/examples/base/ui/button-group.tsx
new file mode 100644
index 0000000000..66607758ea
--- /dev/null
+++ b/apps/v4/examples/base/ui/button-group.tsx
@@ -0,0 +1,86 @@
+import { cn } from "@/examples/base/lib/utils"
+import { Separator } from "@/examples/base/ui/separator"
+import { mergeProps } from "@base-ui/react/merge-props"
+import { useRender } from "@base-ui/react/use-render"
+import { cva, type VariantProps } from "class-variance-authority"
+
+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-lg flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-lg! [&>[data-slot]~[data-slot]]:rounded-l-none [&>[data-slot]~[data-slot]]:border-l-0 [&>[data-slot]]:rounded-r-none",
+ vertical:
+ "[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg! flex-col [&>[data-slot]~[data-slot]]:rounded-t-none [&>[data-slot]~[data-slot]]:border-t-0 [&>[data-slot]]:rounded-b-none",
+ },
+ },
+ 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(
+ "bg-muted gap-2 rounded-lg border px-2.5 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",
+ 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/apps/v4/examples/base/ui/button.tsx b/apps/v4/examples/base/ui/button.tsx
new file mode 100644
index 0000000000..1c65d8a38f
--- /dev/null
+++ b/apps/v4/examples/base/ui/button.tsx
@@ -0,0 +1,59 @@
+"use client"
+
+import { cn } from "@/examples/base/lib/utils"
+import { Button as ButtonPrimitive } from "@base-ui/react/button"
+import { cva, type VariantProps } from "class-variance-authority"
+
+const buttonVariants = cva(
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
+ outline:
+ "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ ghost:
+ "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
+ destructive:
+ "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ 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-3 has-data-[icon=inline-start]:pl-3",
+ 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: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ ...props
+}: ButtonPrimitive.Props & VariantProps) {
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/apps/v4/examples/base/ui/calendar.tsx b/apps/v4/examples/base/ui/calendar.tsx
new file mode 100644
index 0000000000..ac77eb92d5
--- /dev/null
+++ b/apps/v4/examples/base/ui/calendar.tsx
@@ -0,0 +1,236 @@
+"use client"
+
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import { Button, buttonVariants } from "@/examples/base/ui/button"
+import {
+ DayPicker,
+ getDefaultClassNames,
+ type DayButton,
+} from "react-day-picker"
+
+import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: 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}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString("default", { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "flex gap-4 flex-col md:flex-row relative",
+ defaultClassNames.months
+ ),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn(
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full 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",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative cn-calendar-dropdown-root rounded-(--cell-radius)",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ "absolute bg-popover inset-0 opacity-0",
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "cn-calendar-caption-label rounded-(--cell-radius) flex items-center gap-1 text-sm [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ 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",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn(
+ "select-none w-(--cell-size)",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] select-none text-muted-foreground",
+ 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",
+ 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 elative after:bg-muted after:absolute after:inset-y-0 after:w-4 after:right-0 -z-0 isolate",
+ 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",
+ defaultClassNames.range_end
+ ),
+ today: cn(
+ "bg-muted text-foreground rounded-(--cell-radius) 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={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ )
+ }
+
+ if (orientation === "right") {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+ span]:text-xs [&>span]:opacity-70",
+ defaultClassNames.day,
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+export { Calendar, CalendarDayButton }
diff --git a/apps/v4/examples/base/ui/card.tsx b/apps/v4/examples/base/ui/card.tsx
new file mode 100644
index 0000000000..9cd6893d62
--- /dev/null
+++ b/apps/v4/examples/base/ui/card.tsx
@@ -0,0 +1,102 @@
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+
+function Card({
+ className,
+ size = "default",
+ ...props
+}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
+ return (
+ img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/apps/v4/examples/base/ui/carousel.tsx b/apps/v4/examples/base/ui/carousel.tsx
new file mode 100644
index 0000000000..048b03fc4f
--- /dev/null
+++ b/apps/v4/examples/base/ui/carousel.tsx
@@ -0,0 +1,252 @@
+"use client"
+
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import { Button } from "@/examples/base/ui/button"
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react"
+
+import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+ opts?: CarouselOptions
+ plugins?: CarouselPlugin
+ orientation?: "horizontal" | "vertical"
+ setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0]
+ api: ReturnType[1]
+ scrollPrev: () => void
+ scrollNext: () => void
+ canScrollPrev: boolean
+ canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext(null)
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext)
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ")
+ }
+
+ return context
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ )
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return
+ setCanScrollPrev(api.canScrollPrev())
+ setCanScrollNext(api.canScrollNext())
+ }, [])
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev()
+ }, [api])
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext()
+ }, [api])
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault()
+ scrollPrev()
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault()
+ scrollNext()
+ }
+ },
+ [scrollPrev, scrollNext]
+ )
+
+ React.useEffect(() => {
+ if (!api || !setApi) return
+ setApi(api)
+ }, [api, setApi])
+
+ React.useEffect(() => {
+ if (!api) return
+ onSelect(api)
+ api.on("reInit", onSelect)
+ api.on("select", onSelect)
+
+ return () => {
+ api?.off("select", onSelect)
+ }
+ }, [api, onSelect])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel()
+
+ return (
+
+ )
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon-sm",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+ return (
+
+
+ Previous slide
+
+ )
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon-sm",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+ return (
+
+
+ Next slide
+
+ )
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+ useCarousel,
+}
diff --git a/apps/v4/examples/base/ui/chart.tsx b/apps/v4/examples/base/ui/chart.tsx
new file mode 100644
index 0000000000..35feff3fa3
--- /dev/null
+++ b/apps/v4/examples/base/ui/chart.tsx
@@ -0,0 +1,355 @@
+"use client"
+
+import * as React from "react"
+import { cn } from "@/examples/base/lib/utils"
+import * as RechartsPrimitive from "recharts"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode
+ icon?: React.ComponentType
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ )
+}
+
+type ChartContextProps = {
+ config: ChartConfig
+}
+
+const ChartContext = React.createContext(null)
+
+function useChart() {
+ const context = React.useContext(ChartContext)
+
+ if (!context) {
+ throw new Error("useChart must be used within a ")
+ }
+
+ return context
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<"div"> & {
+ config: ChartConfig
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"]
+}) {
+ const uniqueId = React.useId()
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color
+ )
+
+ if (!colorConfig.length) {
+ return null
+ }
+
+ return (
+