diff --git a/.changeset/fast-games-open.md b/.changeset/fast-games-open.md new file mode 100644 index 0000000000..f872cf41b6 --- /dev/null +++ b/.changeset/fast-games-open.md @@ -0,0 +1,5 @@ +--- +"shadcn": minor +--- + +add registry:base item type diff --git a/.changeset/spotty-rivers-kneel.md b/.changeset/spotty-rivers-kneel.md new file mode 100644 index 0000000000..945a6dfc03 --- /dev/null +++ b/.changeset/spotty-rivers-kneel.md @@ -0,0 +1,5 @@ +--- +"shadcn": minor +--- + +add npx shadcn create diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3231e4090e..936c3193e6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,8 +2,12 @@ "permissions": { "allow": [ "Bash(npm test:*)", - "Bash(npm run typecheck:*)" + "Bash(npm run typecheck:*)", + "Bash(ls:*)", + "Bash(cat:*)", + "WebSearch", + "WebFetch(domain:github.com)" ], "deny": [] } -} \ No newline at end of file +} diff --git a/.gitignore b/.gitignore index 865a3fd3be..7cec09962e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ tsconfig.tsbuildinfo .idea .fleet .vscode + +.notes diff --git a/.vscode/settings.json b/.vscode/settings.json index 67872b2b9f..d72f5636fc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,10 @@ ], "files.exclude": { "deprecated": true + }, + "search.exclude": { + "apps/v4/registry/radix-*": true, + "apps/v4/public/r/*": true, + "packages/shadcn/test/fixtures/*": true } } diff --git a/apps/v4/app/(app)/(root)/components/button-group-demo.tsx b/apps/v4/app/(app)/(root)/components/button-group-demo.tsx index 54010a73d0..2f50d8ac8f 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-demo.tsx @@ -6,7 +6,7 @@ import { ArrowLeftIcon, CalendarPlusIcon, ClockIcon, - ListFilterPlusIcon, + ListFilterIcon, MailCheckIcon, MoreHorizontalIcon, TagIcon, @@ -79,7 +79,7 @@ export function ButtonGroupDemo() { Add to Calendar - + Add to List diff --git a/apps/v4/app/(app)/(root)/page.tsx b/apps/v4/app/(app)/(root)/page.tsx index 3bf766127a..8ab6e59b3b 100644 --- a/apps/v4/app/(app)/(root)/page.tsx +++ b/apps/v4/app/(app)/(root)/page.tsx @@ -1,6 +1,8 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Image from "next/image" import Link from "next/link" +import { PlusSignIcon } from "@hugeicons/core-free-icons" +import { HugeiconsIcon } from "@hugeicons/react" import { Announcement } from "@/components/announcement" import { ExamplesNav } from "@/components/examples-nav" @@ -55,10 +57,13 @@ export default function IndexPage() { {title} {description} - - diff --git a/apps/v4/app/(app)/blocks/[...categories]/page.tsx b/apps/v4/app/(app)/blocks/[...categories]/page.tsx index 3cca42607e..13a1f45a9d 100644 --- a/apps/v4/app/(app)/blocks/[...categories]/page.tsx +++ b/apps/v4/app/(app)/blocks/[...categories]/page.tsx @@ -1,7 +1,7 @@ import { getAllBlockIds } from "@/lib/blocks" import { registryCategories } from "@/lib/categories" import { BlockDisplay } from "@/components/block-display" -import { getActiveStyle } from "@/registry/styles" +import { getActiveStyle } from "@/registry/_legacy-styles" export const revalidate = false export const dynamic = "force-static" diff --git a/apps/v4/app/(app)/blocks/layout.tsx b/apps/v4/app/(app)/blocks/layout.tsx index 7f1923be29..cc780fd8a0 100644 --- a/apps/v4/app/(app)/blocks/layout.tsx +++ b/apps/v4/app/(app)/blocks/layout.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Link from "next/link" import { Announcement } from "@/components/announcement" diff --git a/apps/v4/app/(app)/blocks/page.tsx b/apps/v4/app/(app)/blocks/page.tsx index f79e95d65c..c44a7d2e09 100644 --- a/apps/v4/app/(app)/blocks/page.tsx +++ b/apps/v4/app/(app)/blocks/page.tsx @@ -1,8 +1,8 @@ import Link from "next/link" import { BlockDisplay } from "@/components/block-display" +import { getActiveStyle } from "@/registry/_legacy-styles" import { Button } from "@/registry/new-york-v4/ui/button" -import { getActiveStyle } from "@/registry/styles" export const dynamic = "force-static" export const revalidate = false diff --git a/apps/v4/app/(app)/charts/[type]/page.tsx b/apps/v4/app/(app)/charts/[type]/page.tsx index 28c6a17e86..e5d317a813 100644 --- a/apps/v4/app/(app)/charts/[type]/page.tsx +++ b/apps/v4/app/(app)/charts/[type]/page.tsx @@ -3,7 +3,7 @@ import { notFound } from "next/navigation" import { cn } from "@/lib/utils" import { ChartDisplay } from "@/components/chart-display" -import { getActiveStyle } from "@/registry/styles" +import { getActiveStyle } from "@/registry/_legacy-styles" import { charts } from "@/app/(app)/charts/charts" export const revalidate = false diff --git a/apps/v4/app/(app)/charts/charts.tsx b/apps/v4/app/(app)/charts/charts.tsx index 036fc976dd..00220348e5 100644 --- a/apps/v4/app/(app)/charts/charts.tsx +++ b/apps/v4/app/(app)/charts/charts.tsx @@ -1,4 +1,4 @@ -import * as React from "react" +import type * as React from "react" import { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes" import { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default" diff --git a/apps/v4/app/(app)/charts/layout.tsx b/apps/v4/app/(app)/charts/layout.tsx index 608bb1e4e0..1e76367eb9 100644 --- a/apps/v4/app/(app)/charts/layout.tsx +++ b/apps/v4/app/(app)/charts/layout.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Link from "next/link" import { Announcement } from "@/components/announcement" diff --git a/apps/v4/app/(app)/colors/layout.tsx b/apps/v4/app/(app)/colors/layout.tsx index 94dcd83355..58f99ba6a9 100644 --- a/apps/v4/app/(app)/colors/layout.tsx +++ b/apps/v4/app/(app)/colors/layout.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Link from "next/link" import { Announcement } from "@/components/announcement" diff --git a/apps/v4/app/(app)/examples/authentication/page.tsx b/apps/v4/app/(app)/examples/authentication/page.tsx index 2ab8f219dc..bc499dc57b 100644 --- a/apps/v4/app/(app)/examples/authentication/page.tsx +++ b/apps/v4/app/(app)/examples/authentication/page.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Image from "next/image" import Link from "next/link" diff --git a/apps/v4/app/(app)/examples/dashboard/components/chart-area-interactive.tsx b/apps/v4/app/(app)/examples/dashboard/components/chart-area-interactive.tsx index e1364a9cb2..9e9880fbcf 100644 --- a/apps/v4/app/(app)/examples/dashboard/components/chart-area-interactive.tsx +++ b/apps/v4/app/(app)/examples/dashboard/components/chart-area-interactive.tsx @@ -13,10 +13,10 @@ import { CardTitle, } from "@/registry/new-york-v4/ui/card" import { - ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, + type ChartConfig, } from "@/registry/new-york-v4/ui/chart" import { Select, diff --git a/apps/v4/app/(app)/examples/dashboard/components/data-table.tsx b/apps/v4/app/(app)/examples/dashboard/components/data-table.tsx index 1a34e73067..7a039c8161 100644 --- a/apps/v4/app/(app)/examples/dashboard/components/data-table.tsx +++ b/apps/v4/app/(app)/examples/dashboard/components/data-table.tsx @@ -35,8 +35,6 @@ import { IconTrendingUp, } from "@tabler/icons-react" import { - ColumnDef, - ColumnFiltersState, flexRender, getCoreRowModel, getFacetedRowModel, @@ -44,10 +42,12 @@ import { getFilteredRowModel, getPaginationRowModel, getSortedRowModel, - Row, - SortingState, useReactTable, - VisibilityState, + type ColumnDef, + type ColumnFiltersState, + type Row, + type SortingState, + type VisibilityState, } from "@tanstack/react-table" import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" import { toast } from "sonner" @@ -57,10 +57,10 @@ import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile" import { Badge } from "@/registry/new-york-v4/ui/badge" import { Button } from "@/registry/new-york-v4/ui/button" import { - ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, + type ChartConfig, } from "@/registry/new-york-v4/ui/chart" import { Checkbox } from "@/registry/new-york-v4/ui/checkbox" import { diff --git a/apps/v4/app/(app)/examples/layout.tsx b/apps/v4/app/(app)/examples/layout.tsx index 1d687487e7..4267903319 100644 --- a/apps/v4/app/(app)/examples/layout.tsx +++ b/apps/v4/app/(app)/examples/layout.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Link from "next/link" import { Announcement } from "@/components/announcement" diff --git a/apps/v4/app/(app)/examples/playground/components/maxlength-selector.tsx b/apps/v4/app/(app)/examples/playground/components/maxlength-selector.tsx index c2b0a277dd..3a19f81384 100644 --- a/apps/v4/app/(app)/examples/playground/components/maxlength-selector.tsx +++ b/apps/v4/app/(app)/examples/playground/components/maxlength-selector.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { SliderProps } from "@radix-ui/react-slider" +import { type SliderProps } from "@radix-ui/react-slider" import { HoverCard, diff --git a/apps/v4/app/(app)/examples/playground/components/model-selector.tsx b/apps/v4/app/(app)/examples/playground/components/model-selector.tsx index a9c3bd6dc6..5e7dfcb8b4 100644 --- a/apps/v4/app/(app)/examples/playground/components/model-selector.tsx +++ b/apps/v4/app/(app)/examples/playground/components/model-selector.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { PopoverProps } from "@radix-ui/react-popover" +import { type PopoverProps } from "@radix-ui/react-popover" import { Check, ChevronsUpDown } from "lucide-react" import { cn } from "@/lib/utils" @@ -27,7 +27,7 @@ import { PopoverTrigger, } from "@/registry/new-york-v4/ui/popover" -import { Model, ModelType } from "../data/models" +import { type Model, type ModelType } from "../data/models" interface ModelSelectorProps extends PopoverProps { types: readonly ModelType[] diff --git a/apps/v4/app/(app)/examples/playground/components/preset-selector.tsx b/apps/v4/app/(app)/examples/playground/components/preset-selector.tsx index cb2a64c2f4..f764fcb280 100644 --- a/apps/v4/app/(app)/examples/playground/components/preset-selector.tsx +++ b/apps/v4/app/(app)/examples/playground/components/preset-selector.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { PopoverProps } from "@radix-ui/react-popover" +import { type PopoverProps } from "@radix-ui/react-popover" import { Check, ChevronsUpDown } from "lucide-react" import { cn } from "@/lib/utils" @@ -21,7 +21,7 @@ import { PopoverTrigger, } from "@/registry/new-york-v4/ui/popover" -import { Preset } from "../data/presets" +import { type Preset } from "../data/presets" interface PresetSelectorProps extends PopoverProps { presets: Preset[] diff --git a/apps/v4/app/(app)/examples/playground/components/temperature-selector.tsx b/apps/v4/app/(app)/examples/playground/components/temperature-selector.tsx index 70160988ef..7dbbdbb253 100644 --- a/apps/v4/app/(app)/examples/playground/components/temperature-selector.tsx +++ b/apps/v4/app/(app)/examples/playground/components/temperature-selector.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { SliderProps } from "@radix-ui/react-slider" +import { type SliderProps } from "@radix-ui/react-slider" import { HoverCard, diff --git a/apps/v4/app/(app)/examples/playground/components/top-p-selector.tsx b/apps/v4/app/(app)/examples/playground/components/top-p-selector.tsx index e1c743d260..a33bd2c2b1 100644 --- a/apps/v4/app/(app)/examples/playground/components/top-p-selector.tsx +++ b/apps/v4/app/(app)/examples/playground/components/top-p-selector.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { SliderProps } from "@radix-ui/react-slider" +import { type SliderProps } from "@radix-ui/react-slider" import { HoverCard, diff --git a/apps/v4/app/(app)/examples/playground/page.tsx b/apps/v4/app/(app)/examples/playground/page.tsx index 01e6481031..26da238993 100644 --- a/apps/v4/app/(app)/examples/playground/page.tsx +++ b/apps/v4/app/(app)/examples/playground/page.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Image from "next/image" import { RotateCcw } from "lucide-react" diff --git a/apps/v4/app/(app)/examples/tasks/components/columns.tsx b/apps/v4/app/(app)/examples/tasks/components/columns.tsx index 91c2a3f35e..8976274e5c 100644 --- a/apps/v4/app/(app)/examples/tasks/components/columns.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/columns.tsx @@ -1,12 +1,12 @@ "use client" -import { ColumnDef } from "@tanstack/react-table" +import { type ColumnDef } from "@tanstack/react-table" import { Badge } from "@/registry/new-york-v4/ui/badge" import { Checkbox } from "@/registry/new-york-v4/ui/checkbox" import { labels, priorities, statuses } from "../data/data" -import { Task } from "../data/schema" +import { type Task } from "../data/schema" import { DataTableColumnHeader } from "./data-table-column-header" import { DataTableRowActions } from "./data-table-row-actions" diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table-column-header.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table-column-header.tsx index 3738cc6048..3204b182a7 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table-column-header.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table-column-header.tsx @@ -1,4 +1,4 @@ -import { Column } from "@tanstack/react-table" +import { type Column } from "@tanstack/react-table" import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react" import { cn } from "@/lib/utils" diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table-faceted-filter.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table-faceted-filter.tsx index c44ea4d7fe..8c657ffad8 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table-faceted-filter.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table-faceted-filter.tsx @@ -1,5 +1,5 @@ import * as React from "react" -import { Column } from "@tanstack/react-table" +import { type Column } from "@tanstack/react-table" import { Check, PlusCircle } from "lucide-react" import { cn } from "@/lib/utils" diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table-pagination.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table-pagination.tsx index 005d2b28ad..b6782a9c54 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table-pagination.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table-pagination.tsx @@ -1,4 +1,4 @@ -import { Table } from "@tanstack/react-table" +import { type Table } from "@tanstack/react-table" import { ChevronLeft, ChevronRight, diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table-row-actions.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table-row-actions.tsx index 59edafe1b9..5e0c80bcdb 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table-row-actions.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table-row-actions.tsx @@ -1,6 +1,6 @@ "use client" -import { Row } from "@tanstack/react-table" +import { type Row } from "@tanstack/react-table" import { MoreHorizontal } from "lucide-react" import { Button } from "@/registry/new-york-v4/ui/button" diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table-toolbar.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table-toolbar.tsx index c1c2ecd065..10eb27606e 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table-toolbar.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table-toolbar.tsx @@ -1,6 +1,6 @@ "use client" -import { Table } from "@tanstack/react-table" +import { type Table } from "@tanstack/react-table" import { X } from "lucide-react" import { Button } from "@/registry/new-york-v4/ui/button" diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table-view-options.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table-view-options.tsx index ba9c250966..55b7953c9f 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table-view-options.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table-view-options.tsx @@ -1,7 +1,7 @@ "use client" import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu" -import { Table } from "@tanstack/react-table" +import { type Table } from "@tanstack/react-table" import { Settings2 } from "lucide-react" import { Button } from "@/registry/new-york-v4/ui/button" diff --git a/apps/v4/app/(app)/examples/tasks/components/data-table.tsx b/apps/v4/app/(app)/examples/tasks/components/data-table.tsx index 5f3e4c12d2..cba2d981a1 100644 --- a/apps/v4/app/(app)/examples/tasks/components/data-table.tsx +++ b/apps/v4/app/(app)/examples/tasks/components/data-table.tsx @@ -2,8 +2,6 @@ import * as React from "react" import { - ColumnDef, - ColumnFiltersState, flexRender, getCoreRowModel, getFacetedRowModel, @@ -11,9 +9,11 @@ import { getFilteredRowModel, getPaginationRowModel, getSortedRowModel, - SortingState, useReactTable, - VisibilityState, + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, } from "@tanstack/react-table" import { diff --git a/apps/v4/app/(app)/examples/tasks/page.tsx b/apps/v4/app/(app)/examples/tasks/page.tsx index ebab8669c6..79692b7bac 100644 --- a/apps/v4/app/(app)/examples/tasks/page.tsx +++ b/apps/v4/app/(app)/examples/tasks/page.tsx @@ -1,6 +1,6 @@ import { promises as fs } from "fs" import path from "path" -import { Metadata } from "next" +import { type Metadata } from "next" import Image from "next/image" import { z } from "zod" diff --git a/apps/v4/app/(app)/llm/[[...slug]]/route.ts b/apps/v4/app/(app)/llm/[[...slug]]/route.ts index 8cbf7705a1..b50339de4d 100644 --- a/apps/v4/app/(app)/llm/[[...slug]]/route.ts +++ b/apps/v4/app/(app)/llm/[[...slug]]/route.ts @@ -3,7 +3,7 @@ import { NextResponse, type NextRequest } from "next/server" import { processMdxForLLMs } from "@/lib/llm" import { source } from "@/lib/source" -import { getActiveStyle } from "@/registry/styles" +import { getActiveStyle } from "@/registry/_legacy-styles" export const revalidate = false diff --git a/apps/v4/app/(app)/themes/layout.tsx b/apps/v4/app/(app)/themes/layout.tsx index 15d1bf2467..99951af1ae 100644 --- a/apps/v4/app/(app)/themes/layout.tsx +++ b/apps/v4/app/(app)/themes/layout.tsx @@ -1,4 +1,4 @@ -import { Metadata } from "next" +import { type Metadata } from "next" import Link from "next/link" import { Announcement } from "@/components/announcement" diff --git a/apps/v4/app/(create)/components/accent-picker.tsx b/apps/v4/app/(create)/components/accent-picker.tsx new file mode 100644 index 0000000000..8e8914739c --- /dev/null +++ b/apps/v4/app/(create)/components/accent-picker.tsx @@ -0,0 +1,100 @@ +"use client" + +import { useQueryStates } from "nuqs" + +import { MENU_ACCENTS, type MenuAccentValue } from "@/registry/config" +import { LockButton } from "@/app/(create)/components/lock-button" +import { + Picker, + PickerContent, + PickerGroup, + PickerRadioGroup, + PickerRadioItem, + PickerTrigger, +} from "@/app/(create)/components/picker" +import { designSystemSearchParams } from "@/app/(create)/lib/search-params" + +export function MenuAccentPicker({ + isMobile, + anchorRef, +}: { + isMobile: boolean + anchorRef: React.RefObject +}) { + const [params, setParams] = useQueryStates(designSystemSearchParams, { + shallow: false, + history: "push", + }) + + const currentAccent = MENU_ACCENTS.find( + (accent) => accent.value === params.menuAccent + ) + + return ( + +
+ +
+
Menu Accent
+
+ {currentAccent?.label} +
+
+
+ + + + +
+
+ +
+ + { + setParams({ menuAccent: value as MenuAccentValue }) + }} + > + + {MENU_ACCENTS.map((accent) => ( + + {accent.label} + + ))} + + + +
+ ) +} diff --git a/apps/v4/app/(create)/components/base-color-picker.tsx b/apps/v4/app/(create)/components/base-color-picker.tsx new file mode 100644 index 0000000000..d57924dddd --- /dev/null +++ b/apps/v4/app/(create)/components/base-color-picker.tsx @@ -0,0 +1,129 @@ +"use client" + +import * as React from "react" +import { useTheme } from "next-themes" +import { useQueryStates } from "nuqs" + +import { useMounted } from "@/hooks/use-mounted" +import { BASE_COLORS, type BaseColorName } from "@/registry/config" +import { LockButton } from "@/app/(create)/components/lock-button" +import { + Picker, + PickerContent, + PickerGroup, + PickerItem, + PickerRadioGroup, + PickerRadioItem, + PickerSeparator, + PickerTrigger, +} from "@/app/(create)/components/picker" +import { designSystemSearchParams } from "@/app/(create)/lib/search-params" + +export function BaseColorPicker({ + isMobile, + anchorRef, +}: { + isMobile: boolean + anchorRef: React.RefObject +}) { + const { resolvedTheme, setTheme } = useTheme() + const mounted = useMounted() + const [params, setParams] = useQueryStates(designSystemSearchParams, { + shallow: false, + history: "push", + }) + + const currentBaseColor = React.useMemo( + () => BASE_COLORS.find((baseColor) => baseColor.name === params.baseColor), + [params.baseColor] + ) + + return ( + +
+ +
+
Base Color
+
+ {currentBaseColor?.title} +
+
+ {mounted && resolvedTheme && ( +
+ )} + + +
+ + { + if (value === "dark") { + setTheme(resolvedTheme === "dark" ? "light" : "dark") + return + } + + setParams({ baseColor: value as BaseColorName }) + }} + > + + {BASE_COLORS.map((baseColor) => ( + +
+ {mounted && resolvedTheme && ( +
+ )} + {baseColor.title} +
+ + ))} + + + + { + setTheme(resolvedTheme === "dark" ? "light" : "dark") + }} + > +
+
+ Switch to {resolvedTheme === "dark" ? "Light" : "Dark"} Mode +
+
+ Base colors are easier to see in dark mode. +
+
+
+
+ + + + ) +} diff --git a/apps/v4/app/(create)/components/base-picker.tsx b/apps/v4/app/(create)/components/base-picker.tsx new file mode 100644 index 0000000000..31456414b7 --- /dev/null +++ b/apps/v4/app/(create)/components/base-picker.tsx @@ -0,0 +1,92 @@ +"use client" + +import * as React from "react" +import { useQueryStates } from "nuqs" + +import { BASES } from "@/registry/config" +import { + Picker, + PickerContent, + PickerGroup, + PickerRadioGroup, + PickerRadioItem, + PickerTrigger, +} from "@/app/(create)/components/picker" +import { designSystemSearchParams } from "@/app/(create)/lib/search-params" + +export function BasePicker({ + isMobile, + anchorRef, +}: { + isMobile: boolean + anchorRef: React.RefObject +}) { + const [params, setParams] = useQueryStates(designSystemSearchParams, { + shallow: false, + history: "push", + }) + + const currentBase = React.useMemo( + () => BASES.find((base) => base.name === params.base), + [params.base] + ) + + const handleValueChange = React.useCallback( + (value: string) => { + const newBase = BASES.find((base) => base.name === value) + if (!newBase) { + return + } + + setParams({ base: newBase.name }) + }, + [setParams] + ) + + return ( + + +
+
Component Library
+
+ {currentBase?.title} +
+
+ {currentBase?.meta?.logo && ( +
+ )} + + + + + {BASES.map((base) => ( + + {base.meta?.logo && ( +
+ )} + {base.title} + + ))} + + + + + ) +} diff --git a/apps/v4/app/(create)/components/create-project-dialog.tsx b/apps/v4/app/(create)/components/create-project-dialog.tsx new file mode 100644 index 0000000000..9d6454765a --- /dev/null +++ b/apps/v4/app/(create)/components/create-project-dialog.tsx @@ -0,0 +1,126 @@ +"use client" + +import * as React from "react" +import { IconCheck, IconCopy } from "@tabler/icons-react" +import { useQueryStates } from "nuqs" + +import { copyToClipboardWithMeta } from "@/components/copy-button" +import { Icons } from "@/components/icons" +import { DEFAULT_CONFIG } from "@/registry/config" +import { Button } from "@/registry/new-york-v4/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/registry/new-york-v4/ui/dialog" +import { designSystemSearchParams } from "@/app/(create)/lib/search-params" + +export function ToolbarControls() { + const [open, setOpen] = React.useState(false) + const [params, setParams] = useQueryStates(designSystemSearchParams, { + shallow: false, + }) + const [hasCopied, setHasCopied] = React.useState(false) + + const command = React.useMemo(() => { + const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000" + const url = `${origin}/init?base=${params.base}&style=${params.style}&baseColor=${params.baseColor}&theme=${params.theme}&iconLibrary=${params.iconLibrary}&font=${params.font}&menuAccent=${params.menuAccent}&menuColor=${params.menuColor}&radius=${params.radius}` + return `pnpm shadcn create --preset ${url} -c ~/Playground` + }, [ + params.base, + params.style, + params.baseColor, + params.theme, + params.iconLibrary, + params.font, + params.menuAccent, + params.menuColor, + params.radius, + ]) + + React.useEffect(() => { + if (hasCopied) { + const timer = setTimeout(() => setHasCopied(false), 2000) + return () => clearTimeout(timer) + } + }, [hasCopied]) + + const handleCopy = React.useCallback(() => { + copyToClipboardWithMeta(command, { + name: "copy_npm_command", + properties: { + command, + }, + }) + setOpen(false) + setHasCopied(true) + }, [command, setOpen]) + + const handleReset = React.useCallback(() => { + setParams({ + item: "cover", + iconLibrary: DEFAULT_CONFIG.iconLibrary, + style: DEFAULT_CONFIG.style, + theme: DEFAULT_CONFIG.theme, + font: DEFAULT_CONFIG.font, + baseColor: DEFAULT_CONFIG.baseColor, + menuAccent: DEFAULT_CONFIG.menuAccent, + menuColor: DEFAULT_CONFIG.menuColor, + radius: DEFAULT_CONFIG.radius, + size: 100, + custom: false, + }) + }, [setParams]) + + return ( +
+ + + + + + + + Install shadcn/ui + + Run this command to start a new shadcn/ui project with your + selected configuration. + + +
+
+ + {command} + +
+
+ + + + +
+
+
+ ) +} diff --git a/apps/v4/app/(create)/components/customizer-controls.tsx b/apps/v4/app/(create)/components/customizer-controls.tsx new file mode 100644 index 0000000000..1b8bce5c61 --- /dev/null +++ b/apps/v4/app/(create)/components/customizer-controls.tsx @@ -0,0 +1,208 @@ +"use client" + +import * as React from "react" +import { useRouter } from "next/navigation" +import Script from "next/script" +import { DiceFaces05Icon, Undo02Icon } from "@hugeicons/core-free-icons" +import { HugeiconsIcon } from "@hugeicons/react" +import { useQueryStates } from "nuqs" + +import { cn } from "@/lib/utils" +import { + BASE_COLORS, + DEFAULT_CONFIG, + getThemesForBaseColor, + iconLibraries, + MENU_ACCENTS, + MENU_COLORS, + RADII, + STYLES, +} from "@/registry/config" +import { Button } from "@/registry/new-york-v4/ui/button" +import { Kbd } from "@/registry/new-york-v4/ui/kbd" +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/registry/new-york-v4/ui/tooltip" +import { useLocks } from "@/app/(create)/hooks/use-locks" +import { FONTS } from "@/app/(create)/lib/fonts" +import { + applyBias, + RANDOMIZE_BIASES, + type RandomizeContext, +} from "@/app/(create)/lib/randomize-biases" +import { designSystemSearchParams } from "@/app/(create)/lib/search-params" + +export const RANDOMIZE_FORWARD_TYPE = "randomize-forward" + +function randomItem(array: readonly T[]): T { + return array[Math.floor(Math.random() * array.length)] +} + +export function CustomizerControls({ className }: { className?: string }) { + const router = useRouter() + const { locks } = useLocks() + const [params, setParams] = useQueryStates(designSystemSearchParams, { + shallow: false, + history: "push", + }) + + const handleReset = React.useCallback(() => { + setParams({ + base: params.base, // Keep the current base value + style: DEFAULT_CONFIG.style, + baseColor: DEFAULT_CONFIG.baseColor, + theme: DEFAULT_CONFIG.theme, + iconLibrary: DEFAULT_CONFIG.iconLibrary, + font: DEFAULT_CONFIG.font, + menuAccent: DEFAULT_CONFIG.menuAccent, + menuColor: DEFAULT_CONFIG.menuColor, + radius: DEFAULT_CONFIG.radius, + template: DEFAULT_CONFIG.template, + item: "preview", + }) + }, [setParams, params.base]) + + const handleRandomize = React.useCallback(() => { + // Use current value if locked, otherwise randomize. + const baseColor = locks.has("baseColor") + ? params.baseColor + : randomItem(BASE_COLORS).name + const selectedStyle = locks.has("style") + ? params.style + : randomItem(STYLES).name + + // Build context for bias application. + const context: RandomizeContext = { + style: selectedStyle, + baseColor, + } + + const availableThemes = getThemesForBaseColor(baseColor) + const availableFonts = applyBias(FONTS, context, RANDOMIZE_BIASES.fonts) + const availableRadii = applyBias(RADII, context, RANDOMIZE_BIASES.radius) + + const selectedTheme = locks.has("theme") + ? params.theme + : randomItem(availableThemes).name + const selectedFont = locks.has("font") + ? params.font + : randomItem(availableFonts).value + const selectedRadius = locks.has("radius") + ? params.radius + : randomItem(availableRadii).name + const selectedIconLibrary = locks.has("iconLibrary") + ? params.iconLibrary + : randomItem(Object.values(iconLibraries)).name + const selectedMenuAccent = locks.has("menuAccent") + ? params.menuAccent + : randomItem(MENU_ACCENTS).value + const selectedMenuColor = locks.has("menuColor") + ? params.menuColor + : randomItem(MENU_COLORS).value + + // Update context with selected values for potential future biases. + context.theme = selectedTheme + context.font = selectedFont + context.radius = selectedRadius + + setParams({ + style: selectedStyle, + baseColor, + theme: selectedTheme, + iconLibrary: selectedIconLibrary, + font: selectedFont, + menuAccent: selectedMenuAccent, + menuColor: selectedMenuColor, + radius: selectedRadius, + }) + }, [setParams, locks, params]) + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if ((e.key === "r" || e.key === "R") && !e.metaKey && !e.ctrlKey) { + if ( + (e.target instanceof HTMLElement && e.target.isContentEditable) || + e.target instanceof HTMLInputElement || + e.target instanceof HTMLTextAreaElement || + e.target instanceof HTMLSelectElement + ) { + return + } + + e.preventDefault() + handleRandomize() + } + } + + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, [handleRandomize]) + + return ( +
+ + +
+ ) +} + +export function RandomizeScript() { + return ( +