diff --git a/.cursor/rules/registry-bases-parity.mdc b/.cursor/rules/registry-bases-parity.mdc new file mode 100644 index 0000000000..08ebd58b94 --- /dev/null +++ b/.cursor/rules/registry-bases-parity.mdc @@ -0,0 +1,22 @@ +--- +description: Keep registry base and radix trees in sync when editing shared UI +globs: apps/v4/registry/bases/**/* +alwaysApply: false +--- + +# Registry bases: Base UI ↔ Radix parity + +`apps/v4/registry/bases/base` and `apps/v4/registry/bases/radix` are **parallel registries**. Anything that exists in both trees for the same purpose (preview blocks, mirrored examples, shared card layouts, etc.) **must stay in sync**. + +## When editing + +- If you change a file under **`bases/base/...`**, apply the **same behavioral and visual change** to the matching path under **`bases/radix/...`** (and the reverse). +- Only diverge where APIs differ (e.g. import paths like `@/registry/bases/base/ui/*` vs `@/registry/bases/radix/ui/*`, or Base UI vs Radix component props). +- Do **not** update only one side unless the user explicitly asks for a single-base change. + +## Typical mirrored paths + +- `blocks/preview/**` — preview cards and blocks +- Parallel `ui/*` components when both exist for the same component + +After edits, briefly confirm both trees were updated (or state why one side is intentionally unchanged). diff --git a/.gitignore b/.gitignore index c040854c9f..58ebd7be60 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build # misc .DS_Store +.eslintcache *.pem # debug @@ -43,3 +44,4 @@ tsconfig.tsbuildinfo .notes .playwright-mcp shadcn-workspace +.codex-artifacts diff --git a/apps/v4/app/(app)/(root)/components/appearance-settings.tsx b/apps/v4/app/(app)/(root)/components/appearance-settings.tsx index 216c89f3e0..cf9609833e 100644 --- a/apps/v4/app/(app)/(root)/components/appearance-settings.tsx +++ b/apps/v4/app/(app)/(root)/components/appearance-settings.tsx @@ -1,8 +1,10 @@ "use client" import * as React from "react" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { IconMinus, IconPlus } from "@tabler/icons-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { Field, FieldContent, @@ -13,11 +15,10 @@ import { FieldSeparator, FieldSet, FieldTitle, -} from "@/examples/radix/ui/field" -import { Input } from "@/examples/radix/ui/input" -import { RadioGroup, RadioGroupItem } from "@/examples/radix/ui/radio-group" -import { Switch } from "@/examples/radix/ui/switch" -import { IconMinus, IconPlus } from "@tabler/icons-react" +} from "@/styles/radix-nova/ui/field" +import { Input } from "@/styles/radix-nova/ui/input" +import { RadioGroup, RadioGroupItem } from "@/styles/radix-nova/ui/radio-group" +import { Switch } from "@/styles/radix-nova/ui/switch" export function AppearanceSettings() { const [gpuCount, setGpuCount] = React.useState(8) 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 ccfc877544..e515c5fbda 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-demo.tsx @@ -1,8 +1,20 @@ "use client" import * as React from "react" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { + ArchiveIcon, + ArrowLeftIcon, + CalendarPlusIcon, + ClockIcon, + ListFilterIcon, + MailCheckIcon, + MoreHorizontalIcon, + TagIcon, + Trash2Icon, +} from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { DropdownMenu, DropdownMenuContent, @@ -15,18 +27,7 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/examples/radix/ui/dropdown-menu" -import { - ArchiveIcon, - ArrowLeftIcon, - CalendarPlusIcon, - ClockIcon, - ListFilterIcon, - MailCheckIcon, - MoreHorizontalIcon, - TagIcon, - Trash2Icon, -} from "lucide-react" +} from "@/styles/radix-nova/ui/dropdown-menu" export function ButtonGroupDemo() { const [label, setLabel] = React.useState("personal") diff --git a/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx b/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx index 4fd4a4d964..fa871a5722 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-input-group.tsx @@ -1,20 +1,21 @@ "use client" import * as React from "react" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { AudioLinesIcon, PlusIcon } from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, -} from "@/examples/radix/ui/input-group" +} from "@/styles/radix-nova/ui/input-group" import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/examples/radix/ui/tooltip" -import { AudioLinesIcon, PlusIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/tooltip" export function ButtonGroupInputGroup() { const [voiceEnabled, setVoiceEnabled] = React.useState(false) diff --git a/apps/v4/app/(app)/(root)/components/button-group-nested.tsx b/apps/v4/app/(app)/(root)/components/button-group-nested.tsx index e612ea740f..0226072797 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-nested.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-nested.tsx @@ -1,9 +1,10 @@ "use client" -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react" +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" + export function ButtonGroupNested() { return ( diff --git a/apps/v4/app/(app)/(root)/components/button-group-popover.tsx b/apps/v4/app/(app)/(root)/components/button-group-popover.tsx index 187b1c8787..e797d11e72 100644 --- a/apps/v4/app/(app)/(root)/components/button-group-popover.tsx +++ b/apps/v4/app/(app)/(root)/components/button-group-popover.tsx @@ -1,13 +1,14 @@ -import { Button } from "@/examples/radix/ui/button" -import { ButtonGroup } from "@/examples/radix/ui/button-group" +import { BotIcon, ChevronDownIcon } from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" +import { ButtonGroup } from "@/styles/radix-nova/ui/button-group" import { Popover, PopoverContent, PopoverTrigger, -} from "@/examples/radix/ui/popover" -import { Separator } from "@/examples/radix/ui/separator" -import { Textarea } from "@/examples/radix/ui/textarea" -import { BotIcon, ChevronDownIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/popover" +import { Separator } from "@/styles/radix-nova/ui/separator" +import { Textarea } from "@/styles/radix-nova/ui/textarea" export function ButtonGroupPopover() { return ( diff --git a/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx b/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx index 72b9e968b1..39434bbdaa 100644 --- a/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx +++ b/apps/v4/app/(app)/(root)/components/empty-avatar-group.tsx @@ -1,10 +1,12 @@ +import { PlusIcon } from "lucide-react" + import { Avatar, AvatarFallback, AvatarGroup, AvatarImage, -} from "@/examples/radix/ui/avatar" -import { Button } from "@/examples/radix/ui/button" +} from "@/styles/radix-nova/ui/avatar" +import { Button } from "@/styles/radix-nova/ui/button" import { Empty, EmptyContent, @@ -12,8 +14,7 @@ import { EmptyHeader, EmptyMedia, EmptyTitle, -} from "@/examples/radix/ui/empty" -import { PlusIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/empty" export function EmptyAvatarGroup() { return ( diff --git a/apps/v4/app/(app)/(root)/components/field-checkbox.tsx b/apps/v4/app/(app)/(root)/components/field-checkbox.tsx index 595cac1f41..8942fbd597 100644 --- a/apps/v4/app/(app)/(root)/components/field-checkbox.tsx +++ b/apps/v4/app/(app)/(root)/components/field-checkbox.tsx @@ -1,5 +1,5 @@ -import { Checkbox } from "@/examples/radix/ui/checkbox" -import { Field, FieldLabel } from "@/examples/radix/ui/field" +import { Checkbox } from "@/styles/radix-nova/ui/checkbox" +import { Field, FieldLabel } from "@/styles/radix-nova/ui/field" export function FieldCheckbox() { return ( diff --git a/apps/v4/app/(app)/(root)/components/field-demo.tsx b/apps/v4/app/(app)/(root)/components/field-demo.tsx index 0f54aa9cf6..a209ee6158 100644 --- a/apps/v4/app/(app)/(root)/components/field-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/field-demo.tsx @@ -1,5 +1,5 @@ -import { Button } from "@/examples/radix/ui/button" -import { Checkbox } from "@/examples/radix/ui/checkbox" +import { Button } from "@/styles/radix-nova/ui/button" +import { Checkbox } from "@/styles/radix-nova/ui/checkbox" import { Field, FieldDescription, @@ -8,8 +8,8 @@ import { FieldLegend, FieldSeparator, FieldSet, -} from "@/examples/radix/ui/field" -import { Input } from "@/examples/radix/ui/input" +} from "@/styles/radix-nova/ui/field" +import { Input } from "@/styles/radix-nova/ui/input" import { Select, SelectContent, @@ -17,8 +17,8 @@ import { SelectItem, SelectTrigger, SelectValue, -} from "@/examples/radix/ui/select" -import { Textarea } from "@/examples/radix/ui/textarea" +} from "@/styles/radix-nova/ui/select" +import { Textarea } from "@/styles/radix-nova/ui/textarea" export function FieldDemo() { return ( diff --git a/apps/v4/app/(app)/(root)/components/field-hear.tsx b/apps/v4/app/(app)/(root)/components/field-hear.tsx index aa21c77d30..0d410f614a 100644 --- a/apps/v4/app/(app)/(root)/components/field-hear.tsx +++ b/apps/v4/app/(app)/(root)/components/field-hear.tsx @@ -1,5 +1,5 @@ -import { Card, CardContent } from "@/examples/radix/ui/card" -import { Checkbox } from "@/examples/radix/ui/checkbox" +import { Card, CardContent } from "@/styles/radix-nova/ui/card" +import { Checkbox } from "@/styles/radix-nova/ui/checkbox" import { Field, FieldDescription, @@ -8,7 +8,7 @@ import { FieldLegend, FieldSet, FieldTitle, -} from "@/examples/radix/ui/field" +} from "@/styles/radix-nova/ui/field" const options = [ { diff --git a/apps/v4/app/(app)/(root)/components/field-slider.tsx b/apps/v4/app/(app)/(root)/components/field-slider.tsx index b2ab12306c..cd44849c6f 100644 --- a/apps/v4/app/(app)/(root)/components/field-slider.tsx +++ b/apps/v4/app/(app)/(root)/components/field-slider.tsx @@ -1,8 +1,13 @@ "use client" import { useState } from "react" -import { Field, FieldDescription, FieldTitle } from "@/examples/radix/ui/field" -import { Slider } from "@/examples/radix/ui/slider" + +import { + Field, + FieldDescription, + FieldTitle, +} from "@/styles/radix-nova/ui/field" +import { Slider } from "@/styles/radix-nova/ui/slider" export function FieldSlider() { const [value, setValue] = useState([200, 800]) diff --git a/apps/v4/app/(app)/(root)/components/index.tsx b/apps/v4/app/(app)/(root)/components/index.tsx index 79640798e6..c00f9c06e1 100644 --- a/apps/v4/app/(app)/(root)/components/index.tsx +++ b/apps/v4/app/(app)/(root)/components/index.tsx @@ -1,4 +1,4 @@ -import { FieldSeparator } from "@/examples/radix/ui/field" +import { FieldSeparator } from "@/styles/radix-nova/ui/field" import { AppearanceSettings } from "./appearance-settings" import { ButtonGroupDemo } from "./button-group-demo" diff --git a/apps/v4/app/(app)/(root)/components/input-group-button.tsx b/apps/v4/app/(app)/(root)/components/input-group-button.tsx index c2ad283fbf..3880a6543f 100644 --- a/apps/v4/app/(app)/(root)/components/input-group-button.tsx +++ b/apps/v4/app/(app)/(root)/components/input-group-button.tsx @@ -1,19 +1,20 @@ "use client" import * as React from "react" +import { IconInfoCircle, IconStar } from "@tabler/icons-react" + import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, -} from "@/examples/radix/ui/input-group" -import { Label } from "@/examples/radix/ui/label" +} from "@/styles/radix-nova/ui/input-group" +import { Label } from "@/styles/radix-nova/ui/label" import { Popover, PopoverContent, PopoverTrigger, -} from "@/examples/radix/ui/popover" -import { IconInfoCircle, IconStar } from "@tabler/icons-react" +} from "@/styles/radix-nova/ui/popover" export function InputGroupButtonExample() { const [isFavorite, setIsFavorite] = React.useState(false) diff --git a/apps/v4/app/(app)/(root)/components/input-group-demo.tsx b/apps/v4/app/(app)/(root)/components/input-group-demo.tsx index ca6d52d8d4..2e4101f973 100644 --- a/apps/v4/app/(app)/(root)/components/input-group-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/input-group-demo.tsx @@ -1,9 +1,12 @@ +import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react" +import { ArrowUpIcon, Search } from "lucide-react" + import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "@/examples/radix/ui/dropdown-menu" +} from "@/styles/radix-nova/ui/dropdown-menu" import { InputGroup, InputGroupAddon, @@ -11,15 +14,13 @@ import { InputGroupInput, InputGroupText, InputGroupTextarea, -} from "@/examples/radix/ui/input-group" -import { Separator } from "@/examples/radix/ui/separator" +} from "@/styles/radix-nova/ui/input-group" +import { Separator } from "@/styles/radix-nova/ui/separator" import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/examples/radix/ui/tooltip" -import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react" -import { ArrowUpIcon, Search } from "lucide-react" +} from "@/styles/radix-nova/ui/tooltip" export function InputGroupDemo() { return ( @@ -88,7 +89,7 @@ export function InputGroupDemo() {
- +
diff --git a/apps/v4/app/(app)/(root)/components/item-demo.tsx b/apps/v4/app/(app)/(root)/components/item-demo.tsx index 2c97c53e99..9f128396c7 100644 --- a/apps/v4/app/(app)/(root)/components/item-demo.tsx +++ b/apps/v4/app/(app)/(root)/components/item-demo.tsx @@ -1,4 +1,6 @@ -import { Button } from "@/examples/radix/ui/button" +import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react" + +import { Button } from "@/styles/radix-nova/ui/button" import { Item, ItemActions, @@ -6,8 +8,7 @@ import { ItemDescription, ItemMedia, ItemTitle, -} from "@/examples/radix/ui/item" -import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react" +} from "@/styles/radix-nova/ui/item" export function ItemDemo() { return ( diff --git a/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx b/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx index 3e84b1ffa5..973c41cf3a 100644 --- a/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx +++ b/apps/v4/app/(app)/(root)/components/notion-prompt-form.tsx @@ -1,8 +1,24 @@ "use client" import { useMemo, useState } from "react" -import { Avatar, AvatarFallback, AvatarImage } from "@/examples/radix/ui/avatar" -import { Badge } from "@/examples/radix/ui/badge" +import { + IconApps, + IconArrowUp, + IconAt, + IconBook, + IconCircleDashedPlus, + IconPaperclip, + IconPlus, + IconWorld, + IconX, +} from "@tabler/icons-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/styles/radix-nova/ui/avatar" +import { Badge } from "@/styles/radix-nova/ui/badge" import { Command, CommandEmpty, @@ -10,7 +26,7 @@ import { CommandInput, CommandItem, CommandList, -} from "@/examples/radix/ui/command" +} from "@/styles/radix-nova/ui/command" import { DropdownMenu, DropdownMenuCheckboxItem, @@ -23,36 +39,25 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, -} from "@/examples/radix/ui/dropdown-menu" -import { Field, FieldLabel } from "@/examples/radix/ui/field" +} from "@/styles/radix-nova/ui/dropdown-menu" +import { Field, FieldLabel } from "@/styles/radix-nova/ui/field" import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea, -} from "@/examples/radix/ui/input-group" +} from "@/styles/radix-nova/ui/input-group" import { Popover, PopoverContent, PopoverTrigger, -} from "@/examples/radix/ui/popover" -import { Switch } from "@/examples/radix/ui/switch" +} from "@/styles/radix-nova/ui/popover" +import { Switch } from "@/styles/radix-nova/ui/switch" import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/examples/radix/ui/tooltip" -import { - IconApps, - IconArrowUp, - IconAt, - IconBook, - IconCircleDashedPlus, - IconPaperclip, - IconPlus, - IconWorld, - IconX, -} from "@tabler/icons-react" +} from "@/styles/radix-nova/ui/tooltip" const SAMPLE_DATA = { mentionable: [ diff --git a/apps/v4/app/(app)/(root)/components/spinner-badge.tsx b/apps/v4/app/(app)/(root)/components/spinner-badge.tsx index 3bff547115..3c6c7264d5 100644 --- a/apps/v4/app/(app)/(root)/components/spinner-badge.tsx +++ b/apps/v4/app/(app)/(root)/components/spinner-badge.tsx @@ -1,5 +1,5 @@ -import { Badge } from "@/examples/radix/ui/badge" -import { Spinner } from "@/examples/radix/ui/spinner" +import { Badge } from "@/styles/radix-nova/ui/badge" +import { Spinner } from "@/styles/radix-nova/ui/spinner" export function SpinnerBadge() { return ( diff --git a/apps/v4/app/(app)/(root)/components/spinner-empty.tsx b/apps/v4/app/(app)/(root)/components/spinner-empty.tsx index 1ff6b0b8b2..70914e98a7 100644 --- a/apps/v4/app/(app)/(root)/components/spinner-empty.tsx +++ b/apps/v4/app/(app)/(root)/components/spinner-empty.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/examples/radix/ui/button" +import { Button } from "@/styles/radix-nova/ui/button" import { Empty, EmptyContent, @@ -6,8 +6,8 @@ import { EmptyHeader, EmptyMedia, EmptyTitle, -} from "@/examples/radix/ui/empty" -import { Spinner } from "@/examples/radix/ui/spinner" +} from "@/styles/radix-nova/ui/empty" +import { Spinner } from "@/styles/radix-nova/ui/spinner" export function SpinnerEmpty() { return ( diff --git a/apps/v4/app/(app)/(root)/page.tsx b/apps/v4/app/(app)/(root)/page.tsx index 61f8998115..599b4cbb5b 100644 --- a/apps/v4/app/(app)/(root)/page.tsx +++ b/apps/v4/app/(app)/(root)/page.tsx @@ -63,11 +63,7 @@ export default function IndexPage() { - - - - -
+
Browse Blocks diff --git a/apps/v4/app/(create)/components/accent-picker.tsx b/apps/v4/app/(app)/create/components/accent-picker.tsx similarity index 94% rename from apps/v4/app/(create)/components/accent-picker.tsx rename to apps/v4/app/(app)/create/components/accent-picker.tsx index 7ac45b58e7..ba5cc3d45f 100644 --- a/apps/v4/app/(create)/components/accent-picker.tsx +++ b/apps/v4/app/(app)/create/components/accent-picker.tsx @@ -1,7 +1,7 @@ "use client" import { MENU_ACCENTS, type MenuAccentValue } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -9,8 +9,8 @@ import { PickerRadioGroup, PickerRadioItem, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function MenuAccentPicker({ isMobile, diff --git a/apps/v4/app/(create)/components/action-menu.tsx b/apps/v4/app/(app)/create/components/action-menu.tsx similarity index 95% rename from apps/v4/app/(create)/components/action-menu.tsx rename to apps/v4/app/(app)/create/components/action-menu.tsx index 0e6b508ede..1b21088d42 100644 --- a/apps/v4/app/(create)/components/action-menu.tsx +++ b/apps/v4/app/(app)/create/components/action-menu.tsx @@ -1,6 +1,8 @@ "use client" import Script from "next/script" +import { type RegistryItem } from "shadcn/schema" + import { Command, CommandDialog, @@ -9,10 +11,8 @@ import { CommandInput, CommandItem, CommandList, -} from "@/examples/base/ui/command" -import { type RegistryItem } from "shadcn/schema" - -import { useActionMenu } from "@/app/(create)/hooks/use-action-menu" +} from "@/styles/base-nova/ui/command" +import { useActionMenu } from "@/app/(app)/create/hooks/use-action-menu" export const CMD_K_FORWARD_TYPE = "cmd-k-forward" diff --git a/apps/v4/app/(create)/components/base-color-picker.tsx b/apps/v4/app/(app)/create/components/base-color-picker.tsx similarity index 92% rename from apps/v4/app/(create)/components/base-color-picker.tsx rename to apps/v4/app/(app)/create/components/base-color-picker.tsx index e0d1ec1d7d..7dbf8cae8c 100644 --- a/apps/v4/app/(create)/components/base-color-picker.tsx +++ b/apps/v4/app/(app)/create/components/base-color-picker.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { useMounted } from "@/hooks/use-mounted" import { BASE_COLORS, type BaseColorName } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -12,8 +12,8 @@ import { PickerRadioGroup, PickerRadioItem, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function BaseColorPicker({ isMobile, diff --git a/apps/v4/app/(create)/components/base-picker.tsx b/apps/v4/app/(app)/create/components/base-picker.tsx similarity index 94% rename from apps/v4/app/(create)/components/base-picker.tsx rename to apps/v4/app/(app)/create/components/base-picker.tsx index f43b11460d..43f611b36e 100644 --- a/apps/v4/app/(create)/components/base-picker.tsx +++ b/apps/v4/app/(app)/create/components/base-picker.tsx @@ -10,8 +10,8 @@ import { PickerRadioGroup, PickerRadioItem, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function BasePicker({ isMobile, diff --git a/apps/v4/app/(app)/create/components/chart-color-picker.tsx b/apps/v4/app/(app)/create/components/chart-color-picker.tsx new file mode 100644 index 0000000000..3cc2b4636c --- /dev/null +++ b/apps/v4/app/(app)/create/components/chart-color-picker.tsx @@ -0,0 +1,136 @@ +"use client" + +import * as React from "react" + +import { useMounted } from "@/hooks/use-mounted" +import { + BASE_COLORS, + getThemesForBaseColor, + type ChartColorName, +} from "@/registry/config" +import { LockButton } from "@/app/(app)/create/components/lock-button" +import { + Picker, + PickerContent, + PickerGroup, + PickerRadioGroup, + PickerRadioItem, + PickerSeparator, + PickerTrigger, +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" + +export function ChartColorPicker({ + isMobile, + anchorRef, +}: { + isMobile: boolean + anchorRef: React.RefObject +}) { + const mounted = useMounted() + const [params, setParams] = useDesignSystemSearchParams() + + const availableChartColors = React.useMemo( + () => getThemesForBaseColor(params.baseColor), + [params.baseColor] + ) + + const currentChartColor = React.useMemo( + () => + availableChartColors.find((theme) => theme.name === params.chartColor), + [availableChartColors, params.chartColor] + ) + + const currentChartColorIsBaseColor = React.useMemo( + () => BASE_COLORS.find((baseColor) => baseColor.name === params.chartColor), + [params.chartColor] + ) + + React.useEffect(() => { + if (!currentChartColor && availableChartColors.length > 0) { + setParams({ chartColor: availableChartColors[0].name }) + } + }, [currentChartColor, availableChartColors, setParams]) + + return ( +
+ + +
+
Chart Color
+
+ {currentChartColor?.title} +
+
+ {mounted && ( +
+ )} + + + { + setParams({ chartColor: value as ChartColorName }) + }} + > + + {availableChartColors + .filter((theme) => + BASE_COLORS.find((baseColor) => baseColor.name === theme.name) + ) + .map((theme) => ( + + {theme.title} + + ))} + + + + {availableChartColors + .filter( + (theme) => + !BASE_COLORS.find( + (baseColor) => baseColor.name === theme.name + ) + ) + .map((theme) => ( + + {theme.title} + + ))} + + + + + +
+ ) +} diff --git a/apps/v4/app/(create)/components/copy-preset.tsx b/apps/v4/app/(app)/create/components/copy-preset.tsx similarity index 89% rename from apps/v4/app/(create)/components/copy-preset.tsx rename to apps/v4/app/(app)/create/components/copy-preset.tsx index 7e94c8aec3..55024fbe4c 100644 --- a/apps/v4/app/(create)/components/copy-preset.tsx +++ b/apps/v4/app/(app)/create/components/copy-preset.tsx @@ -1,11 +1,11 @@ "use client" import * as React from "react" -import { Button } from "@/examples/base/ui/button" import { cn } from "@/lib/utils" import { copyToClipboardWithMeta } from "@/components/copy-button" -import { usePresetCode } from "@/app/(create)/hooks/use-design-system" +import { Button } from "@/styles/base-nova/ui/button" +import { usePresetCode } from "@/app/(app)/create/hooks/use-design-system" export function CopyPreset({ className }: React.ComponentProps) { const presetCode = usePresetCode() diff --git a/apps/v4/app/(app)/create/components/customizer.tsx b/apps/v4/app/(app)/create/components/customizer.tsx new file mode 100644 index 0000000000..91ba9db7b6 --- /dev/null +++ b/apps/v4/app/(app)/create/components/customizer.tsx @@ -0,0 +1,115 @@ +"use client" + +import * as React from "react" +import dynamic from "next/dynamic" +import { type RegistryItem } from "shadcn/schema" + +import { useIsMobile } from "@/hooks/use-mobile" +import { getThemesForBaseColor, STYLES } from "@/registry/config" +import { + Card, + CardContent, + CardFooter, + CardHeader, +} from "@/styles/base-nova/ui/card" +import { FieldGroup, FieldSeparator } from "@/styles/base-nova/ui/field" +import { MenuAccentPicker } from "@/app/(app)/create/components/accent-picker" +import { ActionMenu } from "@/app/(app)/create/components/action-menu" +import { BaseColorPicker } from "@/app/(app)/create/components/base-color-picker" +import { BasePicker } from "@/app/(app)/create/components/base-picker" +import { ChartColorPicker } from "@/app/(app)/create/components/chart-color-picker" +import { CopyPreset } from "@/app/(app)/create/components/copy-preset" +import { FontPicker } from "@/app/(app)/create/components/font-picker" +import { IconLibraryPicker } from "@/app/(app)/create/components/icon-library-picker" +import { MainMenu } from "@/app/(app)/create/components/main-menu" +import { MenuColorPicker } from "@/app/(app)/create/components/menu-picker" +import { RadiusPicker } from "@/app/(app)/create/components/radius-picker" +import { RandomButton } from "@/app/(app)/create/components/random-button" +import { ResetDialog } from "@/app/(app)/create/components/reset-button" +import { StylePicker } from "@/app/(app)/create/components/style-picker" +import { ThemePicker } from "@/app/(app)/create/components/theme-picker" +import { V0Button } from "@/app/(app)/create/components/v0-button" +import { FONT_HEADING_OPTIONS, FONTS } from "@/app/(app)/create/lib/fonts" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" + +// Only visible when user clicks "Create Project". +const ProjectForm = dynamic(() => + import("@/app/(app)/create/components/project-form").then( + (m) => m.ProjectForm + ) +) + +export function Customizer({ + itemsByBase, +}: { + itemsByBase: Record[]> +}) { + const [params] = useDesignSystemSearchParams() + const isMobile = useIsMobile() + const anchorRef = React.useRef(null) + + const availableThemes = React.useMemo( + () => getThemesForBaseColor(params.baseColor), + [params.baseColor] + ) + + return ( + + + + + + + + + + + + + + + + + + + + + {isMobile && } + + + + + + + + + + + + + ) +} diff --git a/apps/v4/app/(create)/components/design-system-provider.tsx b/apps/v4/app/(app)/create/components/design-system-provider.tsx similarity index 80% rename from apps/v4/app/(create)/components/design-system-provider.tsx rename to apps/v4/app/(app)/create/components/design-system-provider.tsx index 31453c1051..a06d973bc5 100644 --- a/apps/v4/app/(create)/components/design-system-provider.tsx +++ b/apps/v4/app/(app)/create/components/design-system-provider.tsx @@ -7,12 +7,12 @@ import { DEFAULT_CONFIG, type DesignSystemConfig, } from "@/registry/config" -import { useIframeMessageListener } from "@/app/(create)/hooks/use-iframe-sync" -import { FONTS } from "@/app/(create)/lib/fonts" +import { useIframeMessageListener } from "@/app/(app)/create/hooks/use-iframe-sync" +import { FONTS } from "@/app/(app)/create/lib/fonts" import { useDesignSystemSearchParams, type DesignSystemSearchParams, -} from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/lib/search-params" const THEME_STYLE_ELEMENT_ID = "design-system-theme-vars" const MANAGED_BODY_CLASS_PREFIXES = ["style-", "base-color-"] as const @@ -64,18 +64,37 @@ export function DesignSystemProvider({ history: "replace", // …or push updates into the iframe history. }) const [isReady, setIsReady] = React.useState(false) - const { style, theme, font, baseColor, menuAccent, menuColor, radius } = - searchParams + const { + style, + theme, + font, + fontHeading, + baseColor, + chartColor, + menuAccent, + menuColor, + radius, + } = searchParams const effectiveRadius = style === "lyra" ? "none" : radius const selectedFont = React.useMemo( () => FONTS.find((fontOption) => fontOption.value === font), [font] ) + const selectedHeadingFont = React.useMemo(() => { + if (fontHeading === "inherit" || fontHeading === font) { + return selectedFont + } + + return FONTS.find((fontOption) => fontOption.value === fontHeading) + }, [font, fontHeading, selectedFont]) const initialFontSansRef = React.useRef(null) + const initialFontHeadingRef = React.useRef(null) React.useEffect(() => { initialFontSansRef.current = document.documentElement.style.getPropertyValue("--font-sans") + initialFontHeadingRef.current = + document.documentElement.style.getPropertyValue("--font-heading") return () => { removeManagedBodyClasses(document.body) @@ -86,10 +105,18 @@ export function DesignSystemProvider({ "--font-sans", initialFontSansRef.current ) - return + } else { + document.documentElement.style.removeProperty("--font-sans") } - document.documentElement.style.removeProperty("--font-sans") + if (initialFontHeadingRef.current) { + document.documentElement.style.setProperty( + "--font-heading", + initialFontHeadingRef.current + ) + } else { + document.documentElement.style.removeProperty("--font-heading") + } } }, []) @@ -124,12 +151,29 @@ export function DesignSystemProvider({ // Always set --font-sans for the preview so the selected font is visible. // The font type (sans/serif/mono) is metadata for the CLI updater. if (selectedFont) { - const fontFamily = selectedFont.font.style.fontFamily - document.documentElement.style.setProperty("--font-sans", fontFamily) + document.documentElement.style.setProperty( + "--font-sans", + selectedFont.font.style.fontFamily + ) + } + + if (selectedHeadingFont) { + document.documentElement.style.setProperty( + "--font-heading", + selectedHeadingFont.font.style.fontFamily + ) } setIsReady(true) - }, [style, theme, font, baseColor, selectedFont]) + }, [ + style, + theme, + font, + fontHeading, + baseColor, + selectedFont, + selectedHeadingFont, + ]) const registryTheme = React.useMemo(() => { if (!baseColor || !theme || !menuAccent || !effectiveRadius) { @@ -140,12 +184,13 @@ export function DesignSystemProvider({ ...DEFAULT_CONFIG, baseColor, theme, + chartColor, menuAccent, radius: effectiveRadius, } return buildRegistryTheme(config) - }, [baseColor, theme, menuAccent, effectiveRadius]) + }, [baseColor, theme, chartColor, menuAccent, effectiveRadius]) // Use useLayoutEffect for synchronous CSS var updates. React.useLayoutEffect(() => { diff --git a/apps/v4/app/(app)/create/components/font-picker.tsx b/apps/v4/app/(app)/create/components/font-picker.tsx new file mode 100644 index 0000000000..04a76e463f --- /dev/null +++ b/apps/v4/app/(app)/create/components/font-picker.tsx @@ -0,0 +1,158 @@ +"use client" + +import * as React from "react" + +import { LockButton } from "@/app/(app)/create/components/lock-button" +import { + Picker, + PickerContent, + PickerGroup, + PickerLabel, + PickerRadioGroup, + PickerRadioItem, + PickerSeparator, + PickerTrigger, +} from "@/app/(app)/create/components/picker" +import { FONTS } from "@/app/(app)/create/lib/fonts" +import { + useDesignSystemSearchParams, + type DesignSystemSearchParams, +} from "@/app/(app)/create/lib/search-params" + +type FontPickerOption = { + name: string + value: string + type: string + font: { + style: { + fontFamily: string + } + } | null +} + +export function FontPicker({ + label, + param, + fonts, + isMobile, + anchorRef, +}: { + label: string + param: "font" | "fontHeading" + fonts: readonly FontPickerOption[] + isMobile: boolean + anchorRef: React.RefObject +}) { + const [params, setParams] = useDesignSystemSearchParams() + const currentValue = param === "font" ? params.font : params.fontHeading + const handleFontChange = React.useCallback( + (value: string) => { + setParams({ + [param]: value, + } as Partial) + }, + [param, setParams] + ) + + const currentFont = React.useMemo( + () => fonts.find((font) => font.value === currentValue), + [fonts, currentValue] + ) + const currentBodyFont = React.useMemo( + () => FONTS.find((font) => font.value === params.font), + [params.font] + ) + const inheritsBodyFont = param === "fontHeading" && currentValue === "inherit" + const displayFontName = inheritsBodyFont + ? currentBodyFont?.name + : currentFont?.name + const inheritFontLabel = currentBodyFont ? currentBodyFont.name : "Body font" + const groupedFonts = React.useMemo(() => { + const pickerFonts = + param === "fontHeading" + ? fonts.filter((font) => font.value !== "inherit") + : fonts + const groups = new Map() + + for (const font of pickerFonts) { + const existing = groups.get(font.type) + if (existing) { + existing.push(font) + continue + } + + groups.set(font.type, [font]) + } + + return Array.from(groups.entries()).map(([type, items]) => ({ + type, + label: `${type.charAt(0).toUpperCase()}${type.slice(1)}`, + items, + })) + }, [fonts, param]) + + return ( +
+ + +
+
{label}
+
+ {displayFontName} +
+
+
+ Aa +
+
+ + + {param === "fontHeading" ? ( + <> + + + {inheritFontLabel} + + + + + ) : null} + {groupedFonts.map((group) => ( + + {group.label} + {group.items.map((font) => ( + + {font.name} + + ))} + + ))} + + +
+ +
+ ) +} diff --git a/apps/v4/app/(create)/components/history-buttons.tsx b/apps/v4/app/(app)/create/components/history-buttons.tsx similarity index 95% rename from apps/v4/app/(create)/components/history-buttons.tsx rename to apps/v4/app/(app)/create/components/history-buttons.tsx index 391a798b5a..83f053cebc 100644 --- a/apps/v4/app/(create)/components/history-buttons.tsx +++ b/apps/v4/app/(app)/create/components/history-buttons.tsx @@ -1,11 +1,11 @@ "use client" import Script from "next/script" -import { Button } from "@/examples/base/ui/button" import { Redo02Icon, Undo02Icon } from "@hugeicons/core-free-icons" import { HugeiconsIcon } from "@hugeicons/react" -import { useHistory } from "@/app/(create)/hooks/use-history" +import { Button } from "@/styles/base-nova/ui/button" +import { useHistory } from "@/app/(app)/create/hooks/use-history" export const UNDO_FORWARD_TYPE = "undo-forward" export const REDO_FORWARD_TYPE = "redo-forward" diff --git a/apps/v4/app/(create)/components/icon-library-picker.tsx b/apps/v4/app/(app)/create/components/icon-library-picker.tsx similarity index 96% rename from apps/v4/app/(create)/components/icon-library-picker.tsx rename to apps/v4/app/(app)/create/components/icon-library-picker.tsx index 302bbc6fe3..f3a0c4c6ec 100644 --- a/apps/v4/app/(create)/components/icon-library-picker.tsx +++ b/apps/v4/app/(app)/create/components/icon-library-picker.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { iconLibraries, type IconLibraryName } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -11,8 +11,8 @@ import { PickerRadioGroup, PickerRadioItem, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" const logos = { lucide: ( diff --git a/apps/v4/app/(app)/create/components/icon-placeholder.tsx b/apps/v4/app/(app)/create/components/icon-placeholder.tsx new file mode 100644 index 0000000000..a7f100648c --- /dev/null +++ b/apps/v4/app/(app)/create/components/icon-placeholder.tsx @@ -0,0 +1,75 @@ +"use client" + +import { lazy, Suspense } from "react" +import { SquareIcon } from "lucide-react" +import type { IconLibraryName } from "shadcn/icons" + +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" + +const IconLucide = lazy(() => + import("@/registry/icons/icon-lucide").then((mod) => ({ + default: mod.IconLucide, + })) +) + +const IconTabler = lazy(() => + import("@/registry/icons/icon-tabler").then((mod) => ({ + default: mod.IconTabler, + })) +) + +const IconHugeicons = lazy(() => + import("@/registry/icons/icon-hugeicons").then((mod) => ({ + default: mod.IconHugeicons, + })) +) + +const IconPhosphor = lazy(() => + import("@/registry/icons/icon-phosphor").then((mod) => ({ + default: mod.IconPhosphor, + })) +) + +const IconRemixicon = lazy(() => + import("@/registry/icons/icon-remixicon").then((mod) => ({ + default: mod.IconRemixicon, + })) +) + +// Preload all icon renderer modules so switching libraries is instant. +// These warm the browser module cache; React.lazy resolves immediately +// for modules that are already loaded. +void import("@/registry/icons/icon-lucide") +void import("@/registry/icons/icon-tabler") +void import("@/registry/icons/icon-hugeicons") +void import("@/registry/icons/icon-phosphor") +void import("@/registry/icons/icon-remixicon") + +export function IconPlaceholder({ + ...props +}: { + [K in IconLibraryName]: string +} & React.ComponentProps<"svg">) { + const [{ iconLibrary }] = useDesignSystemSearchParams() + const iconName = props[iconLibrary] + + if (!iconName) { + return null + } + + return ( + }> + {iconLibrary === "lucide" && } + {iconLibrary === "tabler" && } + {iconLibrary === "hugeicons" && ( + + )} + {iconLibrary === "phosphor" && ( + + )} + {iconLibrary === "remixicon" && ( + + )} + + ) +} diff --git a/apps/v4/app/(create)/components/item-explorer.tsx b/apps/v4/app/(app)/create/components/item-explorer.tsx similarity index 94% rename from apps/v4/app/(create)/components/item-explorer.tsx rename to apps/v4/app/(app)/create/components/item-explorer.tsx index 022dbd82ae..dc5a46af34 100644 --- a/apps/v4/app/(create)/components/item-explorer.tsx +++ b/apps/v4/app/(app)/create/components/item-explorer.tsx @@ -2,11 +2,16 @@ import * as React from "react" import Link from "next/link" +import { ChevronRightIcon } from "lucide-react" +import { type RegistryItem } from "shadcn/schema" + +import { cn } from "@/lib/utils" +import { type Base } from "@/registry/bases" import { Collapsible, CollapsibleContent, CollapsibleTrigger, -} from "@/examples/base/ui/collapsible" +} from "@/styles/base-nova/ui/collapsible" import { Sidebar, SidebarContent, @@ -15,14 +20,9 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, -} from "@/examples/base/ui/sidebar" -import { ChevronRightIcon } from "lucide-react" -import { type RegistryItem } from "shadcn/schema" - -import { cn } from "@/lib/utils" -import { type Base } from "@/registry/bases" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" -import { groupItemsByType } from "@/app/(create)/lib/utils" +} from "@/styles/base-nova/ui/sidebar" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" +import { groupItemsByType } from "@/app/(app)/create/lib/utils" const cachedGroupedItems = React.cache( (items: Pick[]) => { diff --git a/apps/v4/app/(create)/components/lock-button.tsx b/apps/v4/app/(app)/create/components/lock-button.tsx similarity index 92% rename from apps/v4/app/(create)/components/lock-button.tsx rename to apps/v4/app/(app)/create/components/lock-button.tsx index f082c2c8e1..067804d585 100644 --- a/apps/v4/app/(create)/components/lock-button.tsx +++ b/apps/v4/app/(app)/create/components/lock-button.tsx @@ -7,7 +7,10 @@ import { import { HugeiconsIcon } from "@hugeicons/react" import { cn } from "@/lib/utils" -import { useLocks, type LockableParam } from "@/app/(create)/hooks/use-locks" +import { + useLocks, + type LockableParam, +} from "@/app/(app)/create/hooks/use-locks" export function LockButton({ param, diff --git a/apps/v4/app/(create)/components/main-menu.tsx b/apps/v4/app/(app)/create/components/main-menu.tsx similarity index 83% rename from apps/v4/app/(create)/components/main-menu.tsx rename to apps/v4/app/(app)/create/components/main-menu.tsx index 9e8fd11d3c..75c3839358 100644 --- a/apps/v4/app/(create)/components/main-menu.tsx +++ b/apps/v4/app/(app)/create/components/main-menu.tsx @@ -1,11 +1,11 @@ "use client" import * as React from "react" -import { type Button } from "@/examples/base/ui/button" import { Menu09Icon } from "@hugeicons/core-free-icons" import { HugeiconsIcon } from "@hugeicons/react" import { cn } from "@/lib/utils" +import { type Button } from "@/styles/base-nova/ui/button" import { Picker, PickerContent, @@ -14,12 +14,12 @@ import { PickerSeparator, PickerShortcut, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useActionMenuTrigger } from "@/app/(create)/hooks/use-action-menu" -import { useHistory } from "@/app/(create)/hooks/use-history" -import { useRandom } from "@/app/(create)/hooks/use-random" -import { useReset } from "@/app/(create)/hooks/use-reset" -import { useThemeToggle } from "@/app/(create)/hooks/use-theme-toggle" +} from "@/app/(app)/create/components/picker" +import { useActionMenuTrigger } from "@/app/(app)/create/hooks/use-action-menu" +import { useHistory } from "@/app/(app)/create/hooks/use-history" +import { useRandom } from "@/app/(app)/create/hooks/use-random" +import { useReset } from "@/app/(app)/create/hooks/use-reset" +import { useThemeToggle } from "@/app/(app)/create/hooks/use-theme-toggle" const APPLE_PLATFORM_REGEX = /Mac|iPhone|iPad|iPod/ @@ -73,7 +73,7 @@ export function MainMenu({ className }: React.ComponentProps) { setShowResetDialog(true)}> - Reset + Reset ⇧R diff --git a/apps/v4/app/(create)/components/menu-picker.tsx b/apps/v4/app/(app)/create/components/menu-picker.tsx similarity index 95% rename from apps/v4/app/(create)/components/menu-picker.tsx rename to apps/v4/app/(app)/create/components/menu-picker.tsx index 06d159c4b4..be9e692ce9 100644 --- a/apps/v4/app/(create)/components/menu-picker.tsx +++ b/apps/v4/app/(app)/create/components/menu-picker.tsx @@ -7,7 +7,7 @@ import { useTheme } from "next-themes" import { useMounted } from "@/hooks/use-mounted" import { type MenuColorValue } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -17,11 +17,11 @@ import { PickerRadioItem, PickerSeparator, PickerTrigger, -} from "@/app/(create)/components/picker" +} from "@/app/(app)/create/components/picker" import { isTranslucentMenuColor, useDesignSystemSearchParams, -} from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/lib/search-params" type ColorChoice = "default" | "inverted" type SurfaceChoice = "solid" | "translucent" @@ -104,7 +104,7 @@ export function MenuColorPicker({
Menu
-
+
{currentMenu?.label}
diff --git a/apps/v4/app/(create)/components/mode-switcher.tsx b/apps/v4/app/(app)/create/components/mode-switcher.tsx similarity index 94% rename from apps/v4/app/(create)/components/mode-switcher.tsx rename to apps/v4/app/(app)/create/components/mode-switcher.tsx index 4356113c82..6cd8b4d94a 100644 --- a/apps/v4/app/(create)/components/mode-switcher.tsx +++ b/apps/v4/app/(app)/create/components/mode-switcher.tsx @@ -2,10 +2,10 @@ import * as React from "react" import Script from "next/script" -import { Button } from "@/examples/base/ui/button" import { cn } from "@/lib/utils" -import { useThemeToggle } from "@/app/(create)/hooks/use-theme-toggle" +import { Button } from "@/styles/base-nova/ui/button" +import { useThemeToggle } from "@/app/(app)/create/hooks/use-theme-toggle" export const DARK_MODE_FORWARD_TYPE = "dark-mode-forward" diff --git a/apps/v4/app/(create)/components/picker.tsx b/apps/v4/app/(app)/create/components/picker.tsx similarity index 98% rename from apps/v4/app/(create)/components/picker.tsx rename to apps/v4/app/(app)/create/components/picker.tsx index 8e2775389f..44b647d859 100644 --- a/apps/v4/app/(create)/components/picker.tsx +++ b/apps/v4/app/(app)/create/components/picker.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { Menu as MenuPrimitive } from "@base-ui/react/menu" import { cn } from "@/registry/bases/base/lib/utils" -import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder" +import { IconPlaceholder } from "@/app/(app)/create/components/icon-placeholder" function Picker({ ...props }: MenuPrimitive.Root.Props) { return @@ -19,7 +19,7 @@ function PickerTrigger({ className, ...props }: MenuPrimitive.Trigger.Props) {
- {style?.icon && ( -
- {React.cloneElement(style.icon, { - className: "size-4", - })} -
- )} {preset.description}
diff --git a/apps/v4/app/(create)/components/preview-style.tsx b/apps/v4/app/(app)/create/components/preview-style.tsx similarity index 100% rename from apps/v4/app/(create)/components/preview-style.tsx rename to apps/v4/app/(app)/create/components/preview-style.tsx diff --git a/apps/v4/app/(app)/create/components/preview-switcher.tsx b/apps/v4/app/(app)/create/components/preview-switcher.tsx new file mode 100644 index 0000000000..1daf933785 --- /dev/null +++ b/apps/v4/app/(app)/create/components/preview-switcher.tsx @@ -0,0 +1,37 @@ +"use client" + +import { Button } from "@/registry/new-york-v4/ui/button" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" + +const PREVIEW_ITEMS = [ + { label: "01", value: "preview-02" }, + { label: "02", value: "preview" }, +] + +export function PreviewSwitcher() { + const [params, setParams] = useDesignSystemSearchParams() + + const isPreview = + params.item === "preview" || params.item.startsWith("preview-0") + + if (!isPreview) { + return null + } + + return ( +
+ {PREVIEW_ITEMS.map((item) => ( + + ))} +
+ ) +} diff --git a/apps/v4/app/(create)/components/preview.tsx b/apps/v4/app/(app)/create/components/preview.tsx similarity index 82% rename from apps/v4/app/(create)/components/preview.tsx rename to apps/v4/app/(app)/create/components/preview.tsx index cf99e7feac..56ab4e6c5e 100644 --- a/apps/v4/app/(create)/components/preview.tsx +++ b/apps/v4/app/(app)/create/components/preview.tsx @@ -2,18 +2,20 @@ import * as React from "react" -import { CMD_K_FORWARD_TYPE } from "@/app/(create)/components/action-menu" +import { CMD_K_FORWARD_TYPE } from "@/app/(app)/create/components/action-menu" import { REDO_FORWARD_TYPE, UNDO_FORWARD_TYPE, -} from "@/app/(create)/components/history-buttons" -import { DARK_MODE_FORWARD_TYPE } from "@/app/(create)/components/mode-switcher" -import { RANDOMIZE_FORWARD_TYPE } from "@/app/(create)/components/random-button" -import { sendToIframe } from "@/app/(create)/hooks/use-iframe-sync" +} from "@/app/(app)/create/components/history-buttons" +import { DARK_MODE_FORWARD_TYPE } from "@/app/(app)/create/components/mode-switcher" +import { PreviewSwitcher } from "@/app/(app)/create/components/preview-switcher" +import { RANDOMIZE_FORWARD_TYPE } from "@/app/(app)/create/components/random-button" +import { sendToIframe } from "@/app/(app)/create/hooks/use-iframe-sync" +import { RESET_FORWARD_TYPE } from "@/app/(app)/create/hooks/use-reset" import { serializeDesignSystemSearchParams, useDesignSystemSearchParams, -} from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/lib/search-params" // Hoisted — avoids recreating on every message event. (js-hoist-regexp) const MAC_REGEX = /Mac|iPhone|iPad|iPod/ @@ -70,6 +72,15 @@ function handleMessage(event: MessageEvent) { cancelable: true, }) ) + } else if (type === RESET_FORWARD_TYPE) { + document.dispatchEvent( + new KeyboardEvent("keydown", { + key: "R", + shiftKey: true, + bubbles: true, + cancelable: true, + }) + ) } else if (type === DARK_MODE_FORWARD_TYPE) { document.dispatchEvent( new KeyboardEvent("keydown", { @@ -137,6 +148,7 @@ export function Preview() { title="Preview" />
+
) } diff --git a/apps/v4/app/(create)/components/project-form.tsx b/apps/v4/app/(app)/create/components/project-form.tsx similarity index 59% rename from apps/v4/app/(create)/components/project-form.tsx rename to apps/v4/app/(app)/create/components/project-form.tsx index d64a7b7039..5d900ae107 100644 --- a/apps/v4/app/(create)/components/project-form.tsx +++ b/apps/v4/app/(app)/create/components/project-form.tsx @@ -1,7 +1,14 @@ "use client" import * as React from "react" -import { Button } from "@/examples/base/ui/button" +import { Copy01Icon, Globe02Icon, Tick02Icon } from "@hugeicons/core-free-icons" +import { HugeiconsIcon } from "@hugeicons/react" + +import { cn } from "@/lib/utils" +import { useConfig } from "@/hooks/use-config" +import { copyToClipboardWithMeta } from "@/components/copy-button" +import { BASES, type BaseName } from "@/registry/config" +import { Button } from "@/styles/base-nova/ui/button" import { Dialog, DialogContent, @@ -10,7 +17,7 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from "@/examples/base/ui/dialog" +} from "@/styles/base-nova/ui/dialog" import { Field, FieldContent, @@ -20,32 +27,26 @@ import { FieldSeparator, FieldSet, FieldTitle, -} from "@/examples/base/ui/field" -import { RadioGroup, RadioGroupItem } from "@/examples/base/ui/radio-group" -import { Switch } from "@/examples/base/ui/switch" +} from "@/styles/base-nova/ui/field" +import { RadioGroup, RadioGroupItem } from "@/styles/base-nova/ui/radio-group" +import { Switch } from "@/styles/base-nova/ui/switch" import { Tabs, TabsContent, TabsList, TabsTrigger, -} from "@/examples/base/ui/tabs" -import { Copy01Icon, Globe02Icon, Tick02Icon } from "@hugeicons/core-free-icons" -import { HugeiconsIcon } from "@hugeicons/react" - -import { cn } from "@/lib/utils" -import { useConfig } from "@/hooks/use-config" -import { copyToClipboardWithMeta } from "@/components/copy-button" -import { usePresetCode } from "@/app/(create)/hooks/use-design-system" +} from "@/styles/base-nova/ui/tabs" +import { usePresetCode } from "@/app/(app)/create/hooks/use-design-system" import { useDesignSystemSearchParams, type DesignSystemSearchParams, -} from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/lib/search-params" import { getFramework, getTemplateValue, NO_MONOREPO_FRAMEWORKS, TEMPLATES, -} from "@/app/(create)/lib/templates" +} from "@/app/(app)/create/lib/templates" const TURBOREPO_LOGO = 'Turborepo' @@ -86,7 +87,7 @@ export function ProjectForm({ const rtlFlag = params.rtl ? " --rtl" : "" const flags = `${presetFlag}${baseFlag}${templateFlag}${monorepoFlag}${rtlFlag}` - return IS_LOCAL_DEV && !process.env.NEXT_PUBLIC_RC + return IS_LOCAL_DEV ? { pnpm: `shadcn init${flags}`, npm: `shadcn init${flags}`, @@ -129,69 +130,76 @@ export function ProjectForm({ }> Create Project - + Create Project - Pick a template and configure your project. Available for all major - React frameworks. + Pick a template and configure your project. - - - Template - - - -
- - Options - - - - + + + + Template + + + + + Base + + + +
+ + Options + + + + + Create a monorepo + + { + const framework = getFramework(params.template ?? "next") + setParams({ + template: getTemplateValue( + framework, + checked === true + ) as typeof params.template, + }) }} /> - Create a monorepo - - { - const framework = getFramework(params.template ?? "next") - setParams({ - template: getTemplateValue( - framework, - checked === true - ) as typeof params.template, - }) - }} - /> - - - - - - Enable RTL support - - - setParams({ rtl: checked === true }) - } - /> - -
-
- +
+ + + + + Enable RTL support + + + setParams({ rtl: checked === true }) + } + /> + +
+
+
+
-
- +
+ {PACKAGE_MANAGERS.map((manager) => { return ( {manager} @@ -234,7 +242,7 @@ export function ProjectForm({ {Object.entries(commands).map(([key, cmd]) => { return ( -
+
{cmd} @@ -281,23 +289,26 @@ const TemplateGrid = React.memo(function TemplateGrid({ {TEMPLATES.map((item) => ( - - + +
- {item.title} + {item.title}
) }) + +const BaseGrid = React.memo(function BaseGrid({ + base, + setParams, +}: { + base: DesignSystemSearchParams["base"] + setParams: ReturnType[1] +}) { + const handleBaseChange = React.useCallback( + (value: string) => { + setParams({ base: value as BaseName }) + }, + [setParams] + ) + + return ( + + {BASES.map((item) => ( + + + +
+ {item.title} + + + + + ))} + + ) +}) diff --git a/apps/v4/app/(create)/components/radius-picker.tsx b/apps/v4/app/(app)/create/components/radius-picker.tsx similarity index 94% rename from apps/v4/app/(create)/components/radius-picker.tsx rename to apps/v4/app/(app)/create/components/radius-picker.tsx index 05653604bb..5107afa28c 100644 --- a/apps/v4/app/(create)/components/radius-picker.tsx +++ b/apps/v4/app/(app)/create/components/radius-picker.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { RADII, type RadiusValue } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -12,8 +12,8 @@ import { PickerRadioItem, PickerSeparator, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function RadiusPicker({ isMobile, diff --git a/apps/v4/app/(create)/components/random-button.tsx b/apps/v4/app/(app)/create/components/random-button.tsx similarity index 80% rename from apps/v4/app/(create)/components/random-button.tsx rename to apps/v4/app/(app)/create/components/random-button.tsx index cc23a4cfe8..543fc88f00 100644 --- a/apps/v4/app/(create)/components/random-button.tsx +++ b/apps/v4/app/(app)/create/components/random-button.tsx @@ -1,12 +1,13 @@ "use client" import Script from "next/script" -import { Button } from "@/examples/base/ui/button" import { DiceFaces05Icon } from "@hugeicons/core-free-icons" import { HugeiconsIcon } from "@hugeicons/react" import { cn } from "@/lib/utils" -import { useRandom } from "@/app/(create)/hooks/use-random" +import { Button } from "@/styles/base-nova/ui/button" +import { useRandom } from "@/app/(app)/create/hooks/use-random" +import { RESET_FORWARD_TYPE } from "@/app/(app)/create/hooks/use-reset" export const RANDOMIZE_FORWARD_TYPE = "randomize-forward" @@ -40,7 +41,7 @@ export function RandomizeScript() { dangerouslySetInnerHTML={{ __html: ` (function() { - // Forward R key + // Forward r key (shuffle) and Shift+R (reset). document.addEventListener('keydown', function(e) { if ((e.key === 'r' || e.key === 'R') && !e.metaKey && !e.ctrlKey) { if ( @@ -53,8 +54,11 @@ export function RandomizeScript() { } e.preventDefault(); if (window.parent && window.parent !== window) { + var type = e.shiftKey + ? '${RESET_FORWARD_TYPE}' + : '${RANDOMIZE_FORWARD_TYPE}'; window.parent.postMessage({ - type: '${RANDOMIZE_FORWARD_TYPE}', + type: type, key: e.key }, '*'); } diff --git a/apps/v4/app/(create)/components/reset-button.tsx b/apps/v4/app/(app)/create/components/reset-button.tsx similarity index 89% rename from apps/v4/app/(create)/components/reset-button.tsx rename to apps/v4/app/(app)/create/components/reset-button.tsx index c68e4301c2..99ff122a8f 100644 --- a/apps/v4/app/(create)/components/reset-button.tsx +++ b/apps/v4/app/(app)/create/components/reset-button.tsx @@ -9,9 +9,8 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/examples/base/ui/alert-dialog" - -import { useReset } from "@/app/(create)/hooks/use-reset" +} from "@/styles/base-nova/ui/alert-dialog" +import { useReset } from "@/app/(app)/create/hooks/use-reset" export function ResetDialog() { const { showResetDialog, setShowResetDialog, confirmReset } = useReset() diff --git a/apps/v4/app/(create)/components/share-button.tsx b/apps/v4/app/(app)/create/components/share-button.tsx similarity index 87% rename from apps/v4/app/(create)/components/share-button.tsx rename to apps/v4/app/(app)/create/components/share-button.tsx index 51c118cf2e..c88a1ff490 100644 --- a/apps/v4/app/(create)/components/share-button.tsx +++ b/apps/v4/app/(app)/create/components/share-button.tsx @@ -1,13 +1,13 @@ "use client" import * as React from "react" -import { Button } from "@/examples/base/ui/button" import { Share03Icon, Tick02Icon } from "@hugeicons/core-free-icons" import { HugeiconsIcon } from "@hugeicons/react" import { copyToClipboardWithMeta } from "@/components/copy-button" -import { usePresetCode } from "@/app/(create)/hooks/use-design-system" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +import { Button } from "@/styles/base-nova/ui/button" +import { usePresetCode } from "@/app/(app)/create/hooks/use-design-system" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function ShareButton() { const [params] = useDesignSystemSearchParams() diff --git a/apps/v4/app/(create)/components/style-picker.tsx b/apps/v4/app/(app)/create/components/style-picker.tsx similarity index 91% rename from apps/v4/app/(create)/components/style-picker.tsx rename to apps/v4/app/(app)/create/components/style-picker.tsx index 511df2bca9..5d2dc2f975 100644 --- a/apps/v4/app/(create)/components/style-picker.tsx +++ b/apps/v4/app/(app)/create/components/style-picker.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { type Style, type StyleName } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -11,8 +11,8 @@ import { PickerRadioGroup, PickerRadioItem, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function StylePicker({ styles, diff --git a/apps/v4/app/(create)/components/theme-picker.tsx b/apps/v4/app/(app)/create/components/theme-picker.tsx similarity index 94% rename from apps/v4/app/(create)/components/theme-picker.tsx rename to apps/v4/app/(app)/create/components/theme-picker.tsx index 00a7f52eaf..b3d3b47c69 100644 --- a/apps/v4/app/(create)/components/theme-picker.tsx +++ b/apps/v4/app/(app)/create/components/theme-picker.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { useMounted } from "@/hooks/use-mounted" import { BASE_COLORS, type Theme, type ThemeName } from "@/registry/config" -import { LockButton } from "@/app/(create)/components/lock-button" +import { LockButton } from "@/app/(app)/create/components/lock-button" import { Picker, PickerContent, @@ -13,8 +13,8 @@ import { PickerRadioItem, PickerSeparator, PickerTrigger, -} from "@/app/(create)/components/picker" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/components/picker" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function ThemePicker({ themes, diff --git a/apps/v4/app/(create)/components/v0-button.tsx b/apps/v4/app/(app)/create/components/v0-button.tsx similarity index 88% rename from apps/v4/app/(create)/components/v0-button.tsx rename to apps/v4/app/(app)/create/components/v0-button.tsx index 627065d0e5..eaff8b17b5 100644 --- a/apps/v4/app/(create)/components/v0-button.tsx +++ b/apps/v4/app/(app)/create/components/v0-button.tsx @@ -1,14 +1,14 @@ "use client" import * as React from "react" -import { Button } from "@/examples/base/ui/button" -import { Skeleton } from "@/examples/base/ui/skeleton" import { cn } from "@/lib/utils" import { useIsMobile } from "@/hooks/use-mobile" import { useMounted } from "@/hooks/use-mounted" import { Icons } from "@/components/icons" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" +import { Button } from "@/styles/base-nova/ui/button" +import { Skeleton } from "@/styles/base-nova/ui/skeleton" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" export function V0Button({ className }: { className?: string }) { const [params] = useDesignSystemSearchParams() diff --git a/apps/v4/app/(create)/components/welcome-dialog.tsx b/apps/v4/app/(app)/create/components/welcome-dialog.tsx similarity index 95% rename from apps/v4/app/(create)/components/welcome-dialog.tsx rename to apps/v4/app/(app)/create/components/welcome-dialog.tsx index 15a98d6d45..885df46a4a 100644 --- a/apps/v4/app/(create)/components/welcome-dialog.tsx +++ b/apps/v4/app/(app)/create/components/welcome-dialog.tsx @@ -1,7 +1,9 @@ "use client" import * as React from "react" -import { Button } from "@/examples/base/ui/button" + +import { Icons } from "@/components/icons" +import { Button } from "@/styles/base-nova/ui/button" import { Dialog, DialogClose, @@ -10,9 +12,7 @@ import { DialogFooter, DialogHeader, DialogTitle, -} from "@/examples/base/ui/dialog" - -import { Icons } from "@/components/icons" +} from "@/styles/base-nova/ui/dialog" const STORAGE_KEY = "shadcn-create-welcome-dialog" diff --git a/apps/v4/app/(create)/hooks/use-action-menu.ts b/apps/v4/app/(app)/create/hooks/use-action-menu.ts similarity index 95% rename from apps/v4/app/(create)/hooks/use-action-menu.ts rename to apps/v4/app/(app)/create/hooks/use-action-menu.ts index 59b03b975f..0ad363e74b 100644 --- a/apps/v4/app/(create)/hooks/use-action-menu.ts +++ b/apps/v4/app/(app)/create/hooks/use-action-menu.ts @@ -4,8 +4,8 @@ import * as React from "react" import { type RegistryItem } from "shadcn/schema" import useSWR from "swr" -import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" -import { groupItemsByType } from "@/app/(create)/lib/utils" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" +import { groupItemsByType } from "@/app/(app)/create/lib/utils" const ACTION_MENU_OPEN_KEY = "create:action-menu-open" diff --git a/apps/v4/app/(app)/create/hooks/use-design-system.ts b/apps/v4/app/(app)/create/hooks/use-design-system.ts new file mode 100644 index 0000000000..05f92c83e0 --- /dev/null +++ b/apps/v4/app/(app)/create/hooks/use-design-system.ts @@ -0,0 +1,11 @@ +"use client" + +import { getPresetCode } from "@/app/(app)/create/lib/preset-code" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" + +// Returns the canonical preset code derived from the current search params. +export function usePresetCode() { + const [params] = useDesignSystemSearchParams() + + return getPresetCode(params) +} diff --git a/apps/v4/app/(create)/hooks/use-history.tsx b/apps/v4/app/(app)/create/hooks/use-history.tsx similarity index 80% rename from apps/v4/app/(create)/hooks/use-history.tsx rename to apps/v4/app/(app)/create/hooks/use-history.tsx index 8612005bf0..ad7f341431 100644 --- a/apps/v4/app/(create)/hooks/use-history.tsx +++ b/apps/v4/app/(app)/create/hooks/use-history.tsx @@ -1,7 +1,8 @@ "use client" import * as React from "react" -import { usePathname, useRouter, useSearchParams } from "next/navigation" +import { Suspense } from "react" +import { useRouter, useSearchParams } from "next/navigation" type HistoryContextValue = { canGoBack: boolean @@ -12,12 +13,28 @@ type HistoryContextValue = { const HistoryContext = React.createContext(null) -export function HistoryProvider({ children }: { children: React.ReactNode }) { - const router = useRouter() - const pathname = usePathname() +// Reads useSearchParams() in its own Suspense boundary so the +// provider never blanks out children while search params resolve. +function PresetSync({ + onPresetChange, +}: { + onPresetChange: (preset: string) => void +}) { const searchParams = useSearchParams() const preset = searchParams.get("preset") ?? "" + React.useEffect(() => { + onPresetChange(preset) + }, [preset, onPresetChange]) + + return null +} + +export function HistoryProvider({ children }: { children: React.ReactNode }) { + const router = useRouter() + + const [preset, setPreset] = React.useState("") + const entriesRef = React.useRef([preset]) const indexRef = React.useRef(0) const maxIndexRef = React.useRef(0) @@ -26,6 +43,10 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) { const [index, setIndex] = React.useState(0) const [maxIndex, setMaxIndex] = React.useState(0) + const onPresetChange = React.useCallback((nextPreset: string) => { + setPreset(nextPreset) + }, []) + React.useEffect(() => { if (isNavigatingRef.current) { isNavigatingRef.current = false @@ -67,9 +88,10 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) { } else { params.delete("preset") } + const pathname = window.location.pathname const query = params.toString() router.replace(query ? `${pathname}?${query}` : pathname) - }, [pathname, router]) + }, [router]) const goForward = React.useCallback(() => { if (indexRef.current >= maxIndexRef.current) { @@ -88,9 +110,10 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) { } else { params.delete("preset") } + const pathname = window.location.pathname const query = params.toString() router.replace(query ? `${pathname}?${query}` : pathname) - }, [pathname, router]) + }, [router]) React.useEffect(() => { const down = (e: KeyboardEvent) => { @@ -133,7 +156,14 @@ export function HistoryProvider({ children }: { children: React.ReactNode }) { [canGoBack, canGoForward, goBack, goForward] ) - return {children} + return ( + + + + + {children} + + ) } export function useHistory() { diff --git a/apps/v4/app/(create)/hooks/use-iframe-sync.tsx b/apps/v4/app/(app)/create/hooks/use-iframe-sync.tsx similarity index 94% rename from apps/v4/app/(create)/hooks/use-iframe-sync.tsx rename to apps/v4/app/(app)/create/hooks/use-iframe-sync.tsx index ec0bc70ad3..64b4c58ae6 100644 --- a/apps/v4/app/(create)/hooks/use-iframe-sync.tsx +++ b/apps/v4/app/(app)/create/hooks/use-iframe-sync.tsx @@ -2,7 +2,7 @@ import * as React from "react" -import type { DesignSystemSearchParams } from "@/app/(create)/lib/search-params" +import type { DesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" type ParentToIframeMessage = { type: "design-system-params" diff --git a/apps/v4/app/(create)/hooks/use-locks.tsx b/apps/v4/app/(app)/create/hooks/use-locks.tsx similarity index 80% rename from apps/v4/app/(create)/hooks/use-locks.tsx rename to apps/v4/app/(app)/create/hooks/use-locks.tsx index 457b12d606..2cc1597aba 100644 --- a/apps/v4/app/(create)/hooks/use-locks.tsx +++ b/apps/v4/app/(app)/create/hooks/use-locks.tsx @@ -6,8 +6,10 @@ export type LockableParam = | "style" | "baseColor" | "theme" + | "chartColor" | "iconLibrary" | "font" + | "fontHeading" | "menuAccent" | "menuColor" | "radius" @@ -22,10 +24,15 @@ const LocksContext = React.createContext(null) export function LocksProvider({ children }: { children: React.ReactNode }) { const [locks, setLocks] = React.useState>(new Set()) + const locksRef = React.useRef(locks) + React.useEffect(() => { + locksRef.current = locks + }, [locks]) + // Stable callback — reads from ref so it doesn't change on every lock toggle. const isLocked = React.useCallback( - (param: LockableParam) => locks.has(param), - [locks] + (param: LockableParam) => locksRef.current.has(param), + [] ) const toggleLock = React.useCallback((param: LockableParam) => { diff --git a/apps/v4/app/(create)/hooks/use-random.tsx b/apps/v4/app/(app)/create/hooks/use-random.tsx similarity index 73% rename from apps/v4/app/(create)/hooks/use-random.tsx rename to apps/v4/app/(app)/create/hooks/use-random.tsx index 950b773440..45867c8a0b 100644 --- a/apps/v4/app/(create)/hooks/use-random.tsx +++ b/apps/v4/app/(app)/create/hooks/use-random.tsx @@ -10,18 +10,19 @@ import { MENU_COLORS, RADII, STYLES, + type FontHeadingValue, } from "@/registry/config" -import { useLocks } from "@/app/(create)/hooks/use-locks" -import { FONTS } from "@/app/(create)/lib/fonts" +import { useLocks } from "@/app/(app)/create/hooks/use-locks" +import { FONTS } from "@/app/(app)/create/lib/fonts" import { applyBias, RANDOMIZE_BIASES, type RandomizeContext, -} from "@/app/(create)/lib/randomize-biases" +} from "@/app/(app)/create/lib/randomize-biases" import { isTranslucentMenuColor, useDesignSystemSearchParams, -} from "@/app/(create)/lib/search-params" +} from "@/app/(app)/create/lib/search-params" function randomItem(array: readonly T[]): T { return array[Math.floor(Math.random() * array.length)] @@ -62,9 +63,41 @@ export function useRandom() { const selectedTheme = locks.has("theme") ? paramsRef.current.theme : randomItem(availableThemes).name + context.theme = selectedTheme + + const availableChartColors = applyBias( + getThemesForBaseColor(baseColor), + context, + RANDOMIZE_BIASES.chartColors + ) + const selectedChartColor = locks.has("chartColor") + ? paramsRef.current.chartColor + : randomItem(availableChartColors).name + context.chartColor = selectedChartColor const selectedFont = locks.has("font") ? paramsRef.current.font : randomItem(availableFonts).value + context.font = selectedFont + + // Pick heading font: ~70% inherit, ~30% distinct with cross-category contrast. + let selectedFontHeading: FontHeadingValue + if (locks.has("fontHeading")) { + selectedFontHeading = paramsRef.current.fontHeading + } else if (Math.random() < 0.7) { + selectedFontHeading = "inherit" + } else { + const bodyType = availableFonts.find( + (f) => f.value === selectedFont + )?.type + const contrastFonts = availableFonts.filter( + (f) => f.type !== bodyType && f.value !== selectedFont + ) + selectedFontHeading = ( + contrastFonts.length > 0 + ? randomItem(contrastFonts) + : randomItem(availableFonts) + ).value as FontHeadingValue + } const selectedRadius = locks.has("radius") ? paramsRef.current.radius : randomItem(availableRadii).name @@ -91,16 +124,16 @@ export function useRandom() { : paramsRef.current.menuAccent : randomItem(MENU_ACCENTS).value - context.theme = selectedTheme - context.font = selectedFont context.radius = selectedRadius const nextParams = { style: selectedStyle, baseColor, theme: selectedTheme, + chartColor: selectedChartColor, iconLibrary: selectedIconLibrary, font: selectedFont, + fontHeading: selectedFontHeading, menuAccent: selectedMenuAccent, menuColor: selectedMenuColor, radius: selectedRadius, @@ -123,7 +156,7 @@ export function useRandom() { React.useEffect(() => { const down = (e: KeyboardEvent) => { - if ((e.key === "r" || e.key === "R") && !e.metaKey && !e.ctrlKey) { + if (e.key === "r" && !e.shiftKey && !e.metaKey && !e.ctrlKey) { if ( (e.target instanceof HTMLElement && e.target.isContentEditable) || e.target instanceof HTMLInputElement || diff --git a/apps/v4/app/(app)/create/hooks/use-reset.tsx b/apps/v4/app/(app)/create/hooks/use-reset.tsx new file mode 100644 index 0000000000..52e6b78897 --- /dev/null +++ b/apps/v4/app/(app)/create/hooks/use-reset.tsx @@ -0,0 +1,103 @@ +"use client" + +import * as React from "react" +import useSWR from "swr" + +import { DEFAULT_CONFIG, PRESETS } from "@/registry/config" +import { useDesignSystemSearchParams } from "@/app/(app)/create/lib/search-params" + +const RESET_DIALOG_KEY = "create:reset-dialog-open" +export const RESET_FORWARD_TYPE = "reset-forward" + +export function useReset() { + const [params, setParams] = useDesignSystemSearchParams() + const { data: showResetDialog = false, mutate: setShowResetDialogData } = + useSWR(RESET_DIALOG_KEY, { + fallbackData: false, + revalidateOnFocus: false, + revalidateIfStale: false, + revalidateOnReconnect: false, + }) + + const reset = React.useCallback(() => { + const preset = + PRESETS.find( + (preset) => preset.base === params.base && preset.style === params.style + ) ?? DEFAULT_CONFIG + + setParams({ + base: params.base, + style: params.style, + baseColor: preset.baseColor, + theme: preset.theme, + chartColor: preset.chartColor, + iconLibrary: preset.iconLibrary, + font: preset.font, + fontHeading: preset.fontHeading, + menuAccent: preset.menuAccent, + menuColor: preset.menuColor, + radius: preset.radius, + template: DEFAULT_CONFIG.template, + item: params.item, + }) + }, [setParams, params.base, params.style, params.item]) + + const handleShowResetDialogChange = React.useCallback( + (open: boolean) => { + void setShowResetDialogData(open, { revalidate: false }) + }, + [setShowResetDialogData] + ) + + const confirmReset = React.useCallback(() => { + reset() + void setShowResetDialogData(false, { revalidate: false }) + }, [reset, setShowResetDialogData]) + + const showResetDialogRef = React.useRef(showResetDialog) + React.useEffect(() => { + showResetDialogRef.current = showResetDialog + }, [showResetDialog]) + + const confirmResetRef = React.useRef(confirmReset) + React.useEffect(() => { + confirmResetRef.current = confirmReset + }, [confirmReset]) + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "R" && e.shiftKey && !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() + + // If the dialog is already open, confirm the reset. + if (showResetDialogRef.current) { + confirmResetRef.current() + return + } + + handleShowResetDialogChange(true) + } + } + + document.addEventListener("keydown", down) + return () => { + document.removeEventListener("keydown", down) + } + }, [handleShowResetDialogChange]) + + return { + reset, + showResetDialog, + setShowResetDialog: handleShowResetDialogChange, + confirmReset, + } +} diff --git a/apps/v4/app/(create)/hooks/use-theme-toggle.tsx b/apps/v4/app/(app)/create/hooks/use-theme-toggle.tsx similarity index 100% rename from apps/v4/app/(create)/hooks/use-theme-toggle.tsx rename to apps/v4/app/(app)/create/hooks/use-theme-toggle.tsx diff --git a/apps/v4/app/(create)/create/layout.tsx b/apps/v4/app/(app)/create/layout.tsx similarity index 66% rename from apps/v4/app/(create)/create/layout.tsx rename to apps/v4/app/(app)/create/layout.tsx index 6c80ef2294..a137fdbc4b 100644 --- a/apps/v4/app/(create)/create/layout.tsx +++ b/apps/v4/app/(app)/create/layout.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react" -import { HistoryProvider } from "@/app/(create)/hooks/use-history" -import { LocksProvider } from "@/app/(create)/hooks/use-locks" +import { HistoryProvider } from "@/app/(app)/create/hooks/use-history" +import { LocksProvider } from "@/app/(app)/create/hooks/use-locks" export default function CreateLayout({ children, diff --git a/apps/v4/app/(create)/lib/api.ts b/apps/v4/app/(app)/create/lib/api.ts similarity index 97% rename from apps/v4/app/(create)/lib/api.ts rename to apps/v4/app/(app)/create/lib/api.ts index a44815d84d..d88bce87a0 100644 --- a/apps/v4/app/(create)/lib/api.ts +++ b/apps/v4/app/(app)/create/lib/api.ts @@ -6,7 +6,7 @@ import { BASES, getThemesForBaseColor, type BaseName } from "@/registry/config" import { ALLOWED_ITEM_TYPES, EXCLUDED_ITEMS, -} from "@/app/(create)/lib/constants" +} from "@/app/(app)/create/lib/constants" export async function getItemsForBase(base: BaseName) { const { Index } = await import("@/registry/bases/__index__") diff --git a/apps/v4/app/(create)/lib/constants.ts b/apps/v4/app/(app)/create/lib/constants.ts similarity index 100% rename from apps/v4/app/(create)/lib/constants.ts rename to apps/v4/app/(app)/create/lib/constants.ts diff --git a/apps/v4/app/(app)/create/lib/fonts.ts b/apps/v4/app/(app)/create/lib/fonts.ts new file mode 100644 index 0000000000..7deda364ff --- /dev/null +++ b/apps/v4/app/(app)/create/lib/fonts.ts @@ -0,0 +1,233 @@ +import { + DM_Sans, + Figtree, + Geist, + Geist_Mono, + IBM_Plex_Sans, + Instrument_Sans, + Inter, + JetBrains_Mono, + Lora, + Manrope, + Merriweather, + Montserrat, + Noto_Sans, + Noto_Serif, + Nunito_Sans, + Outfit, + Oxanium, + Playfair_Display, + Public_Sans, + Raleway, + Roboto, + Roboto_Slab, + Source_Sans_3, + Space_Grotesk, +} from "next/font/google" + +import { FONT_DEFINITIONS, type FontName } from "@/lib/font-definitions" + +type PreviewFont = ReturnType + +const geistSans = Geist({ + subsets: ["latin"], + variable: "--font-geist-sans", +}) + +const inter = Inter({ + subsets: ["latin"], + variable: "--font-inter", +}) + +const notoSans = Noto_Sans({ + subsets: ["latin"], + variable: "--font-noto-sans", +}) + +const nunitoSans = Nunito_Sans({ + subsets: ["latin"], + variable: "--font-nunito-sans", +}) + +const figtree = Figtree({ + subsets: ["latin"], + variable: "--font-figtree", +}) + +const roboto = Roboto({ + subsets: ["latin"], + variable: "--font-roboto", +}) + +const raleway = Raleway({ + subsets: ["latin"], + variable: "--font-raleway", +}) + +const dmSans = DM_Sans({ + subsets: ["latin"], + variable: "--font-dm-sans", +}) + +const publicSans = Public_Sans({ + subsets: ["latin"], + variable: "--font-public-sans", +}) + +const outfit = Outfit({ + subsets: ["latin"], + variable: "--font-outfit", +}) + +const oxanium = Oxanium({ + subsets: ["latin"], + variable: "--font-oxanium", +}) + +const manrope = Manrope({ + subsets: ["latin"], + variable: "--font-manrope", +}) + +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + variable: "--font-space-grotesk", +}) + +const montserrat = Montserrat({ + subsets: ["latin"], + variable: "--font-montserrat", +}) + +const ibmPlexSans = IBM_Plex_Sans({ + subsets: ["latin"], + variable: "--font-ibm-plex-sans", +}) + +const sourceSans3 = Source_Sans_3({ + subsets: ["latin"], + variable: "--font-source-sans-3", +}) + +const instrumentSans = Instrument_Sans({ + subsets: ["latin"], + variable: "--font-instrument-sans", +}) + +const jetbrainsMono = JetBrains_Mono({ + subsets: ["latin"], + variable: "--font-jetbrains-mono", +}) + +const geistMono = Geist_Mono({ + subsets: ["latin"], + variable: "--font-geist-mono", +}) + +const notoSerif = Noto_Serif({ + subsets: ["latin"], + variable: "--font-noto-serif", +}) + +const robotoSlab = Roboto_Slab({ + subsets: ["latin"], + variable: "--font-roboto-slab", +}) + +const merriweather = Merriweather({ + subsets: ["latin"], + variable: "--font-merriweather", +}) + +const lora = Lora({ + subsets: ["latin"], + variable: "--font-lora", +}) + +const playfairDisplay = Playfair_Display({ + subsets: ["latin"], + variable: "--font-playfair-display", +}) + +const PREVIEW_FONTS = { + geist: geistSans, + inter, + "noto-sans": notoSans, + "nunito-sans": nunitoSans, + figtree, + roboto, + raleway, + "dm-sans": dmSans, + "public-sans": publicSans, + outfit, + oxanium, + manrope, + "space-grotesk": spaceGrotesk, + montserrat, + "ibm-plex-sans": ibmPlexSans, + "source-sans-3": sourceSans3, + "instrument-sans": instrumentSans, + "jetbrains-mono": jetbrainsMono, + "geist-mono": geistMono, + "noto-serif": notoSerif, + "roboto-slab": robotoSlab, + merriweather, + lora, + "playfair-display": playfairDisplay, +} satisfies Record + +function createFontOption(name: FontName) { + const definition = FONT_DEFINITIONS.find((font) => font.name === name) + + if (!definition) { + throw new Error(`Unknown font definition: ${name}`) + } + + return { + name: definition.title, + value: definition.name, + font: PREVIEW_FONTS[name], + type: definition.type, + } as const +} + +export const FONTS = [ + createFontOption("geist"), + createFontOption("inter"), + createFontOption("noto-sans"), + createFontOption("nunito-sans"), + createFontOption("figtree"), + createFontOption("roboto"), + createFontOption("raleway"), + createFontOption("dm-sans"), + createFontOption("public-sans"), + createFontOption("outfit"), + createFontOption("oxanium"), + createFontOption("manrope"), + createFontOption("space-grotesk"), + createFontOption("montserrat"), + createFontOption("ibm-plex-sans"), + createFontOption("source-sans-3"), + createFontOption("instrument-sans"), + createFontOption("geist-mono"), + createFontOption("jetbrains-mono"), + createFontOption("noto-serif"), + createFontOption("roboto-slab"), + createFontOption("merriweather"), + createFontOption("lora"), + createFontOption("playfair-display"), +] as const + +export type Font = (typeof FONTS)[number] + +export const FONT_HEADING_OPTIONS = [ + { + name: "Inherit", + value: "inherit", + font: null, + type: "default", + }, + ...FONTS, +] as const + +export type FontHeadingOption = (typeof FONT_HEADING_OPTIONS)[number] diff --git a/apps/v4/app/(create)/lib/merge-theme.ts b/apps/v4/app/(app)/create/lib/merge-theme.ts similarity index 100% rename from apps/v4/app/(create)/lib/merge-theme.ts rename to apps/v4/app/(app)/create/lib/merge-theme.ts diff --git a/apps/v4/app/(app)/create/lib/preset-code.ts b/apps/v4/app/(app)/create/lib/preset-code.ts new file mode 100644 index 0000000000..50d1c00727 --- /dev/null +++ b/apps/v4/app/(app)/create/lib/preset-code.ts @@ -0,0 +1,34 @@ +import { encodePreset, type PresetConfig } from "shadcn/preset" + +import { type DesignSystemConfig } from "@/registry/config" + +type PresetCodeConfig = Pick< + DesignSystemConfig, + | "style" + | "baseColor" + | "theme" + | "chartColor" + | "iconLibrary" + | "font" + | "fontHeading" + | "radius" + | "menuAccent" + | "menuColor" +> + +export function getPresetCode(config: PresetCodeConfig) { + const presetConfig: Partial = { + style: config.style as PresetConfig["style"], + baseColor: config.baseColor as PresetConfig["baseColor"], + theme: config.theme as PresetConfig["theme"], + chartColor: config.chartColor as PresetConfig["chartColor"], + iconLibrary: config.iconLibrary as PresetConfig["iconLibrary"], + font: config.font as PresetConfig["font"], + fontHeading: config.fontHeading as PresetConfig["fontHeading"], + radius: config.radius as PresetConfig["radius"], + menuAccent: config.menuAccent as PresetConfig["menuAccent"], + menuColor: config.menuColor as PresetConfig["menuColor"], + } + + return encodePreset(presetConfig) +} diff --git a/apps/v4/app/(app)/create/lib/preset-query.test.ts b/apps/v4/app/(app)/create/lib/preset-query.test.ts new file mode 100644 index 0000000000..93c4ce3b6f --- /dev/null +++ b/apps/v4/app/(app)/create/lib/preset-query.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest" + +import { resolvePresetOverrides } from "./preset-query" + +describe("resolvePresetOverrides", () => { + it("prefers explicit fontHeading and chartColor query params", () => { + const overrides = resolvePresetOverrides( + new URLSearchParams("fontHeading=playfair-display&chartColor=emerald"), + { + theme: "neutral", + chartColor: "blue", + fontHeading: "inherit", + } + ) + + expect(overrides).toEqual({ + fontHeading: "playfair-display", + chartColor: "emerald", + }) + }) + + it("falls back to decoded preset values when no overrides are present", () => { + const overrides = resolvePresetOverrides(new URLSearchParams(), { + theme: "neutral", + chartColor: "blue", + fontHeading: "inherit", + }) + + expect(overrides).toEqual({ + fontHeading: "inherit", + chartColor: "blue", + }) + }) +}) diff --git a/apps/v4/app/(app)/create/lib/preset-query.ts b/apps/v4/app/(app)/create/lib/preset-query.ts new file mode 100644 index 0000000000..f69f1cf9fc --- /dev/null +++ b/apps/v4/app/(app)/create/lib/preset-query.ts @@ -0,0 +1,30 @@ +import { V1_CHART_COLOR_MAP, type PresetConfig } from "shadcn/preset" + +import { type ChartColorName, type FontHeadingValue } from "@/registry/config" + +type SearchParamsLike = Pick + +export function resolvePresetOverrides( + searchParams: SearchParamsLike, + decoded: Pick +) { + const hasFontHeadingOverride = searchParams.has("fontHeading") + const hasChartColorOverride = searchParams.has("chartColor") + + const fontHeading = hasFontHeadingOverride + ? ((searchParams.get("fontHeading") ?? + decoded.fontHeading) as FontHeadingValue) + : decoded.fontHeading + + const chartColor = hasChartColorOverride + ? ((searchParams.get("chartColor") ?? + decoded.chartColor ?? + V1_CHART_COLOR_MAP[decoded.theme] ?? + decoded.theme) as ChartColorName) + : (decoded.chartColor ?? V1_CHART_COLOR_MAP[decoded.theme] ?? decoded.theme) + + return { + fontHeading, + chartColor, + } +} diff --git a/apps/v4/app/(create)/lib/randomize-biases.ts b/apps/v4/app/(app)/create/lib/randomize-biases.ts similarity index 57% rename from apps/v4/app/(create)/lib/randomize-biases.ts rename to apps/v4/app/(app)/create/lib/randomize-biases.ts index de04164bfc..c73965143f 100644 --- a/apps/v4/app/(create)/lib/randomize-biases.ts +++ b/apps/v4/app/(app)/create/lib/randomize-biases.ts @@ -3,6 +3,7 @@ import type { BaseColorName, Radius, StyleName, + Theme, ThemeName, } from "@/registry/config" @@ -12,6 +13,7 @@ export type RandomizeContext = { style?: StyleName baseColor?: BaseColorName theme?: ThemeName + chartColor?: string iconLibrary?: string font?: string menuAccent?: string @@ -26,12 +28,30 @@ export type BiasFilter = ( export type RandomizeBiases = { baseColors?: BiasFilter + chartColors?: BiasFilter fonts?: BiasFilter<(typeof FONTS)[number]> radius?: BiasFilter - // Add more bias filters as needed: - // styles?: BiasFilter" }, + { + "name": "@launchui", + "homepage": "https://www.launchuicomponents.com/", + "url": "https://www.launchuicomponents.com/r/{name}.json", + "description": "Carefully crafted landing page components and templates built with React, Shadcn/ui and Tailwind.", + "logo": "" + }, { "name": "@lens-blocks", "homepage": "https://lensblocks.com", @@ -567,6 +602,20 @@ "description": "A large collection of animated, interactive & fully customizable React components for building memorable websites. From smooth text animations all the way to eye-catching backgrounds, you can find it here.", "logo": "" }, + { + "name": "@react-easy-modals", + "homepage": "https://react-easy-modals-docs.vercel.app", + "url": "https://react-easy-modals-docs.vercel.app/r/styles/{style}/{name}.json", + "description": "Modal component for react-easy-modals. Integrates with shadcn Dialog for a simple, powerful modal system with TypeScript support and promise-based API.", + "logo": "" + }, + { + "name": "@react-slot", + "homepage": "https://react-slot.vercel.app/", + "url": "https://react-slot.vercel.app/r/{name}.json", + "description": "Vue-style slot composition for React - Fine-grained control over component composition", + "logo": "" + }, { "name": "@retroui", "description": "A Neobrutalism styled React + TailwindCSS UI library for building bold, modern web apps. Perfect for any project using Shadcn/ui.", @@ -708,11 +757,18 @@ "logo": " " }, { - "name": "@shadcraft", - "homepage": "https://free.shadcraft.com", - "url": "https://free.shadcraft.com/r/{name}.json", - "description": "A collection of polished shadcn/ui components and marketing blocks built to production standards. Fast to use, easy to extend, and ready for any modern web project.", - "logo": "" + "name": "@shadcncraft", + "homepage": "https://shadcncraft.com", + "url": "https://shadcncraft.com/r/{name}.json", + "description": "A starter collection of polished shadcn/ui components and blocks built to production standards. Part of a larger Figma + React system designed to scale with your product.", + "logo": "" + }, + { + "name": "@shark", + "homepage": "https://shark.vini.one", + "url": "https://shark.vini.one/r/{name}.json", + "description": "shadcn/ui-style components built on Ark UI.", + "logo": "" }, { "name": "@smoothui", @@ -791,6 +847,13 @@ "description": "Fullstack registry items to start those big features. Utilcn has ChatGPT Apps, file uploading (with progress bars) and downloading, and a way to make your env vars typesafe on the backend.", "logo": "" }, + { + "name": "@w3-kit", + "homepage": "https://w3-kit.com", + "url": "https://w3-kit.com/registry/{name}.json", + "description": "Web3 UI components for blockchain dApps. Includes wallet connection, token swaps, NFT cards, staking interfaces, and 20+ more crypto components.", + "logo": "" + }, { "name": "@wandry-ui", "homepage": "http://ui.wandry.com.ua/", @@ -1070,5 +1133,68 @@ "url": "https://ui.flexnative.com/r/{name}.json", "description": "A collection of customizable UI blocks with interactive live previews", "logo": "" + }, + { + "name": "@tailgrids", + "homepage": "https://tailgrids.com", + "url": "https://tailgrids.com/docs/r/{name}.json", + "description": "React UI Components, Powered by Tailwind CSS", + "logo": "" + }, + { + "name": "@nexus-ui", + "homepage": "https://nexus-ui.dev", + "url": "https://nexus-ui.dev/r/{name}.json", + "description": "Open-source component library of composable, copy-paste primitives for building AI interfaces (chat, streaming, multimodal)", + "logo": "" + }, + { + "name": "@sabraman", + "homepage": "https://sabraman.ru/components", + "url": "https://sabraman.ru/r/{name}.json", + "description": "Legacy skeuomorphic UI components and blocks for shadcn.", + "logo": " " + }, + { + "name": "@odysseyui", + "homepage": "https://www.odysseyui.com/docs", + "url": "https://www.odysseyui.com/r/{name}.json", + "description": "A design focused component library for Next.js, built for speed, flexibility and developer experience.", + "logo": " " + }, + { + "name": "@pulkitxm", + "homepage": "https://pulkitxm.com", + "url": "https://pulkitxm.com/components/{name}.json", + "description": "Animated shadcn components powered by GSAP and Framer Motion. Built for expressive UIs.", + "logo": " " + }, + { + "name": "@rescript-shadcn", + "homepage": "https://rescript-shadcn.miriad.studio", + "url": "https://rescript-shadcn.miriad.studio/r/{name}.json", + "description": "Shadcn components rewritten in Rescript, compatible with shadcn CLI.", + "logo": "" + }, + { + "name": "@openpolicy", + "homepage": "https://www.openpolicy.sh", + "url": "https://www.openpolicy.sh/r/{name}.json", + "description": "Open-source components for building terms, privacy policies and cookie banners.", + "logo": "" + }, + { + "name": "@mksingh", + "homepage": "https://mksingh.dev/docs", + "url": "https://mksingh.dev/r/{name}.json", + "description": "A personal registry of production-ready ShadCN components and utilities. Everything is built to drop into your existing ShadCN project with no extra setup.", + "logo": "MKSingh" + }, + { + "name": "@flowkit-ui", + "homepage": "https://flowkit-ui.vzkiss.com", + "url": "https://flowkit-ui.vzkiss.com/r/{name}.json", + "description": "Opinionated, accessible components on Base UI and shadcn-style primitives — starting with a Creatable Combobox.", + "logo": "" } ] diff --git a/apps/v4/registry/fonts.ts b/apps/v4/registry/fonts.ts index 431d2570b8..b2a998c40e 100644 --- a/apps/v4/registry/fonts.ts +++ b/apps/v4/registry/fonts.ts @@ -1,223 +1,34 @@ import { type RegistryItem } from "shadcn/schema" -export const fonts = [ - { - name: "font-geist", - title: "Geist", +import { FONT_DEFINITIONS, type FontDefinition } from "@/lib/font-definitions" + +function createFontItem(definition: FontDefinition, role: "body" | "heading") { + return { + name: + role === "body" + ? `font-${definition.name}` + : `font-heading-${definition.name}`, + title: role === "body" ? definition.title : `${definition.title} (Heading)`, type: "registry:font", font: { - family: "'Geist Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Geist", - dependency: "@fontsource-variable/geist", + family: definition.family, + provider: definition.provider, + variable: + role === "body" ? definition.registryVariable : "--font-heading", + ...(definition.weight ? { weight: [...definition.weight] } : {}), + subsets: [...definition.subsets], + import: definition.import, + dependency: definition.dependency, }, - }, - { - name: "font-inter", - title: "Inter", - type: "registry:font", - font: { - family: "'Inter Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Inter", - dependency: "@fontsource-variable/inter", - }, - }, - { - name: "font-noto-sans", - title: "Noto Sans", - type: "registry:font", - font: { - family: "'Noto Sans Variable', sans-serif", - provider: "google", - variable: "--font-sans", - import: "Noto_Sans", - dependency: "@fontsource-variable/noto-sans", - }, - }, - { - name: "font-nunito-sans", - title: "Nunito Sans", - type: "registry:font", - font: { - family: "'Nunito Sans Variable', sans-serif", - provider: "google", - variable: "--font-sans", - import: "Nunito_Sans", - dependency: "@fontsource-variable/nunito-sans", - }, - }, - { - name: "font-figtree", - title: "Figtree", - type: "registry:font", - font: { - family: "'Figtree Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Figtree", - dependency: "@fontsource-variable/figtree", - }, - }, - { - name: "font-roboto", - title: "Roboto", - type: "registry:font", - font: { - family: "'Roboto Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Roboto", - dependency: "@fontsource-variable/roboto", - }, - }, - { - name: "font-raleway", - title: "Raleway", - type: "registry:font", - font: { - family: "'Raleway Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Raleway", - dependency: "@fontsource-variable/raleway", - }, - }, - { - name: "font-dm-sans", - title: "DM Sans", - type: "registry:font", - font: { - family: "'DM Sans Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "DM_Sans", - dependency: "@fontsource-variable/dm-sans", - }, - }, - { - name: "font-public-sans", - title: "Public Sans", - type: "registry:font", - font: { - family: "'Public Sans Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Public_Sans", - dependency: "@fontsource-variable/public-sans", - }, - }, - { - name: "font-outfit", - title: "Outfit", - type: "registry:font", - font: { - family: "'Outfit Variable', sans-serif", - provider: "google", - variable: "--font-sans", - subsets: ["latin"], - import: "Outfit", - dependency: "@fontsource-variable/outfit", - }, - }, - { - name: "font-jetbrains-mono", - title: "JetBrains Mono", - type: "registry:font", - font: { - family: "'JetBrains Mono Variable', monospace", - provider: "google", - variable: "--font-mono", - subsets: ["latin"], - import: "JetBrains_Mono", - dependency: "@fontsource-variable/jetbrains-mono", - }, - }, - { - name: "font-geist-mono", - title: "Geist Mono", - type: "registry:font", - font: { - family: "'Geist Mono Variable', monospace", - provider: "google", - variable: "--font-mono", - subsets: ["latin"], - import: "Geist_Mono", - dependency: "@fontsource-variable/geist-mono", - }, - }, - { - name: "font-noto-serif", - title: "Noto Serif", - type: "registry:font", - font: { - family: "'Noto Serif Variable', serif", - provider: "google", - variable: "--font-serif", - subsets: ["latin"], - import: "Noto_Serif", - dependency: "@fontsource-variable/noto-serif", - }, - }, - { - name: "font-roboto-slab", - title: "Roboto Slab", - type: "registry:font", - font: { - family: "'Roboto Slab Variable', serif", - provider: "google", - variable: "--font-serif", - subsets: ["latin"], - import: "Roboto_Slab", - dependency: "@fontsource-variable/roboto-slab", - }, - }, - { - name: "font-merriweather", - title: "Merriweather", - type: "registry:font", - font: { - family: "'Merriweather Variable', serif", - provider: "google", - variable: "--font-serif", - subsets: ["latin"], - import: "Merriweather", - dependency: "@fontsource-variable/merriweather", - }, - }, - { - name: "font-lora", - title: "Lora", - type: "registry:font", - font: { - family: "'Lora Variable', serif", - provider: "google", - variable: "--font-serif", - subsets: ["latin"], - import: "Lora", - dependency: "@fontsource-variable/lora", - }, - }, - { - name: "font-playfair-display", - title: "Playfair Display", - type: "registry:font", - font: { - family: "'Playfair Display Variable', serif", - provider: "google", - variable: "--font-serif", - subsets: ["latin"], - import: "Playfair_Display", - dependency: "@fontsource-variable/playfair-display", - }, - }, -] satisfies RegistryItem[] + } satisfies RegistryItem +} + +export const bodyFonts = FONT_DEFINITIONS.map((definition) => + createFontItem(definition, "body") +) satisfies RegistryItem[] + +export const headingFonts = FONT_DEFINITIONS.map((definition) => + createFontItem(definition, "heading") +) satisfies RegistryItem[] + +export const fonts = [...bodyFonts, ...headingFonts] satisfies RegistryItem[] diff --git a/apps/v4/registry/icons/__hugeicons__.ts b/apps/v4/registry/icons/__hugeicons__.ts index cbf263584f..160bc4a131 100644 --- a/apps/v4/registry/icons/__hugeicons__.ts +++ b/apps/v4/registry/icons/__hugeicons__.ts @@ -1,11 +1,14 @@ // Auto-generated by scripts/build-icons.ts export { ActivityIcon } from "@hugeicons/core-free-icons" export { Add01Icon } from "@hugeicons/core-free-icons" +export { AddCircleIcon } from "@hugeicons/core-free-icons" export { Alert02Icon } from "@hugeicons/core-free-icons" export { AlertCircleIcon } from "@hugeicons/core-free-icons" export { Analytics01Icon } from "@hugeicons/core-free-icons" +export { AnalyticsUpIcon } from "@hugeicons/core-free-icons" export { Archive02Icon } from "@hugeicons/core-free-icons" export { ArchiveIcon } from "@hugeicons/core-free-icons" +export { ArrowDataTransferHorizontalIcon } from "@hugeicons/core-free-icons" export { ArrowDown01Icon } from "@hugeicons/core-free-icons" export { ArrowDownIcon } from "@hugeicons/core-free-icons" export { ArrowLeft01Icon } from "@hugeicons/core-free-icons" @@ -26,10 +29,13 @@ export { BluetoothIcon } from "@hugeicons/core-free-icons" export { BookOpen02Icon } from "@hugeicons/core-free-icons" export { BookmarkIcon } from "@hugeicons/core-free-icons" export { CalculatorIcon } from "@hugeicons/core-free-icons" +export { Calendar03Icon } from "@hugeicons/core-free-icons" export { CalendarIcon } from "@hugeicons/core-free-icons" export { Camera01Icon } from "@hugeicons/core-free-icons" export { Cancel01Icon } from "@hugeicons/core-free-icons" +export { Car01Icon } from "@hugeicons/core-free-icons" export { Chart03Icon } from "@hugeicons/core-free-icons" +export { ChartBarLineIcon } from "@hugeicons/core-free-icons" export { ChartDownIcon } from "@hugeicons/core-free-icons" export { ChartHistogramIcon } from "@hugeicons/core-free-icons" export { ChartIcon } from "@hugeicons/core-free-icons" @@ -44,6 +50,7 @@ export { ClipboardIcon } from "@hugeicons/core-free-icons" export { Clock03Icon } from "@hugeicons/core-free-icons" export { CloudUploadIcon } from "@hugeicons/core-free-icons" export { CodeIcon } from "@hugeicons/core-free-icons" +export { CoffeeIcon } from "@hugeicons/core-free-icons" export { CommandIcon } from "@hugeicons/core-free-icons" export { ComputerIcon } from "@hugeicons/core-free-icons" export { ComputerTerminal01Icon } from "@hugeicons/core-free-icons" @@ -79,6 +86,7 @@ export { GridIcon } from "@hugeicons/core-free-icons" export { HelpCircleIcon } from "@hugeicons/core-free-icons" export { Home01Icon } from "@hugeicons/core-free-icons" export { HomeIcon } from "@hugeicons/core-free-icons" +export { Image01Icon } from "@hugeicons/core-free-icons" export { ImageIcon } from "@hugeicons/core-free-icons" export { InboxIcon } from "@hugeicons/core-free-icons" export { InformationCircleIcon } from "@hugeicons/core-free-icons" @@ -99,6 +107,7 @@ export { MapsIcon } from "@hugeicons/core-free-icons" export { Menu01Icon } from "@hugeicons/core-free-icons" export { Menu05Icon } from "@hugeicons/core-free-icons" export { Menu09Icon } from "@hugeicons/core-free-icons" +export { Message01Icon } from "@hugeicons/core-free-icons" export { MessageIcon } from "@hugeicons/core-free-icons" export { MessageQuestionIcon } from "@hugeicons/core-free-icons" export { MinusSignIcon } from "@hugeicons/core-free-icons" @@ -116,6 +125,7 @@ export { PlusSignIcon } from "@hugeicons/core-free-icons" export { RecordIcon } from "@hugeicons/core-free-icons" export { RedoIcon } from "@hugeicons/core-free-icons" export { RefreshIcon } from "@hugeicons/core-free-icons" +export { RepeatIcon } from "@hugeicons/core-free-icons" export { RoboticIcon } from "@hugeicons/core-free-icons" export { Rotate01Icon } from "@hugeicons/core-free-icons" export { ScissorIcon } from "@hugeicons/core-free-icons" @@ -135,16 +145,21 @@ export { ShoppingCart01Icon } from "@hugeicons/core-free-icons" export { SidebarLeftIcon } from "@hugeicons/core-free-icons" export { SmileIcon } from "@hugeicons/core-free-icons" export { SparklesIcon } from "@hugeicons/core-free-icons" +export { SquareLock02Icon } from "@hugeicons/core-free-icons" export { StarIcon } from "@hugeicons/core-free-icons" export { StarOffIcon } from "@hugeicons/core-free-icons" +export { Sun03Icon } from "@hugeicons/core-free-icons" export { SunIcon } from "@hugeicons/core-free-icons" export { TableIcon } from "@hugeicons/core-free-icons" +export { Target02Icon } from "@hugeicons/core-free-icons" export { TextBoldIcon } from "@hugeicons/core-free-icons" export { TextCheckIcon } from "@hugeicons/core-free-icons" export { TextItalicIcon } from "@hugeicons/core-free-icons" export { TextUnderlineIcon } from "@hugeicons/core-free-icons" +export { ThermometerWarmIcon } from "@hugeicons/core-free-icons" export { Tick02Icon } from "@hugeicons/core-free-icons" export { TradeUpIcon } from "@hugeicons/core-free-icons" +export { Tv01Icon } from "@hugeicons/core-free-icons" export { UndoIcon } from "@hugeicons/core-free-icons" export { UnfoldMoreIcon } from "@hugeicons/core-free-icons" export { UserCircle02Icon } from "@hugeicons/core-free-icons" @@ -153,7 +168,9 @@ export { UserIcon } from "@hugeicons/core-free-icons" export { UserRemove01Icon } from "@hugeicons/core-free-icons" export { ViewOffIcon } from "@hugeicons/core-free-icons" export { VoiceIcon } from "@hugeicons/core-free-icons" +export { VolumeHighIcon } from "@hugeicons/core-free-icons" export { VolumeOffIcon } from "@hugeicons/core-free-icons" +export { Wallet01Icon } from "@hugeicons/core-free-icons" export { WalletIcon } from "@hugeicons/core-free-icons" export { ZapIcon } from "@hugeicons/core-free-icons" export { ZoomInAreaIcon } from "@hugeicons/core-free-icons" diff --git a/apps/v4/registry/icons/__lucide__.ts b/apps/v4/registry/icons/__lucide__.ts index 9b79975ba0..198d308a29 100644 --- a/apps/v4/registry/icons/__lucide__.ts +++ b/apps/v4/registry/icons/__lucide__.ts @@ -1,5 +1,6 @@ // Auto-generated by scripts/build-icons.ts export { ActivityIcon } from "lucide-react" +export { AlertCircleIcon } from "lucide-react" export { AlertTriangleIcon } from "lucide-react" export { AppWindowIcon } from "lucide-react" export { ArchiveIcon } from "lucide-react" @@ -7,6 +8,7 @@ export { ArchiveXIcon } from "lucide-react" export { ArrowDownIcon } from "lucide-react" export { ArrowLeftCircleIcon } from "lucide-react" export { ArrowLeftIcon } from "lucide-react" +export { ArrowLeftRightIcon } from "lucide-react" export { ArrowRightIcon } from "lucide-react" export { ArrowUpIcon } from "lucide-react" export { ArrowUpRightIcon } from "lucide-react" @@ -26,6 +28,7 @@ export { CalculatorIcon } from "lucide-react" export { CalendarIcon } from "lucide-react" export { CameraIcon } from "lucide-react" export { CaptionsIcon } from "lucide-react" +export { CarIcon } from "lucide-react" export { ChartBarIcon } from "lucide-react" export { ChartLineIcon } from "lucide-react" export { ChartPieIcon } from "lucide-react" @@ -46,7 +49,9 @@ export { CirclePlusIcon } from "lucide-react" export { CircleUserRoundIcon } from "lucide-react" export { ClipboardPasteIcon } from "lucide-react" export { Clock2Icon } from "lucide-react" +export { CloudIcon } from "lucide-react" export { CodeIcon } from "lucide-react" +export { CoffeeIcon } from "lucide-react" export { Columns3Icon } from "lucide-react" export { CommandIcon } from "lucide-react" export { ContainerIcon } from "lucide-react" @@ -60,6 +65,7 @@ export { EllipsisVerticalIcon } from "lucide-react" export { ExternalLinkIcon } from "lucide-react" export { EyeIcon } from "lucide-react" export { EyeOffIcon } from "lucide-react" +export { FileBarChartIcon } from "lucide-react" export { FileChartColumnIcon } from "lucide-react" export { FileCodeIcon } from "lucide-react" export { FileIcon } from "lucide-react" @@ -72,6 +78,7 @@ export { FolderPlusIcon } from "lucide-react" export { FolderSearchIcon } from "lucide-react" export { FrameIcon } from "lucide-react" export { GalleryVerticalEndIcon } from "lucide-react" +export { GaugeIcon } from "lucide-react" export { GlobeIcon } from "lucide-react" export { GripVerticalIcon } from "lucide-react" export { HeartIcon } from "lucide-react" @@ -93,6 +100,7 @@ export { ListIcon } from "lucide-react" export { Loader2Icon } from "lucide-react" export { LoaderIcon } from "lucide-react" export { LockIcon } from "lucide-react" +export { LockKeyholeIcon } from "lucide-react" export { LogOutIcon } from "lucide-react" export { MailIcon } from "lucide-react" export { MapIcon } from "lucide-react" @@ -114,9 +122,11 @@ export { PaletteIcon } from "lucide-react" export { PanelLeftIcon } from "lucide-react" export { PencilIcon } from "lucide-react" export { PieChartIcon } from "lucide-react" +export { PlusCircleIcon } from "lucide-react" export { PlusIcon } from "lucide-react" export { RadioIcon } from "lucide-react" export { RefreshCwIcon } from "lucide-react" +export { RepeatIcon } from "lucide-react" export { RotateCwIcon } from "lucide-react" export { SaveIcon } from "lucide-react" export { ScissorsIcon } from "lucide-react" @@ -136,19 +146,24 @@ export { StarIcon } from "lucide-react" export { StarOffIcon } from "lucide-react" export { SunIcon } from "lucide-react" export { TableIcon } from "lucide-react" +export { TargetIcon } from "lucide-react" export { TerminalIcon } from "lucide-react" export { TerminalSquareIcon } from "lucide-react" +export { ThermometerIcon } from "lucide-react" +export { TimerIcon } from "lucide-react" export { Trash2Icon } from "lucide-react" export { TrashIcon } from "lucide-react" export { TrendingDownIcon } from "lucide-react" export { TrendingUpIcon } from "lucide-react" export { TriangleAlertIcon } from "lucide-react" +export { TvIcon } from "lucide-react" export { UnderlineIcon } from "lucide-react" export { UploadCloudIcon } from "lucide-react" export { UserIcon } from "lucide-react" export { UserRoundXIcon } from "lucide-react" export { UsersIcon } from "lucide-react" export { VideoIcon } from "lucide-react" +export { Volume2Icon } from "lucide-react" export { VolumeOffIcon } from "lucide-react" export { VolumeX } from "lucide-react" export { WalletIcon } from "lucide-react" diff --git a/apps/v4/registry/icons/__phosphor__.ts b/apps/v4/registry/icons/__phosphor__.ts index d787e39f67..e92f0eba26 100644 --- a/apps/v4/registry/icons/__phosphor__.ts +++ b/apps/v4/registry/icons/__phosphor__.ts @@ -12,7 +12,9 @@ export { ArrowRightIcon } from "@phosphor-icons/react" export { ArrowSquareOutIcon } from "@phosphor-icons/react" export { ArrowUpIcon } from "@phosphor-icons/react" export { ArrowUpRightIcon } from "@phosphor-icons/react" +export { ArrowsClockwiseIcon } from "@phosphor-icons/react" export { ArrowsHorizontalIcon } from "@phosphor-icons/react" +export { ArrowsLeftRightIcon } from "@phosphor-icons/react" export { ArrowsVerticalIcon } from "@phosphor-icons/react" export { BagIcon } from "@phosphor-icons/react" export { BankIcon } from "@phosphor-icons/react" @@ -24,6 +26,7 @@ export { CalculatorIcon } from "@phosphor-icons/react" export { CalendarBlankIcon } from "@phosphor-icons/react" export { CalendarIcon } from "@phosphor-icons/react" export { CameraIcon } from "@phosphor-icons/react" +export { CarIcon } from "@phosphor-icons/react" export { CaretDoubleLeftIcon } from "@phosphor-icons/react" export { CaretDoubleRightIcon } from "@phosphor-icons/react" export { CaretDownIcon } from "@phosphor-icons/react" @@ -35,13 +38,16 @@ export { ChartBarIcon } from "@phosphor-icons/react" export { ChartLineIcon } from "@phosphor-icons/react" export { ChartPieIcon } from "@phosphor-icons/react" export { ChatCircleIcon } from "@phosphor-icons/react" +export { ChatIcon } from "@phosphor-icons/react" export { CheckCircleIcon } from "@phosphor-icons/react" export { CheckIcon } from "@phosphor-icons/react" export { CircleDashedIcon } from "@phosphor-icons/react" export { ClipboardIcon } from "@phosphor-icons/react" export { ClockIcon } from "@phosphor-icons/react" export { CloudArrowUpIcon } from "@phosphor-icons/react" +export { CloudIcon } from "@phosphor-icons/react" export { CodeIcon } from "@phosphor-icons/react" +export { CoffeeIcon } from "@phosphor-icons/react" export { ColumnsIcon } from "@phosphor-icons/react" export { CommandIcon } from "@phosphor-icons/react" export { CopyIcon } from "@phosphor-icons/react" @@ -57,13 +63,13 @@ export { DownloadIcon } from "@phosphor-icons/react" export { EnvelopeIcon } from "@phosphor-icons/react" export { EyeIcon } from "@phosphor-icons/react" export { EyeSlashIcon } from "@phosphor-icons/react" -export { FileCodeIcon } from "@phosphor-icons/react" export { FileIcon } from "@phosphor-icons/react" export { FileTextIcon } from "@phosphor-icons/react" export { FloppyDiskIcon } from "@phosphor-icons/react" export { FolderIcon } from "@phosphor-icons/react" export { FolderOpenIcon } from "@phosphor-icons/react" export { FolderPlusIcon } from "@phosphor-icons/react" +export { GaugeIcon } from "@phosphor-icons/react" export { GearIcon } from "@phosphor-icons/react" export { GlobeIcon } from "@phosphor-icons/react" export { GridFourIcon } from "@phosphor-icons/react" @@ -79,6 +85,7 @@ export { LightningIcon } from "@phosphor-icons/react" export { LinkIcon } from "@phosphor-icons/react" export { ListIcon } from "@phosphor-icons/react" export { LockIcon } from "@phosphor-icons/react" +export { LockKeyIcon } from "@phosphor-icons/react" export { MagnifyingGlassIcon } from "@phosphor-icons/react" export { MagnifyingGlassMinusIcon } from "@phosphor-icons/react" export { MagnifyingGlassPlusIcon } from "@phosphor-icons/react" @@ -94,6 +101,7 @@ export { PlusCircleIcon } from "@phosphor-icons/react" export { PlusIcon } from "@phosphor-icons/react" export { QuestionIcon } from "@phosphor-icons/react" export { RecordIcon } from "@phosphor-icons/react" +export { RepeatIcon } from "@phosphor-icons/react" export { RobotIcon } from "@phosphor-icons/react" export { RowsIcon } from "@phosphor-icons/react" export { ScissorsIcon } from "@phosphor-icons/react" @@ -105,17 +113,22 @@ export { SidebarIcon } from "@phosphor-icons/react" export { SignOutIcon } from "@phosphor-icons/react" export { SmileyIcon } from "@phosphor-icons/react" export { SparkleIcon } from "@phosphor-icons/react" +export { SpeakerHighIcon } from "@phosphor-icons/react" export { SpeakerSlashIcon } from "@phosphor-icons/react" export { SpinnerIcon } from "@phosphor-icons/react" export { SquaresFourIcon } from "@phosphor-icons/react" export { StarIcon } from "@phosphor-icons/react" export { SunIcon } from "@phosphor-icons/react" export { TableIcon } from "@phosphor-icons/react" +export { TargetIcon } from "@phosphor-icons/react" +export { TelevisionIcon } from "@phosphor-icons/react" export { TerminalIcon } from "@phosphor-icons/react" export { TextBIcon } from "@phosphor-icons/react" export { TextItalicIcon } from "@phosphor-icons/react" export { TextTIcon } from "@phosphor-icons/react" export { TextUnderlineIcon } from "@phosphor-icons/react" +export { ThermometerIcon } from "@phosphor-icons/react" +export { TimerIcon } from "@phosphor-icons/react" export { TranslateIcon } from "@phosphor-icons/react" export { TrashIcon } from "@phosphor-icons/react" export { TrayIcon } from "@phosphor-icons/react" diff --git a/apps/v4/registry/icons/__remixicon__.ts b/apps/v4/registry/icons/__remixicon__.ts index aecd7b253b..dedb1eabf0 100644 --- a/apps/v4/registry/icons/__remixicon__.ts +++ b/apps/v4/registry/icons/__remixicon__.ts @@ -1,5 +1,6 @@ // Auto-generated by scripts/build-icons.ts export { RiAddCircleFill } from "@remixicon/react" +export { RiAddCircleLine } from "@remixicon/react" export { RiAddLine } from "@remixicon/react" export { RiArchiveLine } from "@remixicon/react" export { RiArrowDownLine } from "@remixicon/react" @@ -17,6 +18,7 @@ export { RiArrowUpSLine } from "@remixicon/react" export { RiBankCardLine } from "@remixicon/react" export { RiBankLine } from "@remixicon/react" export { RiBarChartLine } from "@remixicon/react" +export { RiBellLine } from "@remixicon/react" export { RiBluetoothLine } from "@remixicon/react" export { RiBold } from "@remixicon/react" export { RiBookOpenLine } from "@remixicon/react" @@ -26,6 +28,7 @@ export { RiBox3Line } from "@remixicon/react" export { RiCalculatorLine } from "@remixicon/react" export { RiCalendarLine } from "@remixicon/react" export { RiCameraLine } from "@remixicon/react" +export { RiCarLine } from "@remixicon/react" export { RiChat1Line } from "@remixicon/react" export { RiCheckLine } from "@remixicon/react" export { RiCheckboxCircleFill } from "@remixicon/react" @@ -33,12 +36,14 @@ export { RiCheckboxCircleLine } from "@remixicon/react" export { RiClipboardLine } from "@remixicon/react" export { RiCloseCircleLine } from "@remixicon/react" export { RiCloseLine } from "@remixicon/react" +export { RiCloudLine } from "@remixicon/react" export { RiCodeLine } from "@remixicon/react" export { RiCommandLine } from "@remixicon/react" export { RiComputerLine } from "@remixicon/react" export { RiCornerUpLeftLine } from "@remixicon/react" export { RiCornerUpRightLine } from "@remixicon/react" export { RiCropLine } from "@remixicon/react" +export { RiCupLine } from "@remixicon/react" export { RiDashboardLine } from "@remixicon/react" export { RiDatabase2Line } from "@remixicon/react" export { RiDeleteBinLine } from "@remixicon/react" @@ -50,11 +55,11 @@ export { RiExternalLinkLine } from "@remixicon/react" export { RiEyeLine } from "@remixicon/react" export { RiEyeOffLine } from "@remixicon/react" export { RiFileChartLine } from "@remixicon/react" -export { RiFileCodeLine } from "@remixicon/react" export { RiFileCopyLine } from "@remixicon/react" export { RiFileLine } from "@remixicon/react" export { RiFileTextLine } from "@remixicon/react" export { RiFlashlightLine } from "@remixicon/react" +export { RiFocus3Line } from "@remixicon/react" export { RiFolderAddLine } from "@remixicon/react" export { RiFolderLine } from "@remixicon/react" export { RiFolderOpenLine } from "@remixicon/react" @@ -98,6 +103,7 @@ export { RiPulseLine } from "@remixicon/react" export { RiQuestionLine } from "@remixicon/react" export { RiRecordCircleLine } from "@remixicon/react" export { RiRefreshLine } from "@remixicon/react" +export { RiRepeatLine } from "@remixicon/react" export { RiRobotLine } from "@remixicon/react" export { RiSaveLine } from "@remixicon/react" export { RiScissorsLine } from "@remixicon/react" @@ -119,16 +125,21 @@ export { RiStarOffLine } from "@remixicon/react" export { RiSubtractLine } from "@remixicon/react" export { RiSunLine } from "@remixicon/react" export { RiTableLine } from "@remixicon/react" +export { RiTeamLine } from "@remixicon/react" export { RiTerminalBoxLine } from "@remixicon/react" export { RiTextWrap } from "@remixicon/react" +export { RiThermometerLine } from "@remixicon/react" export { RiTimeLine } from "@remixicon/react" +export { RiTimerLine } from "@remixicon/react" export { RiTranslate } from "@remixicon/react" +export { RiTvLine } from "@remixicon/react" export { RiUnderline } from "@remixicon/react" export { RiUploadCloudLine } from "@remixicon/react" export { RiUserLine } from "@remixicon/react" export { RiUserUnfollowLine } from "@remixicon/react" export { RiVideoLine } from "@remixicon/react" export { RiVolumeMuteLine } from "@remixicon/react" +export { RiVolumeUpLine } from "@remixicon/react" export { RiWalletLine } from "@remixicon/react" export { RiWindowLine } from "@remixicon/react" export { RiZoomInLine } from "@remixicon/react" diff --git a/apps/v4/registry/icons/__tabler__.ts b/apps/v4/registry/icons/__tabler__.ts index 13e5454e9b..b81a761adb 100644 --- a/apps/v4/registry/icons/__tabler__.ts +++ b/apps/v4/registry/icons/__tabler__.ts @@ -1,5 +1,6 @@ // Auto-generated by scripts/build-icons.ts export { IconActivity } from "@tabler/icons-react" +export { IconAlertCircle } from "@tabler/icons-react" export { IconAlertOctagon } from "@tabler/icons-react" export { IconAlertTriangle } from "@tabler/icons-react" export { IconAppWindow } from "@tabler/icons-react" @@ -11,6 +12,7 @@ export { IconArrowLeft } from "@tabler/icons-react" export { IconArrowRight } from "@tabler/icons-react" export { IconArrowUp } from "@tabler/icons-react" export { IconArrowUpRight } from "@tabler/icons-react" +export { IconArrowsLeftRight } from "@tabler/icons-react" export { IconBell } from "@tabler/icons-react" export { IconBluetooth } from "@tabler/icons-react" export { IconBold } from "@tabler/icons-react" @@ -23,6 +25,7 @@ export { IconBuildingBank } from "@tabler/icons-react" export { IconCalculator } from "@tabler/icons-react" export { IconCalendar } from "@tabler/icons-react" export { IconCamera } from "@tabler/icons-react" +export { IconCar } from "@tabler/icons-react" export { IconChartBar } from "@tabler/icons-react" export { IconChartLine } from "@tabler/icons-react" export { IconChartPie } from "@tabler/icons-react" @@ -37,11 +40,15 @@ export { IconCircleArrowLeft } from "@tabler/icons-react" export { IconCircleCheck } from "@tabler/icons-react" export { IconCircleCheckFilled } from "@tabler/icons-react" export { IconCircleDashed } from "@tabler/icons-react" +export { IconCirclePlus } from "@tabler/icons-react" export { IconCirclePlusFilled } from "@tabler/icons-react" export { IconClipboard } from "@tabler/icons-react" +export { IconClock } from "@tabler/icons-react" export { IconClockHour2 } from "@tabler/icons-react" +export { IconCloud } from "@tabler/icons-react" export { IconCloudUpload } from "@tabler/icons-react" export { IconCode } from "@tabler/icons-react" +export { IconCoffee } from "@tabler/icons-react" export { IconCommand } from "@tabler/icons-react" export { IconCopy } from "@tabler/icons-react" export { IconCornerUpLeft } from "@tabler/icons-react" @@ -53,6 +60,7 @@ export { IconDashboard } from "@tabler/icons-react" export { IconDatabase } from "@tabler/icons-react" export { IconDeviceDesktop } from "@tabler/icons-react" export { IconDeviceFloppy } from "@tabler/icons-react" +export { IconDeviceTv } from "@tabler/icons-react" export { IconDots } from "@tabler/icons-react" export { IconDotsVertical } from "@tabler/icons-react" export { IconDownload } from "@tabler/icons-react" @@ -73,6 +81,7 @@ export { IconFolderOpen } from "@tabler/icons-react" export { IconFolderPlus } from "@tabler/icons-react" export { IconFolderSearch } from "@tabler/icons-react" export { IconFrame } from "@tabler/icons-react" +export { IconGauge } from "@tabler/icons-react" export { IconGlobe } from "@tabler/icons-react" export { IconGripVertical } from "@tabler/icons-react" export { IconHeadphones } from "@tabler/icons-react" @@ -88,6 +97,7 @@ export { IconKeyboard } from "@tabler/icons-react" export { IconLanguage } from "@tabler/icons-react" export { IconLayout } from "@tabler/icons-react" export { IconLayoutColumns } from "@tabler/icons-react" +export { IconLayoutDashboard } from "@tabler/icons-react" export { IconLayoutGrid } from "@tabler/icons-react" export { IconLayoutRows } from "@tabler/icons-react" export { IconLayoutSidebar } from "@tabler/icons-react" @@ -116,7 +126,9 @@ export { IconPhoto } from "@tabler/icons-react" export { IconPlayerRecordFilled } from "@tabler/icons-react" export { IconPlus } from "@tabler/icons-react" export { IconRefresh } from "@tabler/icons-react" +export { IconRepeat } from "@tabler/icons-react" export { IconReport } from "@tabler/icons-react" +export { IconReportAnalytics } from "@tabler/icons-react" export { IconRobot } from "@tabler/icons-react" export { IconRosetteDiscountCheck } from "@tabler/icons-react" export { IconRotateClockwise2 } from "@tabler/icons-react" @@ -137,9 +149,11 @@ export { IconStar } from "@tabler/icons-react" export { IconStarOff } from "@tabler/icons-react" export { IconSun } from "@tabler/icons-react" export { IconTable } from "@tabler/icons-react" +export { IconTarget } from "@tabler/icons-react" export { IconTerminal } from "@tabler/icons-react" export { IconTerminal2 } from "@tabler/icons-react" export { IconTextCaption } from "@tabler/icons-react" +export { IconThermometer } from "@tabler/icons-react" export { IconTrash } from "@tabler/icons-react" export { IconTrendingDown } from "@tabler/icons-react" export { IconTrendingUp } from "@tabler/icons-react" diff --git a/apps/v4/registry/new-york-v4/charts/chart-bar-active.tsx b/apps/v4/registry/new-york-v4/charts/chart-bar-active.tsx index a2762ef314..f76c5812e5 100644 --- a/apps/v4/registry/new-york-v4/charts/chart-bar-active.tsx +++ b/apps/v4/registry/new-york-v4/charts/chart-bar-active.tsx @@ -2,6 +2,7 @@ import { TrendingUp } from "lucide-react" import { Bar, BarChart, CartesianGrid, Rectangle, XAxis } from "recharts" +import type { BarShapeProps } from "recharts/types/cartesian/Bar" import { Card, @@ -54,6 +55,8 @@ const chartConfig = { }, } satisfies ChartConfig +const ACTIVE_INDEX = 2 + export function ChartBarActive() { return ( @@ -82,9 +85,8 @@ export function ChartBarActive() { dataKey="visitors" strokeWidth={2} radius={8} - activeIndex={2} - activeBar={({ ...props }) => { - return ( + shape={({ index, ...props }: BarShapeProps) => + index === ACTIVE_INDEX ? ( + ) : ( + ) - }} + } /> diff --git a/apps/v4/registry/new-york-v4/charts/chart-bar-label-custom.tsx b/apps/v4/registry/new-york-v4/charts/chart-bar-label-custom.tsx index 94fa725ef7..bde2b2bfd8 100644 --- a/apps/v4/registry/new-york-v4/charts/chart-bar-label-custom.tsx +++ b/apps/v4/registry/new-york-v4/charts/chart-bar-label-custom.tsx @@ -75,12 +75,7 @@ export function ChartBarLabelCustom() { cursor={false} content={} /> - + } /> - + diff --git a/apps/v4/registry/new-york-v4/charts/chart-line-dots-custom.tsx b/apps/v4/registry/new-york-v4/charts/chart-line-dots-custom.tsx index 07292f0d04..355f23a107 100644 --- a/apps/v4/registry/new-york-v4/charts/chart-line-dots-custom.tsx +++ b/apps/v4/registry/new-york-v4/charts/chart-line-dots-custom.tsx @@ -75,7 +75,12 @@ export function ChartLineDotsCustom() { stroke="var(--color-desktop)" strokeWidth={2} dot={({ cx, cy, payload }) => { + if (cx == null || cy == null) { + return null + } + const r = 24 + return ( - chartConfig[value]?.label + formatter={(value) => + chartConfig[value as keyof typeof chartConfig]?.label } /> diff --git a/apps/v4/registry/new-york-v4/charts/chart-pie-donut-active.tsx b/apps/v4/registry/new-york-v4/charts/chart-pie-donut-active.tsx index 48c13e6050..b2e41b5ebb 100644 --- a/apps/v4/registry/new-york-v4/charts/chart-pie-donut-active.tsx +++ b/apps/v4/registry/new-york-v4/charts/chart-pie-donut-active.tsx @@ -2,7 +2,7 @@ import { TrendingUp } from "lucide-react" import { Label, Pie, PieChart, Sector } from "recharts" -import { type PieSectorDataItem } from "recharts/types/polar/Pie" +import type { PieSectorShapeProps } from "recharts/types/polar/Pie" import { Card, @@ -55,6 +55,8 @@ const chartConfig = { }, } satisfies ChartConfig +const ACTIVE_INDEX = 0 + export function ChartPieDonutActive() { return ( @@ -78,13 +80,17 @@ export function ChartPieDonutActive() { nameKey="browser" innerRadius={60} strokeWidth={5} - activeIndex={0} - activeShape={({ + shape={({ + index, outerRadius = 0, ...props - }: PieSectorDataItem) => ( - - )} + }: PieSectorShapeProps) => + index === ACTIVE_INDEX ? ( + + ) : ( + + ) + } /> diff --git a/apps/v4/registry/new-york-v4/charts/chart-pie-interactive.tsx b/apps/v4/registry/new-york-v4/charts/chart-pie-interactive.tsx index f0db369e96..dfe17b688b 100644 --- a/apps/v4/registry/new-york-v4/charts/chart-pie-interactive.tsx +++ b/apps/v4/registry/new-york-v4/charts/chart-pie-interactive.tsx @@ -2,7 +2,10 @@ import * as React from "react" import { Label, Pie, PieChart, Sector } from "recharts" -import { type PieSectorDataItem } from "recharts/types/polar/Pie" +import type { + PieSectorDataItem, + PieSectorShapeProps, +} from "recharts/types/polar/Pie" import { Card, @@ -78,6 +81,26 @@ export function ChartPieInteractive() { ) const months = React.useMemo(() => desktopData.map((item) => item.month), []) + const renderPieShape = React.useCallback( + ({ index, outerRadius = 0, ...props }: PieSectorShapeProps) => { + if (index === activeIndex) { + return ( + + + + + ) + } + + return + }, + [activeIndex] + ) + return ( @@ -139,20 +162,7 @@ export function ChartPieInteractive() { nameKey="month" innerRadius={60} strokeWidth={5} - activeIndex={activeIndex} - activeShape={({ - outerRadius = 0, - ...props - }: PieSectorDataItem) => ( - - - - - )} + shape={renderPieShape} >