diff --git a/apps/v4/app/(create)/components/project-form.tsx b/apps/v4/app/(create)/components/project-form.tsx index 82be82152..e9d89bada 100644 --- a/apps/v4/app/(create)/components/project-form.tsx +++ b/apps/v4/app/(create)/components/project-form.tsx @@ -10,6 +10,7 @@ import { HugeiconsIcon } from "@hugeicons/react" import { useConfig } from "@/hooks/use-config" import { copyToClipboardWithMeta } from "@/components/copy-button" +import { BASES } from "@/registry/config" import { Button } from "@/registry/new-york-v4/ui/button" import { Collapsible, @@ -87,23 +88,25 @@ export function ProjectForm() { const commands = React.useMemo(() => { const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:4000" const isLocalDev = origin.includes("localhost") + const baseFlag = params.base ? ` --base ${params.base}` : "" const rtlFlag = params.rtl ? " --rtl" : "" const templateFlag = params.template ? ` --template ${params.template}` : "" + const flags = `${baseFlag}${rtlFlag}` return isLocalDev ? { - pnpm: `pnpm shadcn init${rtlFlag} --preset ${presetCode}${templateFlag}`, - npm: `pnpm shadcn init${rtlFlag} --preset ${presetCode}${templateFlag}`, - yarn: `pnpm shadcn init${rtlFlag} --preset ${presetCode}${templateFlag}`, - bun: `pnpm shadcn init${rtlFlag} --preset ${presetCode}${templateFlag}`, + pnpm: `pnpm shadcn init${flags} --preset ${presetCode}${templateFlag}`, + npm: `pnpm shadcn init${flags} --preset ${presetCode}${templateFlag}`, + yarn: `pnpm shadcn init${flags} --preset ${presetCode}${templateFlag}`, + bun: `pnpm shadcn init${flags} --preset ${presetCode}${templateFlag}`, } : { - pnpm: `pnpm dlx shadcn@latest init${rtlFlag} --preset ${presetCode}${templateFlag}`, - npm: `npx shadcn@latest init${rtlFlag} --preset ${presetCode}${templateFlag}`, - yarn: `yarn dlx shadcn@latest init${rtlFlag} --preset ${presetCode}${templateFlag}`, - bun: `bunx --bun shadcn@latest init${rtlFlag} --preset ${presetCode}${templateFlag}`, + pnpm: `pnpm dlx shadcn@latest init${flags} --preset ${presetCode}${templateFlag}`, + npm: `npx shadcn@latest init${flags} --preset ${presetCode}${templateFlag}`, + yarn: `yarn dlx shadcn@latest init${flags} --preset ${presetCode}${templateFlag}`, + bun: `bunx --bun shadcn@latest init${flags} --preset ${presetCode}${templateFlag}`, } - }, [presetCode, params.rtl, params.template]) + }, [presetCode, params.base, params.rtl, params.template]) const command = commands[packageManager] @@ -222,7 +225,40 @@ export function ProjectForm() { - + + + Component Library + { + setParams({ + base: value as "radix" | "base", + }) + }} + className="grid grid-cols-2 gap-2" + > + {BASES.map((base) => ( + + + + {base.meta?.logo && ( +
+ )} + {base.title} + + + ))} + + diff --git a/apps/v4/app/(create)/hooks/use-design-system.ts b/apps/v4/app/(create)/hooks/use-design-system.ts index 7141dbe18..f759d5525 100644 --- a/apps/v4/app/(create)/hooks/use-design-system.ts +++ b/apps/v4/app/(create)/hooks/use-design-system.ts @@ -17,7 +17,6 @@ export function usePresetCode() { // Otherwise encode current params (e.g. on initial load before first interaction). return encodePreset({ - base: params.base ?? undefined, style: params.style ?? undefined, baseColor: params.baseColor ?? undefined, theme: params.theme ?? undefined, @@ -29,7 +28,6 @@ export function usePresetCode() { } as Parameters[0]) }, [ params.preset, - params.base, params.style, params.baseColor, params.theme, diff --git a/apps/v4/app/(create)/lib/search-params.ts b/apps/v4/app/(create)/lib/search-params.ts index 1fbd44bd6..33d233ac0 100644 --- a/apps/v4/app/(create)/lib/search-params.ts +++ b/apps/v4/app/(create)/lib/search-params.ts @@ -78,7 +78,6 @@ const designSystemSearchParams = { // Design system param keys that get encoded into the preset code. const DESIGN_SYSTEM_KEYS = [ - "base", "style", "baseColor", "theme", @@ -90,7 +89,9 @@ const DESIGN_SYSTEM_KEYS = [ ] as const // Non-design-system keys that get passed through as-is. +// `base` is not encoded in preset codes — it's an architectural choice, not visual. const NON_DESIGN_SYSTEM_KEYS = [ + "base", "item", "template", "rtl", @@ -167,7 +168,6 @@ export function useDesignSystemSearchParams(options: Options = {}) { // Cast needed: merged values may include null from nuqs resets, // but encodePreset handles missing values by falling back to defaults. const code = encodePreset({ - base: merged.base ?? undefined, style: merged.style ?? undefined, baseColor: merged.baseColor ?? undefined, theme: merged.theme ?? undefined, diff --git a/apps/v4/public/r/styles/base-lyra/input-group.json b/apps/v4/public/r/styles/base-lyra/input-group.json index c6e150fe9..3866e78bd 100644 --- a/apps/v4/public/r/styles/base-lyra/input-group.json +++ b/apps/v4/public/r/styles/base-lyra/input-group.json @@ -9,7 +9,7 @@ "files": [ { "path": "registry/base-lyra/ui/input-group.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/registry/base-lyra/lib/utils\"\nimport { Button } from \"@/registry/base-lyra/ui/button\"\nimport { Input } from \"@/registry/base-lyra/ui/input\"\nimport { Textarea } from \"@/registry/base-lyra/ui/textarea\"\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n [data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst inputGroupAddonVariants = cva(\n \"text-muted-foreground h-auto gap-2 py-1.5 text-xs font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-none [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none\",\n {\n variants: {\n align: {\n \"inline-start\": \"pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first\",\n \"inline-end\": \"pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last\",\n \"block-start\":\n \"px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start\",\n \"block-end\":\n \"px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start\",\n },\n },\n defaultVariants: {\n align: \"inline-start\",\n },\n }\n)\n\nfunction InputGroupAddon({\n className,\n align = \"inline-start\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps) {\n return (\n {\n if ((e.target as HTMLElement).closest(\"button\")) {\n return\n }\n e.currentTarget.parentElement?.querySelector(\"input\")?.focus()\n }}\n {...props}\n />\n )\n}\n\nconst inputGroupButtonVariants = cva(\n \"gap-2 text-xs shadow-none flex items-center\",\n {\n variants: {\n size: {\n xs: \"h-6 gap-1 rounded-none px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n sm: \"\",\n \"icon-xs\": \"size-6 rounded-none p-0 has-[>svg]:p-0\",\n \"icon-sm\": \"size-8 p-0 has-[>svg]:p-0\",\n },\n },\n defaultVariants: {\n size: \"xs\",\n },\n }\n)\n\nfunction InputGroupButton({\n className,\n type = \"button\",\n variant = \"ghost\",\n size = \"xs\",\n ...props\n}: Omit, \"size\" | \"type\"> &\n VariantProps & {\n type?: \"button\" | \"submit\" | \"reset\"\n }) {\n return (\n \n )\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n \n )\n}\n\nfunction InputGroupInput({\n className,\n ...props\n}: React.ComponentProps<\"input\">) {\n return (\n \n )\n}\n\nfunction InputGroupTextarea({\n className,\n ...props\n}: React.ComponentProps<\"textarea\">) {\n return (\n \n )\n}\n\nexport {\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupText,\n InputGroupInput,\n InputGroupTextarea,\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/registry/base-lyra/lib/utils\"\nimport { Button } from \"@/registry/base-lyra/ui/button\"\nimport { Input } from \"@/registry/base-lyra/ui/input\"\nimport { Textarea } from \"@/registry/base-lyra/ui/textarea\"\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n [data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst inputGroupAddonVariants = cva(\n \"text-muted-foreground h-auto gap-2 py-1.5 text-xs font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-none [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none\",\n {\n variants: {\n align: {\n \"inline-start\": \"pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first\",\n \"inline-end\": \"pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last\",\n \"block-start\":\n \"px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start\",\n \"block-end\":\n \"px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start\",\n },\n },\n defaultVariants: {\n align: \"inline-start\",\n },\n }\n)\n\nfunction InputGroupAddon({\n className,\n align = \"inline-start\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps) {\n return (\n {\n if ((e.target as HTMLElement).closest(\"button\")) {\n return\n }\n e.currentTarget.parentElement?.querySelector(\"input\")?.focus()\n }}\n {...props}\n />\n )\n}\n\nconst inputGroupButtonVariants = cva(\n \"gap-2 text-xs shadow-none flex items-center\",\n {\n variants: {\n size: {\n xs: \"h-6 gap-1 rounded-none px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n sm: \"gap-1\",\n \"icon-xs\": \"size-6 rounded-none p-0 has-[>svg]:p-0\",\n \"icon-sm\": \"size-7 p-0 has-[>svg]:p-0\",\n },\n },\n defaultVariants: {\n size: \"xs\",\n },\n }\n)\n\nfunction InputGroupButton({\n className,\n type = \"button\",\n variant = \"ghost\",\n size = \"xs\",\n ...props\n}: Omit, \"size\" | \"type\"> &\n VariantProps & {\n type?: \"button\" | \"submit\" | \"reset\"\n }) {\n return (\n \n )\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n \n )\n}\n\nfunction InputGroupInput({\n className,\n ...props\n}: React.ComponentProps<\"input\">) {\n return (\n \n )\n}\n\nfunction InputGroupTextarea({\n className,\n ...props\n}: React.ComponentProps<\"textarea\">) {\n return (\n \n )\n}\n\nexport {\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupText,\n InputGroupInput,\n InputGroupTextarea,\n}\n", "type": "registry:ui" } ], diff --git a/apps/v4/public/r/styles/base-lyra/preview.json b/apps/v4/public/r/styles/base-lyra/preview.json index 6d5bf41a7..8268b2e51 100644 --- a/apps/v4/public/r/styles/base-lyra/preview.json +++ b/apps/v4/public/r/styles/base-lyra/preview.json @@ -34,7 +34,7 @@ "files": [ { "path": "registry/base-lyra/blocks/preview.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Bar, BarChart, CartesianGrid, XAxis } from \"recharts\"\nimport { type IconLibraryName } from \"shadcn/icons\"\n\nimport {\n Example,\n ExampleWrapper,\n} from \"@/registry/base-lyra/components/example\"\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogMedia,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/registry/base-lyra/ui/alert-dialog\"\nimport { Badge } from \"@/registry/base-lyra/ui/badge\"\nimport { Button } from \"@/registry/base-lyra/ui/button\"\nimport { ButtonGroup } from \"@/registry/base-lyra/ui/button-group\"\nimport { Card, CardContent } from \"@/registry/base-lyra/ui/card\"\nimport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n type ChartConfig,\n} from \"@/registry/base-lyra/ui/chart\"\nimport { Checkbox } from \"@/registry/base-lyra/ui/checkbox\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/registry/base-lyra/ui/dropdown-menu\"\nimport { Field } from \"@/registry/base-lyra/ui/field\"\nimport { Input } from \"@/registry/base-lyra/ui/input\"\nimport {\n Item,\n ItemActions,\n ItemContent,\n ItemDescription,\n ItemTitle,\n} from \"@/registry/base-lyra/ui/item\"\nimport {\n RadioGroup,\n RadioGroupItem,\n} from \"@/registry/base-lyra/ui/radio-group\"\nimport { Slider } from \"@/registry/base-lyra/ui/slider\"\nimport { Switch } from \"@/registry/base-lyra/ui/switch\"\nimport { Textarea } from \"@/registry/base-lyra/ui/textarea\"\nimport { RADII } from \"@/registry/config\"\nimport { STYLES } from \"@/registry/styles\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\nimport { FONTS } from \"@/app/(create)/lib/fonts\"\nimport { useDesignSystemSearchParams } from \"@/app/(create)/lib/search-params\"\n\nconst PREVIEW_ICONS = [\n {\n lucide: \"CopyIcon\",\n tabler: \"IconCopy\",\n hugeicons: \"Copy01Icon\",\n phosphor: \"CopyIcon\",\n remixicon: \"RiFileCopyLine\",\n },\n {\n lucide: \"CircleAlertIcon\",\n tabler: \"IconExclamationCircle\",\n hugeicons: \"AlertCircleIcon\",\n phosphor: \"WarningCircleIcon\",\n remixicon: \"RiErrorWarningLine\",\n },\n {\n lucide: \"TrashIcon\",\n tabler: \"IconTrash\",\n hugeicons: \"Delete02Icon\",\n phosphor: \"TrashIcon\",\n remixicon: \"RiDeleteBinLine\",\n },\n {\n lucide: \"ShareIcon\",\n tabler: \"IconShare\",\n hugeicons: \"Share03Icon\",\n phosphor: \"ShareIcon\",\n remixicon: \"RiShareLine\",\n },\n {\n lucide: \"ShoppingBagIcon\",\n tabler: \"IconShoppingBag\",\n hugeicons: \"ShoppingBag01Icon\",\n phosphor: \"BagIcon\",\n remixicon: \"RiShoppingBagLine\",\n },\n {\n lucide: \"MoreHorizontalIcon\",\n tabler: \"IconDots\",\n hugeicons: \"MoreHorizontalCircle01Icon\",\n phosphor: \"DotsThreeIcon\",\n remixicon: \"RiMoreLine\",\n },\n {\n lucide: \"Loader2Icon\",\n tabler: \"IconLoader\",\n hugeicons: \"Loading03Icon\",\n phosphor: \"SpinnerIcon\",\n remixicon: \"RiLoaderLine\",\n },\n {\n lucide: \"PlusIcon\",\n tabler: \"IconPlus\",\n hugeicons: \"PlusSignIcon\",\n phosphor: \"PlusIcon\",\n remixicon: \"RiAddLine\",\n },\n {\n lucide: \"MinusIcon\",\n tabler: \"IconMinus\",\n hugeicons: \"MinusSignIcon\",\n phosphor: \"MinusIcon\",\n remixicon: \"RiSubtractLine\",\n },\n {\n lucide: \"ArrowLeftIcon\",\n tabler: \"IconArrowLeft\",\n hugeicons: \"ArrowLeft02Icon\",\n phosphor: \"ArrowLeftIcon\",\n remixicon: \"RiArrowLeftLine\",\n },\n {\n lucide: \"ArrowRightIcon\",\n tabler: \"IconArrowRight\",\n hugeicons: \"ArrowRight02Icon\",\n phosphor: \"ArrowRightIcon\",\n remixicon: \"RiArrowRightLine\",\n },\n {\n lucide: \"CheckIcon\",\n tabler: \"IconCheck\",\n hugeicons: \"Tick02Icon\",\n phosphor: \"CheckIcon\",\n remixicon: \"RiCheckLine\",\n },\n {\n lucide: \"ChevronDownIcon\",\n tabler: \"IconChevronDown\",\n hugeicons: \"ArrowDown01Icon\",\n phosphor: \"CaretDownIcon\",\n remixicon: \"RiArrowDownSLine\",\n },\n {\n lucide: \"ChevronRightIcon\",\n tabler: \"IconChevronRight\",\n hugeicons: \"ArrowRight01Icon\",\n phosphor: \"CaretRightIcon\",\n remixicon: \"RiArrowRightSLine\",\n },\n] satisfies Record[]\n\nconst barChartData = [\n { month: \"January\", desktop: 186, mobile: 80 },\n { month: \"February\", desktop: 305, mobile: 200 },\n { month: \"March\", desktop: 237, mobile: 120 },\n { month: \"April\", desktop: 73, mobile: 190 },\n { month: \"May\", desktop: 209, mobile: 130 },\n { month: \"June\", desktop: 214, mobile: 140 },\n]\n\nconst barChartConfig = {\n desktop: {\n label: \"Desktop\",\n color: \"var(--chart-1)\",\n },\n mobile: {\n label: \"Mobile\",\n color: \"var(--chart-2)\",\n },\n} satisfies ChartConfig\n\nexport default function PreviewExample() {\n const [params] = useDesignSystemSearchParams()\n\n const currentFont = React.useMemo(\n () => FONTS.find((font) => font.value === params.font),\n [params.font]\n )\n\n const currentStyle = React.useMemo(\n () => STYLES.find((style) => style.name === params.style),\n [params.style]\n )\n\n const radiusValue = React.useMemo(() => {\n const radius = RADII.find((r) => r.name === params.radius)\n return radius?.value ?? \"\"\n }, [params.radius])\n\n return (\n
\n \n \n
\n \n \n
\n
\n {currentStyle?.title} - {currentFont?.name}\n
\n
\n Designers love packing quirky glyphs into test phrases. This\n is a preview of the typography styles.\n
\n
\n
\n {[\n \"Background\",\n \"Primary\",\n \"Secondary\",\n \"Muted\",\n \"Accent\",\n \"Destructive\",\n ].map((variant) => (\n \n \n
\n {variant}\n
\n
\n ))}\n
\n \n \n \n \n \n \n \n value.slice(0, 3)}\n />\n }\n />\n \n \n \n \n \n \n
\n
\n \n \n
\n {PREVIEW_ICONS.map((icon, index) => (\n \n \n \n ))}\n
\n
\n
\n \n \n
\n
\n \n \n \n \n
\n \n \n Two-factor authentication\n \n Verify via email or phone number.\n \n \n \n \n \n \n
\n Badge\n Secondary\n Outline\n
\n \n \n \n \n \n
\n \n \n
\n
\n
\n
\n \n \n \n \n