Compare commits

..

2 Commits

Author SHA1 Message Date
shadcn
cbf7b3708a chore: minor refactor and error message 2026-01-06 13:23:13 +04:00
yeasin2002
9da12c13f9 fix(cli): #9160 updated CLI name validation 2025-12-20 17:36:15 +06:00
3630 changed files with 23292 additions and 174129 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
validate app name on create

View File

@@ -0,0 +1,63 @@
name: Add registry to directory
description: Add your registry to the directory
title: "[Registry Directory]: "
labels: ["registry", "directory"]
assignees: []
body:
- type: input
id: name
attributes:
label: Name
description: The name of your registry. This is also the namespace.
placeholder: e.g., "@acme"
validations:
required: true
- type: input
id: url
attributes:
label: URL
description: The URL to your registry index. Use {name} placeholder.
placeholder: https://ui.acme.com/r/{name}.json
validations:
required: true
- type: input
id: homepage
attributes:
label: Homepage
description: The URL to your registry homepage. This is where users can browse your registry.
placeholder: https://ui.acme.com
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Briefly describe what is your registry and what type of components or code it distributes.
placeholder:
validations:
required: true
- type: textarea
id: logo
attributes:
label: Logo
description: Add your SVG logo here.
placeholder:
validations:
required: true
- type: checkboxes
id: requirements
attributes:
label: Checklist
description: Verify that your registry meets the following requirements.
options:
- label: The registry must be open source and publicly accessible.
- label: The registry must be a valid JSON file that conforms to the [registry schema](https://ui.shadcn.com/docs/registry/registry-json) specification.
- label: The `files` array, if present on your registry items, must NOT include a `content` property.
- label: I've attached a square SVG logo to this issue
validations:
required: true

View File

@@ -1,12 +1,12 @@
// ORIGINALLY FROM CLOUDFLARE WRANGLER: // ORIGINALLY FROM CLOUDFLARE WRANGLER:
// https://github.com/cloudflare/wrangler2/blob/main/.github/changeset-version.js // https://github.com/cloudflare/wrangler2/blob/main/.github/changeset-version.js
import { execSync } from "child_process" import { exec } from "child_process"
// This script is used by the `release.yml` workflow to update the version of the packages being released. // This script is used by the `release.yml` workflow to update the version of the packages being released.
// The standard step is only to run `changeset version` but this does not update the pnpm-lock.yaml file. // The standard step is only to run `changeset version` but this does not update the package-lock.json file.
// So we also run `pnpm install`, which does this update. // So we also run `npm install`, which does this update.
// This is a workaround until this is handled automatically by `changeset version`. // This is a workaround until this is handled automatically by `changeset version`.
// See https://github.com/changesets/changesets/issues/421. // See https://github.com/changesets/changesets/issues/421.
execSync("npx changeset version", { stdio: "inherit" }) exec("npx changeset version")
execSync("pnpm install --lockfile-only", { stdio: "inherit" }) exec("npm install")

View File

@@ -4,13 +4,11 @@ on:
pull_request: pull_request:
paths: paths:
- "apps/v4/public/r/registries.json" - "apps/v4/public/r/registries.json"
- "apps/v4/registry/directory.json"
push: push:
branches: branches:
- main - main
paths: paths:
- "apps/v4/public/r/registries.json" - "apps/v4/public/r/registries.json"
- "apps/v4/registry/directory.json"
jobs: jobs:
validate: validate:
@@ -49,5 +47,8 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install
- name: Build packages
run: pnpm build --filter=shadcn
- name: Validate registries - name: Validate registries
run: pnpm --filter=v4 validate:registries run: pnpm --filter=v4 validate:registries

1
.gitignore vendored
View File

@@ -41,4 +41,3 @@ tsconfig.tsbuildinfo
.vscode .vscode
.notes .notes
.playwright-mcp

View File

@@ -1,8 +1,10 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { Button } from "@/examples/radix/ui/button" import { IconMinus, IconPlus } from "@tabler/icons-react"
import { ButtonGroup } from "@/examples/radix/ui/button-group"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import { import {
Field, Field,
FieldContent, FieldContent,
@@ -13,11 +15,13 @@ import {
FieldSeparator, FieldSeparator,
FieldSet, FieldSet,
FieldTitle, FieldTitle,
} from "@/examples/radix/ui/field" } from "@/registry/new-york-v4/ui/field"
import { Input } from "@/examples/radix/ui/input" import { Input } from "@/registry/new-york-v4/ui/input"
import { RadioGroup, RadioGroupItem } from "@/examples/radix/ui/radio-group" import {
import { Switch } from "@/examples/radix/ui/switch" RadioGroup,
import { IconMinus, IconPlus } from "@tabler/icons-react" RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group"
import { Switch } from "@/registry/new-york-v4/ui/switch"
export function AppearanceSettings() { export function AppearanceSettings() {
const [gpuCount, setGpuCount] = React.useState(8) const [gpuCount, setGpuCount] = React.useState(8)
@@ -93,7 +97,7 @@ export function AppearanceSettings() {
value={gpuCount} value={gpuCount}
onChange={handleGpuInputChange} onChange={handleGpuInputChange}
size={3} size={3}
className="h-7 !w-14 font-mono" className="h-8 !w-14 font-mono"
maxLength={3} maxLength={3}
/> />
<Button <Button

View File

@@ -1,8 +1,20 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { Button } from "@/examples/radix/ui/button" import {
import { ButtonGroup } from "@/examples/radix/ui/button-group" ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -15,18 +27,7 @@ import {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/examples/radix/ui/dropdown-menu" } from "@/registry/new-york-v4/ui/dropdown-menu"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
export function ButtonGroupDemo() { export function ButtonGroupDemo() {
const [label, setLabel] = React.useState("personal") const [label, setLabel] = React.useState("personal")
@@ -56,7 +57,7 @@ export function ButtonGroupDemo() {
<MoreHorizontalIcon /> <MoreHorizontalIcon />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48"> <DropdownMenuContent align="end" className="w-48 [--radius:1rem]">
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem> <DropdownMenuItem>
<MailCheckIcon /> <MailCheckIcon />

View File

@@ -1,20 +1,21 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { Button } from "@/examples/radix/ui/button" import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { ButtonGroup } from "@/examples/radix/ui/button-group"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import { import {
InputGroup, InputGroup,
InputGroupAddon, InputGroupAddon,
InputGroupButton, InputGroupButton,
InputGroupInput, InputGroupInput,
} from "@/examples/radix/ui/input-group" } from "@/registry/new-york-v4/ui/input-group"
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/examples/radix/ui/tooltip" } from "@/registry/new-york-v4/ui/tooltip"
import { AudioLinesIcon, PlusIcon } from "lucide-react"
export function ButtonGroupInputGroup() { export function ButtonGroupInputGroup() {
const [voiceEnabled, setVoiceEnabled] = React.useState(false) const [voiceEnabled, setVoiceEnabled] = React.useState(false)

View File

@@ -1,9 +1,10 @@
"use client" "use client"
import { Button } from "@/examples/radix/ui/button"
import { ButtonGroup } from "@/examples/radix/ui/button-group"
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react" import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
export function ButtonGroupNested() { export function ButtonGroupNested() {
return ( return (
<ButtonGroup> <ButtonGroup>

View File

@@ -1,13 +1,14 @@
import { Button } from "@/examples/radix/ui/button" import { BotIcon, ChevronDownIcon } from "lucide-react"
import { ButtonGroup } from "@/examples/radix/ui/button-group"
import { Button } from "@/registry/new-york-v4/ui/button"
import { ButtonGroup } from "@/registry/new-york-v4/ui/button-group"
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/examples/radix/ui/popover" } from "@/registry/new-york-v4/ui/popover"
import { Separator } from "@/examples/radix/ui/separator" import { Separator } from "@/registry/new-york-v4/ui/separator"
import { Textarea } from "@/examples/radix/ui/textarea" import { Textarea } from "@/registry/new-york-v4/ui/textarea"
import { BotIcon, ChevronDownIcon } from "lucide-react"
export function ButtonGroupPopover() { export function ButtonGroupPopover() {
return ( return (
@@ -21,7 +22,7 @@ export function ButtonGroupPopover() {
<ChevronDownIcon /> <ChevronDownIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" className="gap-0 rounded-xl p-0 text-sm"> <PopoverContent align="end" className="rounded-xl p-0 text-sm">
<div className="px-4 py-3"> <div className="px-4 py-3">
<div className="text-sm font-medium">Agent Tasks</div> <div className="text-sm font-medium">Agent Tasks</div>
</div> </div>

View File

@@ -1,10 +1,11 @@
import { PlusIcon } from "lucide-react"
import { import {
Avatar, Avatar,
AvatarFallback, AvatarFallback,
AvatarGroup,
AvatarImage, AvatarImage,
} from "@/examples/radix/ui/avatar" } from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/examples/radix/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
import { import {
Empty, Empty,
EmptyContent, EmptyContent,
@@ -12,15 +13,14 @@ import {
EmptyHeader, EmptyHeader,
EmptyMedia, EmptyMedia,
EmptyTitle, EmptyTitle,
} from "@/examples/radix/ui/empty" } from "@/registry/new-york-v4/ui/empty"
import { PlusIcon } from "lucide-react"
export function EmptyAvatarGroup() { export function EmptyAvatarGroup() {
return ( return (
<Empty className="flex-none border py-10"> <Empty className="flex-none border">
<EmptyHeader> <EmptyHeader>
<EmptyMedia> <EmptyMedia>
<AvatarGroup className="grayscale"> <div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar> <Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback> <AvatarFallback>CN</AvatarFallback>
@@ -39,7 +39,7 @@ export function EmptyAvatarGroup() {
/> />
<AvatarFallback>ER</AvatarFallback> <AvatarFallback>ER</AvatarFallback>
</Avatar> </Avatar>
</AvatarGroup> </div>
</EmptyMedia> </EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle> <EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription> <EmptyDescription>

View File

@@ -1,5 +1,5 @@
import { Checkbox } from "@/examples/radix/ui/checkbox" import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import { Field, FieldLabel } from "@/examples/radix/ui/field" import { Field, FieldLabel } from "@/registry/new-york-v4/ui/field"
export function FieldCheckbox() { export function FieldCheckbox() {
return ( return (

View File

@@ -1,5 +1,5 @@
import { Button } from "@/examples/radix/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
import { Checkbox } from "@/examples/radix/ui/checkbox" import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import { import {
Field, Field,
FieldDescription, FieldDescription,
@@ -8,21 +8,20 @@ import {
FieldLegend, FieldLegend,
FieldSeparator, FieldSeparator,
FieldSet, FieldSet,
} from "@/examples/radix/ui/field" } from "@/registry/new-york-v4/ui/field"
import { Input } from "@/examples/radix/ui/input" import { Input } from "@/registry/new-york-v4/ui/input"
import { import {
Select, Select,
SelectContent, SelectContent,
SelectGroup,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/examples/radix/ui/select" } from "@/registry/new-york-v4/ui/select"
import { Textarea } from "@/examples/radix/ui/textarea" import { Textarea } from "@/registry/new-york-v4/ui/textarea"
export function FieldDemo() { export function FieldDemo() {
return ( return (
<div className="w-full max-w-md rounded-xl border p-6"> <div className="w-full max-w-md rounded-lg border p-6">
<form> <form>
<FieldGroup> <FieldGroup>
<FieldSet> <FieldSet>
@@ -70,20 +69,18 @@ export function FieldDemo() {
<SelectValue placeholder="MM" /> <SelectValue placeholder="MM" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectItem value="01">01</SelectItem>
<SelectItem value="01">01</SelectItem> <SelectItem value="02">02</SelectItem>
<SelectItem value="02">02</SelectItem> <SelectItem value="03">03</SelectItem>
<SelectItem value="03">03</SelectItem> <SelectItem value="04">04</SelectItem>
<SelectItem value="04">04</SelectItem> <SelectItem value="05">05</SelectItem>
<SelectItem value="05">05</SelectItem> <SelectItem value="06">06</SelectItem>
<SelectItem value="06">06</SelectItem> <SelectItem value="07">07</SelectItem>
<SelectItem value="07">07</SelectItem> <SelectItem value="08">08</SelectItem>
<SelectItem value="08">08</SelectItem> <SelectItem value="09">09</SelectItem>
<SelectItem value="09">09</SelectItem> <SelectItem value="10">10</SelectItem>
<SelectItem value="10">10</SelectItem> <SelectItem value="11">11</SelectItem>
<SelectItem value="11">11</SelectItem> <SelectItem value="12">12</SelectItem>
<SelectItem value="12">12</SelectItem>
</SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</Field> </Field>
@@ -96,14 +93,12 @@ export function FieldDemo() {
<SelectValue placeholder="YYYY" /> <SelectValue placeholder="YYYY" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectItem value="2024">2024</SelectItem>
<SelectItem value="2024">2024</SelectItem> <SelectItem value="2025">2025</SelectItem>
<SelectItem value="2025">2025</SelectItem> <SelectItem value="2026">2026</SelectItem>
<SelectItem value="2026">2026</SelectItem> <SelectItem value="2027">2027</SelectItem>
<SelectItem value="2027">2027</SelectItem> <SelectItem value="2028">2028</SelectItem>
<SelectItem value="2028">2028</SelectItem> <SelectItem value="2029">2029</SelectItem>
<SelectItem value="2029">2029</SelectItem>
</SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</Field> </Field>

View File

@@ -1,5 +1,5 @@
import { Card, CardContent } from "@/examples/radix/ui/card" import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
import { Checkbox } from "@/examples/radix/ui/checkbox" import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
import { import {
Field, Field,
FieldDescription, FieldDescription,
@@ -8,7 +8,7 @@ import {
FieldLegend, FieldLegend,
FieldSet, FieldSet,
FieldTitle, FieldTitle,
} from "@/examples/radix/ui/field" } from "@/registry/new-york-v4/ui/field"
const options = [ const options = [
{ {
@@ -50,7 +50,7 @@ export function FieldHear() {
> >
<Field <Field
orientation="horizontal" orientation="horizontal"
className="gap-1.5 overflow-hidden px-3! py-1.5! transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:px-2!" className="gap-1.5 overflow-hidden !px-3 !py-1.5 transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:!px-2"
> >
<Checkbox <Checkbox
value={option.value} value={option.value}

View File

@@ -1,8 +1,13 @@
"use client" "use client"
import { useState } from "react" 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 "@/registry/new-york-v4/ui/field"
import { Slider } from "@/registry/new-york-v4/ui/slider"
export function FieldSlider() { export function FieldSlider() {
const [value, setValue] = useState([200, 800]) const [value, setValue] = useState([200, 800])

View File

@@ -1,4 +1,4 @@
import { FieldSeparator } from "@/examples/radix/ui/field" import { FieldSeparator } from "@/registry/new-york-v4/ui/field"
import { AppearanceSettings } from "./appearance-settings" import { AppearanceSettings } from "./appearance-settings"
import { ButtonGroupDemo } from "./button-group-demo" import { ButtonGroupDemo } from "./button-group-demo"

View File

@@ -1,19 +1,20 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
import { import {
InputGroup, InputGroup,
InputGroupAddon, InputGroupAddon,
InputGroupButton, InputGroupButton,
InputGroupInput, InputGroupInput,
} from "@/examples/radix/ui/input-group" } from "@/registry/new-york-v4/ui/input-group"
import { Label } from "@/examples/radix/ui/label" import { Label } from "@/registry/new-york-v4/ui/label"
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/examples/radix/ui/popover" } from "@/registry/new-york-v4/ui/popover"
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
export function InputGroupButtonExample() { export function InputGroupButtonExample() {
const [isFavorite, setIsFavorite] = React.useState(false) const [isFavorite, setIsFavorite] = React.useState(false)

View File

@@ -1,9 +1,12 @@
import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/examples/radix/ui/dropdown-menu" } from "@/registry/new-york-v4/ui/dropdown-menu"
import { import {
InputGroup, InputGroup,
InputGroupAddon, InputGroupAddon,
@@ -11,15 +14,13 @@ import {
InputGroupInput, InputGroupInput,
InputGroupText, InputGroupText,
InputGroupTextarea, InputGroupTextarea,
} from "@/examples/radix/ui/input-group" } from "@/registry/new-york-v4/ui/input-group"
import { Separator } from "@/examples/radix/ui/separator" import { Separator } from "@/registry/new-york-v4/ui/separator"
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/examples/radix/ui/tooltip" } from "@/registry/new-york-v4/ui/tooltip"
import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
export function InputGroupDemo() { export function InputGroupDemo() {
return ( return (
@@ -66,7 +67,11 @@ export function InputGroupDemo() {
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost">Auto</InputGroupButton> <InputGroupButton variant="ghost">Auto</InputGroupButton>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start"> <DropdownMenuContent
side="top"
align="start"
className="[--radius:0.95rem]"
>
<DropdownMenuItem>Auto</DropdownMenuItem> <DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem> <DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem> <DropdownMenuItem>Manual</DropdownMenuItem>

View File

@@ -1,4 +1,6 @@
import { Button } from "@/examples/radix/ui/button" import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button"
import { import {
Item, Item,
ItemActions, ItemActions,
@@ -6,8 +8,7 @@ import {
ItemDescription, ItemDescription,
ItemMedia, ItemMedia,
ItemTitle, ItemTitle,
} from "@/examples/radix/ui/item" } from "@/registry/new-york-v4/ui/item"
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
export function ItemDemo() { export function ItemDemo() {
return ( return (

View File

@@ -1,8 +1,24 @@
"use client" "use client"
import { useMemo, useState } from "react" import { useMemo, useState } from "react"
import { Avatar, AvatarFallback, AvatarImage } from "@/examples/radix/ui/avatar" import {
import { Badge } from "@/examples/radix/ui/badge" IconApps,
IconArrowUp,
IconAt,
IconBook,
IconCircleDashedPlus,
IconPaperclip,
IconPlus,
IconWorld,
IconX,
} from "@tabler/icons-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { import {
Command, Command,
CommandEmpty, CommandEmpty,
@@ -10,7 +26,7 @@ import {
CommandInput, CommandInput,
CommandItem, CommandItem,
CommandList, CommandList,
} from "@/examples/radix/ui/command" } from "@/registry/new-york-v4/ui/command"
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
@@ -23,36 +39,25 @@ import {
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/examples/radix/ui/dropdown-menu" } from "@/registry/new-york-v4/ui/dropdown-menu"
import { Field, FieldLabel } from "@/examples/radix/ui/field" import { Field, FieldLabel } from "@/registry/new-york-v4/ui/field"
import { import {
InputGroup, InputGroup,
InputGroupAddon, InputGroupAddon,
InputGroupButton, InputGroupButton,
InputGroupTextarea, InputGroupTextarea,
} from "@/examples/radix/ui/input-group" } from "@/registry/new-york-v4/ui/input-group"
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/examples/radix/ui/popover" } from "@/registry/new-york-v4/ui/popover"
import { Switch } from "@/examples/radix/ui/switch" import { Switch } from "@/registry/new-york-v4/ui/switch"
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/examples/radix/ui/tooltip" } from "@/registry/new-york-v4/ui/tooltip"
import {
IconApps,
IconArrowUp,
IconAt,
IconBook,
IconCircleDashedPlus,
IconPaperclip,
IconPlus,
IconWorld,
IconX,
} from "@tabler/icons-react"
const SAMPLE_DATA = { const SAMPLE_DATA = {
mentionable: [ mentionable: [
@@ -185,17 +190,17 @@ export function NotionPromptForm() {
const hasMentions = mentions.length > 0 const hasMentions = mentions.length > 0
return ( return (
<form> <form className="[--radius:1.2rem]">
<Field> <Field>
<FieldLabel htmlFor="notion-prompt" className="sr-only"> <FieldLabel htmlFor="notion-prompt" className="sr-only">
Prompt Prompt
</FieldLabel> </FieldLabel>
<InputGroup className="rounded-xl"> <InputGroup>
<InputGroupTextarea <InputGroupTextarea
id="notion-prompt" id="notion-prompt"
placeholder="Ask, search, or make anything..." placeholder="Ask, search, or make anything..."
/> />
<InputGroupAddon align="block-start" className="pt-3"> <InputGroupAddon align="block-start">
<Popover <Popover
open={mentionPopoverOpen} open={mentionPopoverOpen}
onOpenChange={setMentionPopoverOpen} onOpenChange={setMentionPopoverOpen}
@@ -209,7 +214,7 @@ export function NotionPromptForm() {
<InputGroupButton <InputGroupButton
variant="outline" variant="outline"
size={!hasMentions ? "sm" : "icon-sm"} size={!hasMentions ? "sm" : "icon-sm"}
className="transition-transform" className="rounded-full transition-transform"
> >
<IconAt /> {!hasMentions && "Add context"} <IconAt /> {!hasMentions && "Add context"}
</InputGroupButton> </InputGroupButton>
@@ -217,7 +222,7 @@ export function NotionPromptForm() {
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Mention a person, page, or date</TooltipContent> <TooltipContent>Mention a person, page, or date</TooltipContent>
</Tooltip> </Tooltip>
<PopoverContent className="p-0" align="start"> <PopoverContent className="p-0 [--radius:1.2rem]" align="start">
<Command> <Command>
<CommandInput placeholder="Search pages..." /> <CommandInput placeholder="Search pages..." />
<CommandList> <CommandList>
@@ -235,7 +240,6 @@ export function NotionPromptForm() {
setMentions((prev) => [...prev, currentValue]) setMentions((prev) => [...prev, currentValue])
setMentionPopoverOpen(false) setMentionPopoverOpen(false)
}} }}
className="rounded-lg"
> >
<MentionableIcon item={item} /> <MentionableIcon item={item} />
{item.title} {item.title}
@@ -302,8 +306,12 @@ export function NotionPromptForm() {
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Select AI model</TooltipContent> <TooltipContent>Select AI model</TooltipContent>
</Tooltip> </Tooltip>
<DropdownMenuContent side="top" align="start" className="w-48"> <DropdownMenuContent
<DropdownMenuGroup className="w-48"> side="top"
align="start"
className="[--radius:1rem]"
>
<DropdownMenuGroup className="w-42">
<DropdownMenuLabel className="text-muted-foreground text-xs"> <DropdownMenuLabel className="text-muted-foreground text-xs">
Select Agent Mode Select Agent Mode
</DropdownMenuLabel> </DropdownMenuLabel>
@@ -338,7 +346,11 @@ export function NotionPromptForm() {
<IconWorld /> All Sources <IconWorld /> All Sources
</InputGroupButton> </InputGroupButton>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent side="top" align="end" className="w-72"> <DropdownMenuContent
side="top"
align="end"
className="[--radius:1rem]"
>
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem <DropdownMenuItem
asChild asChild

View File

@@ -1,5 +1,5 @@
import { Badge } from "@/examples/radix/ui/badge" import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Spinner } from "@/examples/radix/ui/spinner" import { Spinner } from "@/registry/new-york-v4/ui/spinner"
export function SpinnerBadge() { export function SpinnerBadge() {
return ( return (

View File

@@ -1,4 +1,4 @@
import { Button } from "@/examples/radix/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
import { import {
Empty, Empty,
EmptyContent, EmptyContent,
@@ -6,8 +6,8 @@ import {
EmptyHeader, EmptyHeader,
EmptyMedia, EmptyMedia,
EmptyTitle, EmptyTitle,
} from "@/examples/radix/ui/empty" } from "@/registry/new-york-v4/ui/empty"
import { Spinner } from "@/examples/radix/ui/spinner" import { Spinner } from "@/registry/new-york-v4/ui/spinner"
export function SpinnerEmpty() { export function SpinnerEmpty() {
return ( return (

View File

@@ -1,6 +1,8 @@
import { type Metadata } from "next" import { type Metadata } from "next"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
import { PlusSignIcon } from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react"
import { Announcement } from "@/components/announcement" import { Announcement } from "@/components/announcement"
import { ExamplesNav } from "@/components/examples-nav" import { ExamplesNav } from "@/components/examples-nav"
@@ -56,7 +58,10 @@ export default function IndexPage() {
<PageHeaderDescription>{description}</PageHeaderDescription> <PageHeaderDescription>{description}</PageHeaderDescription>
<PageActions> <PageActions>
<Button asChild size="sm" className="h-[31px] rounded-lg"> <Button asChild size="sm" className="h-[31px] rounded-lg">
<Link href="/docs/installation">Get Started</Link> <Link href="/create">
<HugeiconsIcon icon={PlusSignIcon} />
New Project
</Link>
</Button> </Button>
<Button asChild size="sm" variant="ghost" className="rounded-lg"> <Button asChild size="sm" variant="ghost" className="rounded-lg">
<Link href="/docs/components">View Components</Link> <Link href="/docs/components">View Components</Link>

View File

@@ -2,11 +2,7 @@ import * as React from "react"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { import { ChartDisplay } from "@/components/chart-display"
ChartDisplay,
getCachedRegistryItem,
getChartHighlightedCode,
} from "@/components/chart-display"
import { getActiveStyle } from "@/registry/_legacy-styles" import { getActiveStyle } from "@/registry/_legacy-styles"
import { charts } from "@/app/(app)/charts/charts" import { charts } from "@/app/(app)/charts/charts"
@@ -48,26 +44,6 @@ export default async function ChartPage({ params }: ChartPageProps) {
const chartList = charts[chartType] const chartList = charts[chartType]
const activeStyle = await getActiveStyle() const activeStyle = await getActiveStyle()
// Prefetch all chart data in parallel for better performance.
// Charts are rendered via iframes, so we only need the metadata and highlighted code.
const chartDataPromises = chartList.map(async (chart) => {
const registryItem = await getCachedRegistryItem(chart.id, activeStyle.name)
if (!registryItem) return null
const highlightedCode = await getChartHighlightedCode(
registryItem.files?.[0]?.content ?? ""
)
if (!highlightedCode) return null
return {
...registryItem,
highlightedCode,
fullWidth: chart.fullWidth,
}
})
const prefetchedCharts = await Promise.all(chartDataPromises)
return ( return (
<div className="grid flex-1 gap-12 lg:gap-24"> <div className="grid flex-1 gap-12 lg:gap-24">
<h2 className="sr-only"> <h2 className="sr-only">
@@ -75,14 +51,16 @@ export default async function ChartPage({ params }: ChartPageProps) {
</h2> </h2>
<div className="grid flex-1 scroll-mt-20 items-stretch gap-10 md:grid-cols-2 md:gap-6 lg:grid-cols-3 xl:gap-10"> <div className="grid flex-1 scroll-mt-20 items-stretch gap-10 md:grid-cols-2 md:gap-6 lg:grid-cols-3 xl:gap-10">
{Array.from({ length: 12 }).map((_, index) => { {Array.from({ length: 12 }).map((_, index) => {
const chart = prefetchedCharts[index] const chart = chartList[index]
return chart ? ( return chart ? (
<ChartDisplay <ChartDisplay
key={chart.name} key={chart.id}
chart={chart} name={chart.id}
style={activeStyle.name} styleName={activeStyle.name}
className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")} className={cn(chart.fullWidth && "md:col-span-2 lg:col-span-3")}
/> >
<chart.component />
</ChartDisplay>
) : ( ) : (
<div <div
key={`empty-${index}`} key={`empty-${index}`}

View File

@@ -63,8 +63,9 @@ export default function ChartsLayout({
</PageHeader> </PageHeader>
<PageNav id="charts"> <PageNav id="charts">
<ChartsNav /> <ChartsNav />
<ThemeSelector className="mr-4 hidden md:flex" />
</PageNav> </PageNav>
<div className="container-wrapper flex-1"> <div className="container-wrapper section-soft flex-1">
<div className="container pb-6"> <div className="container pb-6">
<section className="theme-container">{children}</section> <section className="theme-container">{children}</section>
</div> </div>

View File

@@ -1,15 +1,21 @@
import Link from "next/link" import Link from "next/link"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { mdxComponents } from "@/mdx-components" import { mdxComponents } from "@/mdx-components"
import { IconArrowLeft, IconArrowRight } from "@tabler/icons-react" import {
IconArrowLeft,
IconArrowRight,
IconArrowUpRight,
} from "@tabler/icons-react"
import fm from "front-matter"
import { findNeighbour } from "fumadocs-core/page-tree" import { findNeighbour } from "fumadocs-core/page-tree"
import z from "zod"
import { source } from "@/lib/source" import { source } from "@/lib/source"
import { absoluteUrl } from "@/lib/utils" import { absoluteUrl } from "@/lib/utils"
import { DocsBaseSwitcher } from "@/components/docs-base-switcher"
import { DocsCopyPage } from "@/components/docs-copy-page" import { DocsCopyPage } from "@/components/docs-copy-page"
import { DocsTableOfContents } from "@/components/docs-toc" import { DocsTableOfContents } from "@/components/docs-toc"
import { OpenInV0Cta } from "@/components/open-in-v0-cta" import { OpenInV0Cta } from "@/components/open-in-v0-cta"
import { Badge } from "@/registry/new-york-v4/ui/badge"
import { Button } from "@/registry/new-york-v4/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
export const revalidate = false export const revalidate = false
@@ -79,116 +85,127 @@ export default async function Page(props: {
const doc = page.data const doc = page.data
const MDX = doc.body const MDX = doc.body
const isChangelog = params.slug?.[0] === "changelog" const neighbours = findNeighbour(source.pageTree, page.url)
const neighbours = isChangelog
? { previous: null, next: null }
: findNeighbour(source.pageTree, page.url)
const raw = await page.data.getText("raw") const raw = await page.data.getText("raw")
const { attributes } = fm(raw)
const { links } = z
.object({
links: z
.object({
doc: z.string().optional(),
api: z.string().optional(),
})
.optional(),
})
.parse(attributes)
return ( return (
<div <div className="flex items-stretch text-[1.05rem] sm:text-[15px] xl:w-full">
data-slot="docs"
className="flex scroll-mt-24 items-stretch pb-8 text-[1.05rem] sm:text-[15px] xl:w-full"
>
<div className="flex min-w-0 flex-1 flex-col"> <div className="flex min-w-0 flex-1 flex-col">
<div className="h-(--top-spacing) shrink-0" /> <div className="h-(--top-spacing) shrink-0" />
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300"> <div className="mx-auto flex w-full max-w-2xl min-w-0 flex-1 flex-col gap-8 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center justify-between md:items-start"> <div className="flex items-start justify-between">
<h1 className="scroll-m-24 text-3xl font-semibold tracking-tight sm:text-3xl"> <h1 className="scroll-m-20 text-4xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">
{doc.title} {doc.title}
</h1> </h1>
<div className="docs-nav flex items-center gap-2"> <div className="docs-nav bg-background/80 border-border/50 fixed inset-x-0 bottom-0 isolate z-50 flex items-center gap-2 border-t px-6 py-4 backdrop-blur-sm sm:static sm:z-0 sm:border-t-0 sm:bg-transparent sm:px-0 sm:pt-1.5 sm:backdrop-blur-none">
<div className="hidden sm:block"> <DocsCopyPage page={raw} url={absoluteUrl(page.url)} />
<DocsCopyPage page={raw} url={absoluteUrl(page.url)} /> {neighbours.previous && (
</div> <Button
<div className="ml-auto flex gap-2"> variant="secondary"
{neighbours.previous && ( size="icon"
<Button className="extend-touch-target ml-auto size-8 shadow-none md:size-7"
variant="secondary" asChild
size="icon" >
className="extend-touch-target size-8 shadow-none md:size-7" <Link href={neighbours.previous.url}>
asChild <IconArrowLeft />
> <span className="sr-only">Previous</span>
<Link href={neighbours.previous.url}> </Link>
<IconArrowLeft /> </Button>
<span className="sr-only">Previous</span> )}
</Link> {neighbours.next && (
</Button> <Button
)} variant="secondary"
{neighbours.next && ( size="icon"
<Button className="extend-touch-target size-8 shadow-none md:size-7"
variant="secondary" asChild
size="icon" >
className="extend-touch-target size-8 shadow-none md:size-7" <Link href={neighbours.next.url}>
asChild <span className="sr-only">Next</span>
> <IconArrowRight />
<Link href={neighbours.next.url}> </Link>
<span className="sr-only">Next</span> </Button>
<IconArrowRight /> )}
</Link>
</Button>
)}
</div>
</div> </div>
</div> </div>
{doc.description && ( {doc.description && (
<p className="text-muted-foreground text-[1.05rem] sm:text-base sm:text-balance md:max-w-[80%]"> <p className="text-muted-foreground text-[1.05rem] text-balance sm:text-base">
{doc.description} {doc.description}
</p> </p>
)} )}
</div> </div>
{links ? (
<div className="flex items-center gap-2 pt-4">
{links?.doc && (
<Badge asChild variant="secondary" className="rounded-full">
<a href={links.doc} target="_blank" rel="noreferrer">
Docs <IconArrowUpRight />
</a>
</Badge>
)}
{links?.api && (
<Badge asChild variant="secondary" className="rounded-full">
<a href={links.api} target="_blank" rel="noreferrer">
API Reference <IconArrowUpRight />
</a>
</Badge>
)}
</div>
) : null}
</div> </div>
<div className="w-full flex-1 pb-16 *:data-[slot=alert]:first:mt-0 sm:pb-0"> <div className="w-full flex-1 *:data-[slot=alert]:first:mt-0">
{params.slug &&
params.slug[0] === "components" &&
params.slug[1] &&
params.slug[2] && (
<DocsBaseSwitcher
base={params.slug[1]}
component={params.slug[2]}
className="mb-4"
/>
)}
<MDX components={mdxComponents} /> <MDX components={mdxComponents} />
</div> </div>
<div className="hidden h-16 w-full items-center gap-2 px-4 sm:flex sm:px-0"> </div>
{neighbours.previous && ( <div className="mx-auto hidden h-16 w-full max-w-2xl items-center gap-2 px-4 sm:flex md:px-0">
<Button {neighbours.previous && (
variant="secondary" <Button
size="sm" variant="secondary"
asChild size="sm"
className="shadow-none" asChild
> className="shadow-none"
<Link href={neighbours.previous.url}> >
<IconArrowLeft /> {neighbours.previous.name} <Link href={neighbours.previous.url}>
</Link> <IconArrowLeft /> {neighbours.previous.name}
</Button> </Link>
)} </Button>
{neighbours.next && ( )}
<Button {neighbours.next && (
variant="secondary" <Button
size="sm" variant="secondary"
className="ml-auto shadow-none" size="sm"
asChild className="ml-auto shadow-none"
> asChild
<Link href={neighbours.next.url}> >
{neighbours.next.name} <IconArrowRight /> <Link href={neighbours.next.url}>
</Link> {neighbours.next.name} <IconArrowRight />
</Button> </Link>
)} </Button>
</div> )}
</div> </div>
</div> </div>
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-(--sidebar-width) flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex"> <div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[calc(100svh-var(--footer-height)+2rem)] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 xl:flex">
<div className="h-(--top-spacing) shrink-0"></div> <div className="h-(--top-spacing) shrink-0" />
{doc.toc?.length ? ( {doc.toc?.length ? (
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8"> <div className="no-scrollbar overflow-y-auto px-8">
<DocsTableOfContents toc={doc.toc} /> <DocsTableOfContents toc={doc.toc} />
<div className="h-12" />
</div> </div>
) : null} ) : null}
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex"> <div className="flex flex-1 flex-col gap-12 px-6">
<OpenInV0Cta /> <OpenInV0Cta />
</div> </div>
</div> </div>

View File

@@ -1,144 +0,0 @@
import Link from "next/link"
import { Button } from "@/examples/radix/ui/button"
import { mdxComponents } from "@/mdx-components"
import { IconRss } from "@tabler/icons-react"
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
import { absoluteUrl } from "@/lib/utils"
import { OpenInV0Cta } from "@/components/open-in-v0-cta"
export const revalidate = false
export const dynamic = "force-static"
export function generateMetadata() {
return {
title: "Changelog",
description: "Latest updates and announcements.",
openGraph: {
title: "Changelog",
description: "Latest updates and announcements.",
type: "article",
url: absoluteUrl("/docs/changelog"),
images: [
{
url: `/og?title=${encodeURIComponent(
"Changelog"
)}&description=${encodeURIComponent(
"Latest updates and announcements."
)}`,
},
],
},
}
}
export default function ChangelogPage() {
const pages = getChangelogPages()
const latestPages = pages.slice(0, 5)
const olderPages = pages.slice(5)
return (
<div
data-slot="docs"
className="flex scroll-mt-24 items-stretch pb-8 text-[1.05rem] sm:text-[15px] xl:w-full"
>
<div className="flex min-w-0 flex-1 flex-col">
<div className="h-(--top-spacing) shrink-0" />
<div className="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 flex-col gap-6 px-4 py-6 text-neutral-800 md:px-0 lg:py-8 dark:text-neutral-300">
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<h1 className="scroll-m-24 text-4xl font-semibold tracking-tight sm:text-3xl">
Changelog
</h1>
<Button variant="secondary" size="sm" asChild>
<a href="/rss.xml" target="_blank" rel="noopener noreferrer">
<IconRss />
RSS
</a>
</Button>
</div>
<p className="text-muted-foreground text-[1.05rem] sm:text-base sm:text-balance md:max-w-[80%]">
Latest updates and announcements.
</p>
</div>
<div className="w-full flex-1 pb-16 sm:pb-0">
{latestPages.map((page) => {
const data = page.data as ChangelogPageData
const MDX = page.data.body
return (
<article key={page.url} className="mb-12 border-b pb-12">
<h2 className="font-heading text-xl font-semibold tracking-tight">
{data.title}
</h2>
<div className="prose-changelog mt-6 *:first:mt-0">
<MDX components={mdxComponents} />
</div>
</article>
)
})}
{olderPages.length > 0 && (
<div id="more-updates" className="mb-24 scroll-mt-24">
<h2 className="font-heading mb-6 text-xl font-semibold tracking-tight">
More Updates
</h2>
<div className="grid auto-rows-fr gap-3 sm:grid-cols-2">
{olderPages.map((page) => {
const data = page.data as ChangelogPageData
const [date, ...titleParts] = data.title.split(" - ")
const title = titleParts.join(" - ")
return (
<Link
key={page.url}
href={page.url}
className="bg-surface text-surface-foreground hover:bg-surface/80 flex w-full flex-col rounded-xl px-4 py-3 transition-colors"
>
<span className="text-muted-foreground text-xs">
{date}
</span>
<span className="text-sm font-medium">{title}</span>
</Link>
)
})}
</div>
</div>
)}
</div>
</div>
</div>
<div className="sticky top-[calc(var(--header-height)+1px)] z-30 ml-auto hidden h-[90svh] w-72 flex-col gap-4 overflow-hidden overscroll-none pb-8 lg:flex">
<div className="h-(--top-spacing) shrink-0"></div>
<div className="no-scrollbar flex flex-col gap-8 overflow-y-auto px-8">
<div className="flex flex-col gap-2 p-4 pt-0 text-sm">
<p className="text-muted-foreground bg-background sticky top-0 h-6 text-xs font-medium">
On This Page
</p>
{latestPages.map((page) => {
const data = page.data as ChangelogPageData
return (
<Link
key={page.url}
href={page.url}
className="text-muted-foreground hover:text-foreground text-[0.8rem] no-underline transition-colors"
>
{data.title}
</Link>
)
})}
{olderPages.length > 0 && (
<a
href="#more-updates"
className="text-muted-foreground hover:text-foreground text-[0.8rem] no-underline transition-colors"
>
More Updates
</a>
)}
</div>
</div>
<div className="hidden flex-1 flex-col gap-6 px-6 xl:flex">
<OpenInV0Cta />
</div>
</div>
</div>
)
}

View File

@@ -9,14 +9,7 @@ export default function DocsLayout({
}) { }) {
return ( return (
<div className="container-wrapper flex flex-1 flex-col px-2"> <div className="container-wrapper flex flex-1 flex-col px-2">
<SidebarProvider <SidebarProvider className="3xl:fixed:container 3xl:fixed:px-3 min-h-min flex-1 items-start px-0 [--sidebar-width:220px] [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--sidebar-width:240px] lg:[--top-spacing:calc(var(--spacing)*4)]">
className="3xl:fixed:container 3xl:fixed:px-3 min-h-min flex-1 items-start px-0 [--top-spacing:0] lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] lg:[--top-spacing:calc(var(--spacing)*4)]"
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
} as React.CSSProperties
}
>
<DocsSidebar tree={source.pageTree} /> <DocsSidebar tree={source.pageTree} />
<div className="h-full w-full">{children}</div> <div className="h-full w-full">{children}</div>
</SidebarProvider> </SidebarProvider>

View File

@@ -70,7 +70,7 @@ export default function ExamplesLayout({
</PageNav> </PageNav>
<div className="container-wrapper section-soft flex flex-1 flex-col pb-6"> <div className="container-wrapper section-soft flex flex-1 flex-col pb-6">
<div className="theme-container container flex flex-1 scroll-mt-20 flex-col"> <div className="theme-container container flex flex-1 scroll-mt-20 flex-col">
<div className="bg-background flex flex-col overflow-hidden rounded-lg border bg-clip-padding has-[[data-slot=rtl-components]]:overflow-visible has-[[data-slot=rtl-components]]:border-0 has-[[data-slot=rtl-components]]:bg-transparent md:flex-1 xl:rounded-xl"> <div className="bg-background flex flex-col overflow-hidden rounded-lg border bg-clip-padding md:flex-1 xl:rounded-xl">
{children} {children}
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import type { Slider as SliderPrimitive } from "radix-ui" import { type SliderProps } from "@radix-ui/react-slider"
import { import {
HoverCard, HoverCard,
@@ -12,9 +12,7 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { Slider } from "@/registry/new-york-v4/ui/slider" import { Slider } from "@/registry/new-york-v4/ui/slider"
interface MaxLengthSelectorProps { interface MaxLengthSelectorProps {
defaultValue: React.ComponentProps< defaultValue: SliderProps["defaultValue"]
typeof SliderPrimitive.Root
>["defaultValue"]
} }
export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) { export function MaxLengthSelector({ defaultValue }: MaxLengthSelectorProps) {

View File

@@ -1,8 +1,8 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { type PopoverProps } from "@radix-ui/react-popover"
import { Check, ChevronsUpDown } from "lucide-react" import { Check, ChevronsUpDown } from "lucide-react"
import type { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { useMutationObserver } from "@/hooks/use-mutation-observer" import { useMutationObserver } from "@/hooks/use-mutation-observer"
@@ -29,8 +29,7 @@ import {
import { type Model, type ModelType } from "../data/models" import { type Model, type ModelType } from "../data/models"
interface ModelSelectorProps interface ModelSelectorProps extends PopoverProps {
extends React.ComponentProps<typeof PopoverPrimitive.Root> {
types: readonly ModelType[] types: readonly ModelType[]
models: Model[] models: Model[]
} }

View File

@@ -1,6 +1,7 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { Dialog } from "@radix-ui/react-dialog"
import { MoreHorizontal } from "lucide-react" import { MoreHorizontal } from "lucide-react"
import { toast } from "sonner" import { toast } from "sonner"
@@ -15,7 +16,6 @@ import {
} from "@/registry/new-york-v4/ui/alert-dialog" } from "@/registry/new-york-v4/ui/alert-dialog"
import { Button } from "@/registry/new-york-v4/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
import { import {
Dialog,
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogDescription, DialogDescription,

View File

@@ -1,8 +1,8 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { type PopoverProps } from "@radix-ui/react-popover"
import { Check, ChevronsUpDown } from "lucide-react" import { Check, ChevronsUpDown } from "lucide-react"
import type { Popover as PopoverPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/registry/new-york-v4/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
@@ -23,8 +23,7 @@ import {
import { type Preset } from "../data/presets" import { type Preset } from "../data/presets"
interface PresetSelectorProps interface PresetSelectorProps extends PopoverProps {
extends React.ComponentProps<typeof PopoverPrimitive.Root> {
presets: Preset[] presets: Preset[]
} }

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import type { Slider as SliderPrimitive } from "radix-ui" import { type SliderProps } from "@radix-ui/react-slider"
import { import {
HoverCard, HoverCard,
@@ -12,9 +12,7 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { Slider } from "@/registry/new-york-v4/ui/slider" import { Slider } from "@/registry/new-york-v4/ui/slider"
interface TemperatureSelectorProps { interface TemperatureSelectorProps {
defaultValue: React.ComponentProps< defaultValue: SliderProps["defaultValue"]
typeof SliderPrimitive.Root
>["defaultValue"]
} }
export function TemperatureSelector({ export function TemperatureSelector({

View File

@@ -1,7 +1,7 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import type { Slider as SliderPrimitive } from "radix-ui" import { type SliderProps } from "@radix-ui/react-slider"
import { import {
HoverCard, HoverCard,
@@ -12,9 +12,7 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { Slider } from "@/registry/new-york-v4/ui/slider" import { Slider } from "@/registry/new-york-v4/ui/slider"
interface TopPSelectorProps { interface TopPSelectorProps {
defaultValue: React.ComponentProps< defaultValue: SliderProps["defaultValue"]
typeof SliderPrimitive.Root
>["defaultValue"]
} }
export function TopPSelector({ defaultValue }: TopPSelectorProps) { export function TopPSelector({ defaultValue }: TopPSelectorProps) {

View File

@@ -1,170 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/examples/base/ui-rtl/field"
import { Input } from "@/examples/base/ui-rtl/input"
import { RadioGroup, RadioGroupItem } from "@/examples/base/ui-rtl/radio-group"
import { Switch } from "@/examples/base/ui-rtl/switch"
import { IconMinus, IconPlus } from "@tabler/icons-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
computeEnvironment: "بيئة الحوسبة",
computeDescription: "اختر بيئة الحوسبة لمجموعتك.",
kubernetes: "كوبرنيتس",
kubernetesDescription:
"تشغيل أحمال عمل GPU على مجموعة مُهيأة بـ K8s. هذا هو الافتراضي.",
virtualMachine: "جهاز افتراضي",
vmDescription: "الوصول إلى مجموعة VM مُهيأة لتشغيل أحمال العمل. (قريبًا)",
numberOfGpus: "عدد وحدات GPU",
gpuDescription: "يمكنك إضافة المزيد لاحقًا.",
decrement: "إنقاص",
increment: "زيادة",
wallpaperTinting: "تلوين الخلفية",
wallpaperDescription: "السماح بتلوين الخلفية.",
},
he: {
dir: "rtl" as const,
computeEnvironment: "סביבת מחשוב",
computeDescription: "בחר את סביבת המחשוב לאשכול שלך.",
kubernetes: "קוברנטיס",
kubernetesDescription:
"הפעל עומסי עבודה של GPU באשכול מוגדר K8s. זו ברירת המחדל.",
virtualMachine: "מכונה וירטואלית",
vmDescription: "גש לאשכול VM מוגדר להפעלת עומסי עבודה. (בקרוב)",
numberOfGpus: "מספר GPUs",
gpuDescription: "תוכל להוסיף עוד מאוחר יותר.",
decrement: "הפחת",
increment: "הגדל",
wallpaperTinting: "צביעת טפט",
wallpaperDescription: "אפשר לטפט להיצבע.",
},
}
export function AppearanceSettings() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [gpuCount, setGpuCount] = React.useState(8)
const handleGpuAdjustment = React.useCallback((adjustment: number) => {
setGpuCount((prevCount) =>
Math.max(1, Math.min(99, prevCount + adjustment))
)
}, [])
const handleGpuInputChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(e.target.value, 10)
if (!isNaN(value) && value >= 1 && value <= 99) {
setGpuCount(value)
}
},
[]
)
return (
<div dir={t.dir}>
<FieldSet>
<FieldGroup>
<FieldSet>
<FieldLegend>{t.computeEnvironment}</FieldLegend>
<FieldDescription>{t.computeDescription}</FieldDescription>
<RadioGroup defaultValue="kubernetes">
<FieldLabel htmlFor="rtl-kubernetes">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>{t.kubernetes}</FieldTitle>
<FieldDescription>
{t.kubernetesDescription}
</FieldDescription>
</FieldContent>
<RadioGroupItem
value="kubernetes"
id="rtl-kubernetes"
aria-label={t.kubernetes}
/>
</Field>
</FieldLabel>
<FieldLabel htmlFor="rtl-vm">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>{t.virtualMachine}</FieldTitle>
<FieldDescription>{t.vmDescription}</FieldDescription>
</FieldContent>
<RadioGroupItem
value="vm"
id="rtl-vm"
aria-label={t.virtualMachine}
/>
</Field>
</FieldLabel>
</RadioGroup>
</FieldSet>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="rtl-gpu-count">{t.numberOfGpus}</FieldLabel>
<FieldDescription>{t.gpuDescription}</FieldDescription>
</FieldContent>
<ButtonGroup>
<Input
id="rtl-gpu-count"
value={gpuCount}
onChange={handleGpuInputChange}
size={3}
className="h-7 !w-14 font-mono"
maxLength={3}
/>
<Button
variant="outline"
size="icon-sm"
type="button"
aria-label={t.decrement}
onClick={() => handleGpuAdjustment(-1)}
disabled={gpuCount <= 1}
>
<IconMinus />
</Button>
<Button
variant="outline"
size="icon-sm"
type="button"
aria-label={t.increment}
onClick={() => handleGpuAdjustment(1)}
disabled={gpuCount >= 99}
>
<IconPlus />
</Button>
</ButtonGroup>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="rtl-tinting">
{t.wallpaperTinting}
</FieldLabel>
<FieldDescription>{t.wallpaperDescription}</FieldDescription>
</FieldContent>
<Switch id="rtl-tinting" defaultChecked />
</Field>
</FieldGroup>
</FieldSet>
</div>
)
}

View File

@@ -1,179 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/examples/base/ui-rtl/dropdown-menu"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
goBack: "رجوع",
archive: "أرشفة",
report: "إبلاغ",
snooze: "تأجيل",
moreOptions: "خيارات أخرى",
markAsRead: "تحديد كمقروء",
addToCalendar: "إضافة إلى التقويم",
addToList: "إضافة إلى القائمة",
labelAs: "تصنيف كـ...",
personal: "شخصي",
work: "عمل",
other: "أخرى",
trash: "حذف",
},
he: {
dir: "rtl" as const,
goBack: "חזור",
archive: "ארכיון",
report: "דווח",
snooze: "נודניק",
moreOptions: "אפשרויות נוספות",
markAsRead: "סמן כנקרא",
addToCalendar: "הוסף ליומן",
addToList: "הוסף לרשימה",
labelAs: "תייג כ...",
personal: "אישי",
work: "עבודה",
other: "אחר",
trash: "מחק",
},
}
export function ButtonGroupDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [label, setLabel] = React.useState("personal")
return (
<div dir={t.dir}>
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button variant="outline" size="icon-sm" aria-label={t.goBack}>
<ArrowLeftIcon className="rtl:rotate-180" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
{t.archive}
</Button>
<Button variant="outline" size="sm">
{t.report}
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
{t.snooze}
</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
size="icon-sm"
aria-label={t.moreOptions}
/>
}
>
<MoreHorizontalIcon />
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
dir={t.dir}
data-lang={lang}
className="w-44"
>
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
{t.markAsRead}
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
{t.archive}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
{t.snooze}
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
{t.addToCalendar}
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
{t.addToList}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
{t.labelAs}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent
side="left"
dir={t.dir}
data-lang={lang}
>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
{t.personal}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
{t.work}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
{t.other}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
{t.trash}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
</div>
)
}

View File

@@ -1,82 +0,0 @@
"use client"
import * as React from "react"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/examples/base/ui-rtl/input-group"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui-rtl/tooltip"
import { AudioLinesIcon, PlusIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
add: "إضافة",
voicePlaceholder: "سجل وأرسل صوتًا...",
messagePlaceholder: "أرسل رسالة...",
voiceMode: "الوضع الصوتي",
},
he: {
dir: "rtl" as const,
add: "הוסף",
voicePlaceholder: "הקלט ושלח אודיו...",
messagePlaceholder: "שלח הודעה...",
voiceMode: "מצב קולי",
},
}
export function ButtonGroupInputGroup() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
return (
<ButtonGroup dir={t.dir}>
<ButtonGroup>
<Button variant="outline" size="icon" aria-label={t.add}>
<PlusIcon />
</Button>
</ButtonGroup>
<ButtonGroup className="flex-1">
<InputGroup>
<InputGroupInput
placeholder={
voiceEnabled ? t.voicePlaceholder : t.messagePlaceholder
}
disabled={voiceEnabled}
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
onClick={() => setVoiceEnabled(!voiceEnabled)}
data-active={voiceEnabled}
className="data-[active=true]:bg-primary data-[active=true]:text-primary-foreground"
aria-pressed={voiceEnabled}
size="icon-xs"
aria-label={t.voiceMode}
/>
}
>
<AudioLinesIcon />
</TooltipTrigger>
<TooltipContent>{t.voiceMode}</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,56 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
locale: "ar-SA",
previous: "السابق",
next: "التالي",
},
he: {
dir: "rtl" as const,
locale: "he-IL",
previous: "הקודם",
next: "הבא",
},
}
function formatNumber(value: number, locale: string) {
return new Intl.NumberFormat(locale).format(value)
}
export function ButtonGroupNested() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<ButtonGroup dir={t.dir}>
<ButtonGroup>
<Button variant="outline" size="sm">
{formatNumber(1, t.locale)}
</Button>
<Button variant="outline" size="sm">
{formatNumber(2, t.locale)}
</Button>
<Button variant="outline" size="sm">
{formatNumber(3, t.locale)}
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon-sm" aria-label={t.previous}>
<ArrowLeftIcon className="rtl:rotate-180" />
</Button>
<Button variant="outline" size="icon-sm" aria-label={t.next}>
<ArrowRightIcon className="rtl:rotate-180" />
</Button>
</ButtonGroup>
</ButtonGroup>
)
}

View File

@@ -1,83 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import { ButtonGroup } from "@/examples/base/ui-rtl/button-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/examples/base/ui-rtl/popover"
import { Separator } from "@/examples/base/ui-rtl/separator"
import { Textarea } from "@/examples/base/ui-rtl/textarea"
import { BotIcon, ChevronDownIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
copilot: "المساعد",
openPopover: "فتح القائمة",
agentTasks: "مهام الوكيل",
placeholder: "صف مهمتك بلغة طبيعية.",
startTask: "ابدأ مهمة جديدة مع المساعد",
description:
"صف مهمتك بلغة طبيعية. سيعمل المساعد في الخلفية ويفتح طلب سحب لمراجعتك.",
},
he: {
dir: "rtl" as const,
copilot: "עוזר",
openPopover: "פתח תפריט",
agentTasks: "משימות סוכן",
placeholder: "תאר את המשימה שלך בשפה טבעית.",
startTask: "התחל משימה חדשה עם העוזר",
description:
"תאר את המשימה שלך בשפה טבעית. העוזר יעבוד ברקע ויפתח בקשת משיכה לבדיקתך.",
},
}
export function ButtonGroupPopover() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<ButtonGroup dir={t.dir}>
<Button variant="outline" size="sm">
<BotIcon /> {t.copilot}
</Button>
<Popover>
<PopoverTrigger
render={
<Button
variant="outline"
size="icon-sm"
aria-label={t.openPopover}
/>
}
>
<ChevronDownIcon />
</PopoverTrigger>
<PopoverContent
align="start"
dir={t.dir}
data-lang={lang}
className="p-0"
>
<div className="px-4 py-3">
<div className="text-sm font-medium">{t.agentTasks}</div>
</div>
<Separator />
<div className="p-4 text-sm *:[p:not(:last-child)]:mb-2">
<Textarea
placeholder={t.placeholder}
className="mb-4 resize-none"
/>
<p className="font-medium">{t.startTask}</p>
<p className="text-muted-foreground">{t.description}</p>
</div>
</PopoverContent>
</Popover>
</ButtonGroup>
)
}

View File

@@ -1,78 +0,0 @@
"use client"
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
} from "@/examples/base/ui-rtl/avatar"
import { Button } from "@/examples/base/ui-rtl/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/examples/base/ui-rtl/empty"
import { PlusIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
title: "لا يوجد أعضاء في الفريق",
description: "قم بدعوة فريقك للتعاون في هذا المشروع.",
invite: "دعوة أعضاء",
},
he: {
dir: "rtl" as const,
title: "אין חברי צוות",
description: "הזמן את הצוות שלך לשתף פעולה בפרויקט זה.",
invite: "הזמן חברים",
},
}
export function EmptyAvatarGroup() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<Empty className="flex-none border py-10" dir={t.dir}>
<EmptyHeader>
<EmptyMedia>
<AvatarGroup className="grayscale">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</EmptyMedia>
<EmptyTitle>{t.title}</EmptyTitle>
<EmptyDescription>{t.description}</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">
<PlusIcon />
{t.invite}
</Button>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,36 +0,0 @@
"use client"
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
import { Field, FieldLabel } from "@/examples/base/ui-rtl/field"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
terms: "أوافق على الشروط والأحكام",
},
he: {
dir: "rtl" as const,
terms: "אני מסכים לתנאים וההגבלות",
},
}
export function FieldCheckbox() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const { dir, terms } = translations[lang]
return (
<div dir={dir}>
<FieldLabel htmlFor="checkbox-demo-rtl">
<Field orientation="horizontal">
<Checkbox id="checkbox-demo-rtl" defaultChecked />
<FieldLabel htmlFor="checkbox-demo-rtl" className="line-clamp-1">
{terms}
</FieldLabel>
</Field>
</FieldLabel>
</div>
)
}

View File

@@ -1,217 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
} from "@/examples/base/ui-rtl/field"
import { Input } from "@/examples/base/ui-rtl/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/examples/base/ui-rtl/select"
import { Textarea } from "@/examples/base/ui-rtl/textarea"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
locale: "ar-SA",
paymentMethod: "طريقة الدفع",
secureEncrypted: "جميع المعاملات آمنة ومشفرة",
nameOnCard: "الاسم على البطاقة",
namePlaceholder: "أحمد محمد",
cardNumber: "رقم البطاقة",
cardDescription: "أدخل رقمك المكون من 16 رقمًا.",
cvv: "رمز الأمان",
month: "الشهر",
year: "السنة",
billingAddress: "عنوان الفواتير",
billingDescription: "عنوان الفواتير المرتبط بطريقة الدفع الخاصة بك",
sameAsShipping: "نفس عنوان الشحن",
comments: "تعليقات",
commentsPlaceholder: "أضف أي تعليقات إضافية",
submit: "إرسال",
cancel: "إلغاء",
},
he: {
dir: "rtl" as const,
locale: "he-IL",
paymentMethod: "אמצעי תשלום",
secureEncrypted: "כל העסקאות מאובטחות ומוצפנות",
nameOnCard: "שם על הכרטיס",
namePlaceholder: "ישראל ישראלי",
cardNumber: "מספר כרטיס",
cardDescription: "הזן את המספר בן 16 הספרות שלך.",
cvv: "קוד אבטחה",
month: "חודש",
year: "שנה",
billingAddress: "כתובת לחיוב",
billingDescription: "כתובת החיוב המשויכת לאמצעי התשלום שלך",
sameAsShipping: "זהה לכתובת המשלוח",
comments: "הערות",
commentsPlaceholder: "הוסף הערות נוספות",
submit: "שלח",
cancel: "ביטול",
},
}
function formatCardNumber(locale: string) {
const formatter = new Intl.NumberFormat(locale, { useGrouping: false })
return `${formatter.format(1234)} ${formatter.format(5678)} ${formatter.format(9012)} ${formatter.format(3456)}`
}
function formatCvv(locale: string) {
return new Intl.NumberFormat(locale, { useGrouping: false }).format(123)
}
function getMonths(locale: string) {
const formatter = new Intl.NumberFormat(locale, {
minimumIntegerDigits: 2,
useGrouping: false,
})
return Array.from({ length: 12 }, (_, i) => {
const value = String(i + 1).padStart(2, "0")
return { label: formatter.format(i + 1), value }
})
}
function getYears(locale: string) {
const formatter = new Intl.NumberFormat(locale, { useGrouping: false })
return Array.from({ length: 6 }, (_, i) => {
const year = 2024 + i
return { label: formatter.format(year), value: String(year) }
})
}
export function FieldDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const months = getMonths(t.locale)
const years = getYears(t.locale)
const cardPlaceholder = formatCardNumber(t.locale)
const cvvPlaceholder = formatCvv(t.locale)
return (
<div dir={t.dir} className="w-full max-w-md rounded-lg border p-6">
<form>
<FieldGroup>
<FieldSet>
<FieldLegend>{t.paymentMethod}</FieldLegend>
<FieldDescription>{t.secureEncrypted}</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="rtl-card-name">{t.nameOnCard}</FieldLabel>
<Input
id="rtl-card-name"
placeholder={t.namePlaceholder}
required
/>
</Field>
<div className="grid grid-cols-3 gap-4">
<Field className="col-span-2">
<FieldLabel htmlFor="rtl-card-number">
{t.cardNumber}
</FieldLabel>
<Input
id="rtl-card-number"
placeholder={cardPlaceholder}
required
/>
<FieldDescription>{t.cardDescription}</FieldDescription>
</Field>
<Field className="col-span-1">
<FieldLabel htmlFor="rtl-cvv">{t.cvv}</FieldLabel>
<Input id="rtl-cvv" placeholder={cvvPlaceholder} required />
</Field>
</div>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="rtl-exp-month">{t.month}</FieldLabel>
<Select defaultValue="" items={months}>
<SelectTrigger id="rtl-exp-month">
<SelectValue placeholder="MM" />
</SelectTrigger>
<SelectContent data-lang={lang} dir={t.dir}>
<SelectGroup>
{months.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="rtl-exp-year">{t.year}</FieldLabel>
<Select defaultValue="" items={years}>
<SelectTrigger id="rtl-exp-year">
<SelectValue placeholder="YYYY" />
</SelectTrigger>
<SelectContent data-lang={lang} dir={t.dir}>
<SelectGroup>
{years.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLegend>{t.billingAddress}</FieldLegend>
<FieldDescription>{t.billingDescription}</FieldDescription>
<FieldGroup>
<Field orientation="horizontal">
<Checkbox id="rtl-same-as-shipping" defaultChecked />
<FieldLabel
htmlFor="rtl-same-as-shipping"
className="font-normal"
>
{t.sameAsShipping}
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldGroup>
<Field>
<FieldLabel htmlFor="rtl-comments">{t.comments}</FieldLabel>
<Textarea
id="rtl-comments"
placeholder={t.commentsPlaceholder}
className="resize-none"
/>
</Field>
</FieldGroup>
</FieldSet>
<Field orientation="horizontal">
<Button type="submit">{t.submit}</Button>
<Button variant="outline" type="button">
{t.cancel}
</Button>
</Field>
</FieldGroup>
</form>
</div>
)
}

View File

@@ -1,90 +0,0 @@
"use client"
import { Card, CardContent } from "@/examples/base/ui-rtl/card"
import { Checkbox } from "@/examples/base/ui-rtl/checkbox"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/examples/base/ui-rtl/field"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
legend: "كيف سمعت عنا؟",
description: "اختر الخيار الذي يصف أفضل طريقة سمعت عنا من خلالها.",
socialMedia: "التواصل الاجتماعي",
searchEngine: "البحث",
referral: "إحالة",
other: "أخرى",
},
he: {
dir: "rtl" as const,
legend: "איך שמעת עלינו?",
description: "בחר את האפשרות שמתארת בצורה הטובה ביותר כיצד שמעת עלינו.",
socialMedia: "חברתיות",
searchEngine: "חיפוש",
referral: "הפניה",
other: "אחר",
},
}
export function FieldHear() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const options = [
{ label: t.socialMedia, value: "social-media" },
{ label: t.searchEngine, value: "search-engine" },
{ label: t.referral, value: "referral" },
{ label: t.other, value: "other" },
]
return (
<div dir={t.dir}>
<Card className="border-0 py-4 shadow-none">
<CardContent className="px-4">
<form>
<FieldGroup>
<FieldSet className="gap-4">
<FieldLegend>{t.legend}</FieldLegend>
<FieldDescription className="line-clamp-1">
{t.description}
</FieldDescription>
<FieldGroup className="flex flex-row flex-wrap gap-2 [--radius:9999rem]">
{options.map((option) => (
<FieldLabel
htmlFor={`rtl-${option.value}`}
key={option.value}
className="!w-fit"
>
<Field
orientation="horizontal"
className="gap-1.5 overflow-hidden px-3! py-1.5! transition-all duration-100 ease-linear group-has-data-[state=checked]/field-label:px-2!"
>
<Checkbox
value={option.value}
id={`rtl-${option.value}`}
defaultChecked={option.value === "social-media"}
className="-ms-6 translate-x-1 rounded-full transition-all duration-100 ease-linear data-checked:ms-0 data-checked:translate-x-0"
/>
<FieldTitle>{option.label}</FieldTitle>
</Field>
</FieldLabel>
))}
</FieldGroup>
</FieldSet>
</FieldGroup>
</form>
</CardContent>
</Card>
</div>
)
}

View File

@@ -1,67 +0,0 @@
"use client"
import { useState } from "react"
import {
Field,
FieldDescription,
FieldTitle,
} from "@/examples/base/ui-rtl/field"
import { Slider } from "@/examples/base/ui-rtl/slider"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
locale: "ar-SA",
title: "نطاق السعر",
description: "حدد نطاق ميزانيتك",
ariaLabel: "نطاق السعر",
currency: "﷼",
},
he: {
dir: "rtl" as const,
locale: "he-IL",
title: "טווח מחירים",
description: "הגדר את טווח התקציב שלך",
ariaLabel: "טווח מחירים",
currency: "₪",
},
}
function formatNumber(value: number, locale: string) {
return new Intl.NumberFormat(locale).format(value)
}
export function FieldSlider() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [value, setValue] = useState([200, 800])
return (
<Field dir={t.dir}>
<FieldTitle>{t.title}</FieldTitle>
<FieldDescription>
{t.description} ({t.currency}
<span className="font-medium tabular-nums">
{formatNumber(value[0], t.locale)}
</span>{" "}
-{" "}
<span className="font-medium tabular-nums">
{formatNumber(value[1], t.locale)}
</span>
).
</FieldDescription>
<Slider
value={value}
onValueChange={(value) => setValue(value as [number, number])}
max={1000}
min={0}
step={10}
className="mt-2 w-full"
aria-label={t.ariaLabel}
/>
</Field>
)
}

View File

@@ -1,92 +0,0 @@
"use client"
import { DirectionProvider } from "@/examples/base/ui-rtl/direction"
import { FieldSeparator } from "@/examples/base/ui-rtl/field"
import {
LanguageProvider,
LanguageSelector,
useLanguageContext,
} from "@/components/language-selector"
import { AppearanceSettings } from "./appearance-settings"
import { ButtonGroupDemo } from "./button-group-demo"
import { ButtonGroupInputGroup } from "./button-group-input-group"
import { ButtonGroupNested } from "./button-group-nested"
import { ButtonGroupPopover } from "./button-group-popover"
import { EmptyAvatarGroup } from "./empty-avatar-group"
import { FieldCheckbox } from "./field-checkbox"
import { FieldDemo } from "./field-demo"
import { FieldHear } from "./field-hear"
import { FieldSlider } from "./field-slider"
import { InputGroupButtonExample } from "./input-group-button"
import { InputGroupDemo } from "./input-group-demo"
import { ItemDemo } from "./item-demo"
import { NotionPromptForm } from "./notion-prompt-form"
import { SpinnerBadge } from "./spinner-badge"
import { SpinnerEmpty } from "./spinner-empty"
function RtlComponentsContent() {
const context = useLanguageContext()
if (!context) {
return null
}
const { language } = context
return (
<div
className="relative grid gap-8 p-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-6 2xl:gap-8"
dir="rtl"
data-lang={language}
data-slot="rtl-components"
>
<LanguageSelector
value={language}
onValueChange={context.setLanguage}
className="absolute -top-12 right-52 hidden h-8! data-[size=sm]:rounded-lg lg:flex"
languages={["ar", "he"]}
/>
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<FieldDemo />
</div>
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<EmptyAvatarGroup />
<SpinnerBadge />
<ButtonGroupInputGroup />
<FieldSlider />
<InputGroupDemo />
</div>
<div className="flex flex-col gap-6 *:[div]:w-full *:[div]:max-w-full">
<InputGroupButtonExample />
<ItemDemo />
<FieldSeparator className="my-4">
{language === "he" ? "הגדרות מראה" : "إعدادات المظهر"}
</FieldSeparator>
<AppearanceSettings />
</div>
<div className="order-first flex flex-col gap-6 lg:hidden xl:order-last xl:flex *:[div]:w-full *:[div]:max-w-full">
<NotionPromptForm />
<ButtonGroupDemo />
<FieldCheckbox />
<div className="flex justify-between gap-4">
<ButtonGroupNested />
<ButtonGroupPopover />
</div>
<FieldHear />
<SpinnerEmpty />
</div>
</div>
)
}
export function RtlComponents() {
return (
<LanguageProvider defaultLanguage="ar">
<DirectionProvider direction="rtl">
<RtlComponentsContent />
</DirectionProvider>
</LanguageProvider>
)
}

View File

@@ -1,97 +0,0 @@
"use client"
import * as React from "react"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/examples/base/ui-rtl/input-group"
import { Label } from "@/examples/base/ui-rtl/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/examples/base/ui-rtl/popover"
import { IconInfoCircle, IconStar } from "@tabler/icons-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
inputLabel: "السعر",
info: "معلومات",
priceInfo: "أدخل السعر بالريال السعودي.",
priceDescription: "سيتم تحويل السعر تلقائياً.",
favorite: "مفضل",
currency: "ر.س",
},
he: {
dir: "rtl" as const,
inputLabel: "מחיר",
info: "מידע",
priceInfo: "הזן את המחיר בשקלים.",
priceDescription: "המחיר יומר אוטומטית.",
favorite: "מועדף",
currency: "₪",
},
}
export function InputGroupButtonExample() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const [isFavorite, setIsFavorite] = React.useState(false)
return (
<div dir={t.dir} className="grid w-full max-w-sm gap-6">
<Label htmlFor="input-secure-rtl" className="sr-only">
{t.inputLabel}
</Label>
<InputGroup className="[--radius:9999px]">
<InputGroupInput id="input-secure-rtl" className="!pr-0.5" />
<InputGroupAddon>
<Popover>
<PopoverTrigger
render={
<InputGroupButton
variant="secondary"
size="icon-xs"
aria-label={t.info}
/>
}
>
<IconInfoCircle />
</PopoverTrigger>
<PopoverContent
align="end"
alignOffset={10}
className="flex flex-col gap-1 rounded-xl text-sm"
data-lang={lang}
dir={t.dir}
>
<p className="font-medium">{t.priceInfo}</p>
<p>{t.priceDescription}</p>
</PopoverContent>
</Popover>
</InputGroupAddon>
<InputGroupAddon className="text-muted-foreground">
{t.currency}
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton
onClick={() => setIsFavorite(!isFavorite)}
size="icon-xs"
aria-label={t.favorite}
>
<IconStar
data-favorite={isFavorite}
className="data-[favorite=true]:fill-primary data-[favorite=true]:stroke-primary"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,140 +0,0 @@
"use client"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/examples/base/ui-rtl/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/base/ui-rtl/input-group"
import { Separator } from "@/examples/base/ui-rtl/separator"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui-rtl/tooltip"
import {
IconCheck,
IconChevronDown,
IconInfoCircle,
IconPlus,
} from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
search: "بحث...",
results: "12 نتيجة",
example: "example.com",
tooltipContent: "هذا محتوى في تلميح.",
askSearchChat: "اسأل، ابحث أو تحدث...",
add: "إضافة",
auto: "تلقائي",
agent: "وكيل",
manual: "يدوي",
used: "52% مستخدم",
send: "إرسال",
},
he: {
dir: "rtl" as const,
search: "חיפוש...",
results: "12 תוצאות",
example: "example.com",
tooltipContent: "זה תוכן בטולטיפ.",
askSearchChat: "שאל, חפש או שוחח...",
add: "הוסף",
auto: "אוטומטי",
agent: "סוכן",
manual: "ידני",
used: "52% בשימוש",
send: "שלח",
},
}
export function InputGroupDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<div dir={t.dir} className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder={t.search} />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">{t.results}</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder={t.example} />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
className="rounded-full"
size="icon-xs"
aria-label={t.add}
/>
}
>
<IconInfoCircle />
</TooltipTrigger>
<TooltipContent>{t.tooltipContent}</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder={t.askSearchChat} />
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
className="rounded-full"
size="icon-xs"
aria-label={t.add}
>
<IconPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger render={<InputGroupButton variant="ghost" />}>
<IconChevronDown />
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuItem>{t.auto}</DropdownMenuItem>
<DropdownMenuItem>{t.agent}</DropdownMenuItem>
<DropdownMenuItem>{t.manual}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText className="ms-auto">{t.used}</InputGroupText>
<Separator orientation="vertical" className="!h-4" />
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
>
<ArrowUpIcon />
<span className="sr-only">{t.send}</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="shadcn" />
<InputGroupAddon align="inline-end">
<div className="bg-primary text-foreground flex size-4 items-center justify-center rounded-full">
<IconCheck className="size-3 text-white" />
</div>
</InputGroupAddon>
</InputGroup>
</div>
)
}

View File

@@ -1,64 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/examples/base/ui-rtl/item"
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
twoFactor: "المصادقة الثنائية",
twoFactorDescription: "التحقق عبر البريد الإلكتروني أو رقم الهاتف.",
enable: "تفعيل",
verified: "تم التحقق من ملفك الشخصي.",
},
he: {
dir: "rtl" as const,
twoFactor: "אימות דו-שלבי",
twoFactorDescription: "אמת באמצעות אימייל או מספר טלפון.",
enable: "הפעל",
verified: "הפרופיל שלך אומת.",
},
}
export function ItemDemo() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<div dir={t.dir} className="flex w-full max-w-md flex-col gap-6">
<Item variant="outline">
<ItemContent>
<ItemTitle>{t.twoFactor}</ItemTitle>
<ItemDescription className="text-pretty xl:hidden 2xl:block">
{t.twoFactorDescription}
</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm">{t.enable}</Button>
</ItemActions>
</Item>
<Item variant="outline" size="sm">
<ItemMedia>
<BadgeCheckIcon className="size-5" />
</ItemMedia>
<ItemContent>
<ItemTitle>{t.verified}</ItemTitle>
</ItemContent>
<ItemActions>
<ChevronRightIcon className="size-4 rtl:rotate-180" />
</ItemActions>
</Item>
</div>
)
}

View File

@@ -1,516 +0,0 @@
"use client"
import { useMemo, useState } from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/examples/base/ui-rtl/avatar"
import { Badge } from "@/examples/base/ui-rtl/badge"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/examples/base/ui-rtl/command"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/examples/base/ui-rtl/dropdown-menu"
import { Field, FieldLabel } from "@/examples/base/ui-rtl/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/examples/base/ui-rtl/input-group"
import { Popover, PopoverContent } from "@/examples/base/ui-rtl/popover"
import { Switch } from "@/examples/base/ui-rtl/switch"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui-rtl/tooltip"
import {
IconApps,
IconArrowUp,
IconAt,
IconBook,
IconCircleDashedPlus,
IconPaperclip,
IconPlus,
IconWorld,
IconX,
} from "@tabler/icons-react"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
prompt: "الأمر",
placeholder: "اسأل، ابحث، أو أنشئ أي شيء...",
addContext: "أضف سياق",
mentionTooltip: "اذكر شخصًا أو صفحة أو تاريخًا",
searchPages: "البحث في الصفحات...",
noPagesFound: "لم يتم العثور على صفحات",
pages: "الصفحات",
users: "المستخدمون",
attachFile: "إرفاق ملف",
selectModel: "اختر نموذج الذكاء الاصطناعي",
selectAgentMode: "اختر وضع الوكيل",
webSearch: "البحث على الويب",
appsIntegrations: "التطبيقات والتكاملات",
allSourcesAccess: "جميع المصادر التي يمكنني الوصول إليها",
findKnowledge: "ابحث أو استخدم المعرفة في...",
noKnowledgeFound: "لم يتم العثور على معرفة",
helpCenter: "مركز المساعدة",
connectApps: "ربط التطبيقات",
searchSourcesNote: "سنبحث فقط في المصادر المحددة هنا.",
send: "إرسال",
allSources: "جميع المصادر",
auto: "تلقائي",
agentMode: "وضع الوكيل",
planMode: "وضع التخطيط",
beta: "تجريبي",
workspace: "مساحة العمل",
meetingNotes: "ملاحظات الاجتماع",
projectDashboard: "لوحة المشروع",
ideasBrainstorming: "أفكار وعصف ذهني",
calendarEvents: "التقويم والأحداث",
documentation: "التوثيق",
goalsObjectives: "الأهداف والغايات",
budgetPlanning: "تخطيط الميزانية",
teamDirectory: "دليل الفريق",
technicalSpecs: "المواصفات التقنية",
analyticsReport: "تقرير التحليلات",
},
he: {
dir: "rtl" as const,
prompt: "פקודה",
placeholder: "שאל, חפש, או צור משהו...",
addContext: "הוסף הקשר",
mentionTooltip: "הזכר אדם, עמוד או תאריך",
searchPages: "חפש עמודים...",
noPagesFound: "לא נמצאו עמודים",
pages: "עמודים",
users: "משתמשים",
attachFile: "צרף קובץ",
selectModel: "בחר מודל AI",
selectAgentMode: "בחר מצב סוכן",
webSearch: "חיפוש באינטרנט",
appsIntegrations: "אפליקציות ואינטגרציות",
allSourcesAccess: "כל המקורות שיש לי גישה אליהם",
findKnowledge: "מצא או השתמש בידע ב...",
noKnowledgeFound: "לא נמצא ידע",
helpCenter: "מרכז עזרה",
connectApps: "חבר אפליקציות",
searchSourcesNote: "נחפש רק במקורות שנבחרו כאן.",
send: "שלח",
allSources: "כל המקורות",
auto: "אוטומטי",
agentMode: "מצב סוכן",
planMode: "מצב תכנון",
beta: "בטא",
workspace: "סביבת עבודה",
meetingNotes: "הערות פגישה",
projectDashboard: "לוח מחוונים לפרויקט",
ideasBrainstorming: "רעיונות וסיעור מוחות",
calendarEvents: "יומן ואירועים",
documentation: "תיעוד",
goalsObjectives: "מטרות ויעדים",
budgetPlanning: "תכנון תקציב",
teamDirectory: "ספריית צוות",
technicalSpecs: "מפרט טכני",
analyticsReport: "דוח אנליטיקה",
},
}
function MentionableIcon({
item,
}: {
item: { type: string; title: string; image: string }
}) {
return item.type === "page" ? (
<span className="flex size-4 items-center justify-center">
{item.image}
</span>
) : (
<Avatar className="size-4">
<AvatarImage src={item.image} />
<AvatarFallback>{item.title[0]}</AvatarFallback>
</Avatar>
)
}
export function NotionPromptForm() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
const SAMPLE_DATA = useMemo(
() => ({
mentionable: [
{ type: "page", title: t.meetingNotes, image: "📝" },
{ type: "page", title: t.projectDashboard, image: "📊" },
{ type: "page", title: t.ideasBrainstorming, image: "💡" },
{ type: "page", title: t.calendarEvents, image: "📅" },
{ type: "page", title: t.documentation, image: "📚" },
{ type: "page", title: t.goalsObjectives, image: "🎯" },
{ type: "page", title: t.budgetPlanning, image: "💰" },
{ type: "page", title: t.teamDirectory, image: "👥" },
{ type: "page", title: t.technicalSpecs, image: "🔧" },
{ type: "page", title: t.analyticsReport, image: "📈" },
{
type: "user",
title: "shadcn",
image: "https://github.com/shadcn.png",
workspace: t.workspace,
},
{
type: "user",
title: "maxleiter",
image: "https://github.com/maxleiter.png",
workspace: t.workspace,
},
{
type: "user",
title: "evilrabbit",
image: "https://github.com/evilrabbit.png",
workspace: t.workspace,
},
],
models: [
{ name: t.auto },
{ name: t.agentMode, badge: t.beta },
{ name: t.planMode },
],
}),
[t]
)
const [mentions, setMentions] = useState<string[]>([])
const [mentionPopoverOpen, setMentionPopoverOpen] = useState(false)
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
const [selectedModel, setSelectedModel] = useState<
(typeof SAMPLE_DATA.models)[0]
>(SAMPLE_DATA.models[0])
const [scopeMenuOpen, setScopeMenuOpen] = useState(false)
const grouped = useMemo(() => {
return SAMPLE_DATA.mentionable.reduce(
(acc, item) => {
const isAvailable = !mentions.includes(item.title)
if (isAvailable) {
if (!acc[item.type]) {
acc[item.type] = []
}
acc[item.type].push(item)
}
return acc
},
{} as Record<string, typeof SAMPLE_DATA.mentionable>
)
}, [mentions, SAMPLE_DATA])
const hasMentions = mentions.length > 0
return (
<div dir={t.dir}>
<form>
<Field>
<FieldLabel htmlFor="rtl-notion-prompt" className="sr-only">
{t.prompt}
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="rtl-notion-prompt"
placeholder={t.placeholder}
/>
<InputGroupAddon align="block-start">
<Popover
open={mentionPopoverOpen}
onOpenChange={setMentionPopoverOpen}
>
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
variant="outline"
size={!hasMentions ? "sm" : "icon-sm"}
className="rounded-full transition-transform"
/>
}
onFocusCapture={(e) => e.stopPropagation()}
>
<IconAt /> {!hasMentions && t.addContext}
</TooltipTrigger>
<TooltipContent>{t.mentionTooltip}</TooltipContent>
</Tooltip>
<PopoverContent className="p-0" align="start" dir={t.dir}>
<Command>
<CommandInput placeholder={t.searchPages} />
<CommandList>
<CommandEmpty>{t.noPagesFound}</CommandEmpty>
{Object.entries(grouped).map(([type, items]) => (
<CommandGroup
key={type}
heading={type === "page" ? t.pages : t.users}
>
{items.map((item) => (
<CommandItem
key={item.title}
value={item.title}
onSelect={(currentValue) => {
setMentions((prev) => [...prev, currentValue])
setMentionPopoverOpen(false)
}}
>
<MentionableIcon item={item} />
{item.title}
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
<div className="no-scrollbar -m-1.5 flex gap-1 overflow-y-auto p-1.5">
{mentions.map((mention) => {
const item = SAMPLE_DATA.mentionable.find(
(item) => item.title === mention
)
if (!item) {
return null
}
return (
<InputGroupButton
key={mention}
size="sm"
variant="secondary"
className="rounded-full !pr-2"
onClick={() => {
setMentions((prev) => prev.filter((m) => m !== mention))
}}
>
<MentionableIcon item={item} />
{item.title}
<IconX />
</InputGroupButton>
)
})}
</div>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="gap-1">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton
size="icon-sm"
className="rounded-full"
aria-label={t.attachFile}
/>
}
>
<IconPaperclip />
</TooltipTrigger>
<TooltipContent>{t.attachFile}</TooltipContent>
</Tooltip>
<DropdownMenu
open={modelPopoverOpen}
onOpenChange={setModelPopoverOpen}
>
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton size="sm" className="rounded-full" />
}
>
{selectedModel.name}
</TooltipTrigger>
<TooltipContent>{t.selectModel}</TooltipContent>
</Tooltip>
<DropdownMenuContent
side="top"
align="start"
className="w-48"
dir={t.dir}
>
<DropdownMenuGroup className="w-48">
<DropdownMenuLabel className="text-muted-foreground text-xs">
{t.selectAgentMode}
</DropdownMenuLabel>
{SAMPLE_DATA.models.map((model) => (
<DropdownMenuCheckboxItem
key={model.name}
checked={model.name === selectedModel.name}
onCheckedChange={(checked) => {
if (checked) {
setSelectedModel(model)
}
}}
className="pr-2 *:[span:first-child]:right-auto *:[span:first-child]:left-2"
>
{model.name}
{model.badge && (
<Badge
variant="secondary"
className="h-5 rounded-sm bg-blue-100 px-1 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-100"
>
{model.badge}
</Badge>
)}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu
open={scopeMenuOpen}
onOpenChange={setScopeMenuOpen}
>
<DropdownMenuTrigger
render={
<InputGroupButton size="sm" className="rounded-full" />
}
>
<IconWorld /> {t.allSources}
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="end"
className="w-72"
dir={t.dir}
>
<DropdownMenuGroup>
<DropdownMenuItem
render={
<label htmlFor="rtl-web-search">
<IconWorld /> {t.webSearch}{" "}
<Switch
id="rtl-web-search"
className="ms-auto"
defaultChecked
size="sm"
/>
</label>
}
onSelect={(e) => e.preventDefault()}
></DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
render={
<label htmlFor="rtl-apps">
<IconApps /> {t.appsIntegrations}
<Switch
id="rtl-apps"
className="ms-auto"
defaultChecked
size="sm"
/>
</label>
}
onSelect={(e) => e.preventDefault()}
></DropdownMenuItem>
<DropdownMenuItem>
<IconCircleDashedPlus /> {t.allSourcesAccess}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Avatar className="size-4">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
shadcn
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
className="w-72 rounded-lg p-0"
dir={t.dir}
side="left"
>
<Command>
<CommandInput
placeholder={t.findKnowledge}
autoFocus
/>
<CommandList>
<CommandEmpty>{t.noKnowledgeFound}</CommandEmpty>
<CommandGroup>
{SAMPLE_DATA.mentionable
.filter((item) => item.type === "user")
.map((user) => (
<CommandItem
key={user.title}
value={user.title}
onSelect={() => {
console.log("Selected user:", user.title)
}}
>
<Avatar className="size-4">
<AvatarImage src={user.image} />
<AvatarFallback>
{user.title[0]}
</AvatarFallback>
</Avatar>
{user.title}{" "}
<span className="text-muted-foreground">
-{" "}
{
(user as { workspace?: string })
.workspace
}
</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuItem>
<IconBook /> {t.helpCenter}
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlus /> {t.connectApps}
</DropdownMenuItem>
<DropdownMenuLabel className="text-muted-foreground text-xs">
{t.searchSourcesNote}
</DropdownMenuLabel>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupButton
aria-label={t.send}
className="ms-auto rounded-full"
variant="default"
size="icon-sm"
>
<IconArrowUp />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</form>
</div>
)
}

View File

@@ -1,44 +0,0 @@
"use client"
import { Badge } from "@/examples/base/ui-rtl/badge"
import { Spinner } from "@/examples/base/ui-rtl/spinner"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
syncing: "جارٍ المزامنة",
updating: "جارٍ التحديث",
loading: "جارٍ التحميل",
},
he: {
dir: "rtl" as const,
syncing: "מסנכרן",
updating: "מעדכן",
loading: "טוען",
},
}
export function SpinnerBadge() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<div dir={t.dir} className="flex items-center gap-2">
<Badge>
<Spinner />
{t.syncing}
</Badge>
<Badge variant="secondary">
<Spinner />
{t.updating}
</Badge>
<Badge variant="outline">
<Spinner />
{t.loading}
</Badge>
</div>
)
}

View File

@@ -1,52 +0,0 @@
"use client"
import { Button } from "@/examples/base/ui-rtl/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/examples/base/ui-rtl/empty"
import { Spinner } from "@/examples/base/ui-rtl/spinner"
import { useLanguageContext } from "@/components/language-selector"
const translations = {
ar: {
dir: "rtl" as const,
title: "جارٍ معالجة طلبك",
description: "يرجى الانتظار بينما نعالج طلبك. لا تقم بتحديث الصفحة.",
cancel: "إلغاء",
},
he: {
dir: "rtl" as const,
title: "מעבד את הבקשה שלך",
description: "אנא המתן בזמן שאנו מעבדים את בקשתך. אל תרענן את הדף.",
cancel: "ביטול",
},
}
export function SpinnerEmpty() {
const context = useLanguageContext()
const lang = context?.language === "he" ? "he" : "ar"
const t = translations[lang]
return (
<Empty className="w-full border md:p-6" dir={t.dir}>
<EmptyHeader>
<EmptyMedia variant="icon">
<Spinner />
</EmptyMedia>
<EmptyTitle>{t.title}</EmptyTitle>
<EmptyDescription>{t.description}</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline" size="sm">
{t.cancel}
</Button>
</EmptyContent>
</Empty>
)
}

View File

@@ -1,14 +0,0 @@
import { type Metadata } from "next"
import { RtlComponents } from "./components"
export const metadata: Metadata = {
title: "RTL",
description: "RTL example page with right-to-left language support.",
}
export function RtlPage() {
return <RtlComponents />
}
export default RtlPage

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
import { type Table } from "@tanstack/react-table" import { type Table } from "@tanstack/react-table"
import { Settings2 } from "lucide-react" import { Settings2 } from "lucide-react"
@@ -10,7 +11,6 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu" } from "@/registry/new-york-v4/ui/dropdown-menu"
export function DataTableViewOptions<TData>({ export function DataTableViewOptions<TData>({

View File

@@ -3,23 +3,10 @@ import { NextResponse, type NextRequest } from "next/server"
import { processMdxForLLMs } from "@/lib/llm" import { processMdxForLLMs } from "@/lib/llm"
import { source } from "@/lib/source" import { source } from "@/lib/source"
import { getActiveStyle, type Style } from "@/registry/_legacy-styles" import { getActiveStyle } from "@/registry/_legacy-styles"
export const revalidate = false export const revalidate = false
function getStyleFromSlug(slug: string[] | undefined, fallbackStyle: string) {
// Detect base from URL: /docs/components/base/... or /docs/components/radix/...
if (slug && slug[0] === "components" && slug[1]) {
if (slug[1] === "base") {
return "base-nova"
}
if (slug[1] === "radix") {
return "new-york-v4"
}
}
return fallbackStyle
}
export async function GET( export async function GET(
_req: NextRequest, _req: NextRequest,
{ params }: { params: Promise<{ slug?: string[] }> } { params }: { params: Promise<{ slug?: string[] }> }
@@ -32,11 +19,9 @@ export async function GET(
notFound() notFound()
} }
const effectiveStyle = getStyleFromSlug(slug, activeStyle.name)
const processedContent = processMdxForLLMs( const processedContent = processMdxForLLMs(
await page.data.getText("raw"), await page.data.getText("raw"),
effectiveStyle as Style["name"] activeStyle.name
) )
return new NextResponse(processedContent, { return new NextResponse(processedContent, {

View File

@@ -2,11 +2,13 @@
import * as React from "react" import * as React from "react"
import Script from "next/script" import Script from "next/script"
import { DiceFaces05Icon } from "@hugeicons/core-free-icons" import { DiceFaces05Icon, Undo02Icon } from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react" import { HugeiconsIcon } from "@hugeicons/react"
import { cn } from "@/lib/utils"
import { import {
BASE_COLORS, BASE_COLORS,
DEFAULT_CONFIG,
getThemesForBaseColor, getThemesForBaseColor,
iconLibraries, iconLibraries,
MENU_ACCENTS, MENU_ACCENTS,
@@ -16,11 +18,6 @@ import {
} from "@/registry/config" } from "@/registry/config"
import { Button } from "@/registry/new-york-v4/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
import { Kbd } from "@/registry/new-york-v4/ui/kbd" import { Kbd } from "@/registry/new-york-v4/ui/kbd"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
import { useLocks } from "@/app/(create)/hooks/use-locks" import { useLocks } from "@/app/(create)/hooks/use-locks"
import { FONTS } from "@/app/(create)/lib/fonts" import { FONTS } from "@/app/(create)/lib/fonts"
import { import {
@@ -36,10 +33,26 @@ function randomItem<T>(array: readonly T[]): T {
return array[Math.floor(Math.random() * array.length)] return array[Math.floor(Math.random() * array.length)]
} }
export function RandomButton() { export function CustomizerControls({ className }: { className?: string }) {
const { locks } = useLocks() const { locks } = useLocks()
const [params, setParams] = useDesignSystemSearchParams() const [params, setParams] = useDesignSystemSearchParams()
const handleReset = React.useCallback(() => {
setParams({
base: params.base, // Keep the current base value
style: DEFAULT_CONFIG.style,
baseColor: DEFAULT_CONFIG.baseColor,
theme: DEFAULT_CONFIG.theme,
iconLibrary: DEFAULT_CONFIG.iconLibrary,
font: DEFAULT_CONFIG.font,
menuAccent: DEFAULT_CONFIG.menuAccent,
menuColor: DEFAULT_CONFIG.menuColor,
radius: DEFAULT_CONFIG.radius,
template: DEFAULT_CONFIG.template,
item: "preview",
})
}, [setParams, params.base])
const handleRandomize = React.useCallback(() => { const handleRandomize = React.useCallback(() => {
// Use current value if locked, otherwise randomize. // Use current value if locked, otherwise randomize.
const baseColor = locks.has("baseColor") const baseColor = locks.has("baseColor")
@@ -117,30 +130,33 @@ export function RandomButton() {
}, [handleRandomize]) }, [handleRandomize])
return ( return (
<Tooltip> <div className={cn("items-center gap-0", className)}>
<TooltipTrigger asChild> <Button
<Button variant="ghost"
variant="ghost" size="sm"
size="sm" onClick={handleRandomize}
onClick={handleRandomize} className="border-foreground/10 bg-muted/50 h-[calc(--spacing(13.5))] w-[140px] touch-manipulation justify-between rounded-xl border select-none focus-visible:border-transparent focus-visible:ring-1 sm:rounded-lg md:w-full md:rounded-lg md:border-transparent md:bg-transparent md:pr-3.5! md:pl-2!"
className="border-foreground/10 bg-muted/50 h-[calc(--spacing(13.5))] w-[140px] touch-manipulation justify-between rounded-xl border select-none focus-visible:border-transparent focus-visible:ring-1 sm:rounded-lg md:w-full md:rounded-lg md:border-transparent md:bg-transparent md:pr-3.5! md:pl-2!" >
> <div className="flex flex-col justify-start text-left">
<div className="flex flex-col justify-start text-left"> <div className="text-muted-foreground text-xs">Shuffle</div>
<div className="text-muted-foreground text-xs">Shuffle</div> <div className="text-foreground text-sm font-medium">Try Random</div>
<div className="text-foreground text-sm font-medium"> </div>
Try Random <HugeiconsIcon icon={DiceFaces05Icon} className="size-5 md:hidden" />
</div> <Kbd className="bg-foreground/10 text-foreground hidden md:flex">R</Kbd>
</div> </Button>
<HugeiconsIcon icon={DiceFaces05Icon} className="size-5 md:hidden" /> <Button
<Kbd className="bg-foreground/10 text-foreground hidden md:flex"> variant="ghost"
R size="sm"
</Kbd> onClick={handleReset}
</Button> className="border-foreground/10 bg-muted/50 hidden h-[calc(--spacing(13.5))] w-[140px] touch-manipulation justify-between rounded-xl border select-none focus-visible:border-transparent focus-visible:ring-1 sm:rounded-lg md:flex md:w-full md:rounded-lg md:border-transparent md:bg-transparent md:pr-3.5! md:pl-2!"
</TooltipTrigger> >
<TooltipContent side="left"> <div className="flex flex-col justify-start text-left">
Use browser back/forward to navigate history <div className="text-muted-foreground text-xs">Reset</div>
</TooltipContent> <div className="text-foreground text-sm font-medium">Start Over</div>
</Tooltip> </div>
<HugeiconsIcon icon={Undo02Icon} className="-translate-x-0.5" />
</Button>
</div>
) )
} }

View File

@@ -7,17 +7,15 @@ import { HugeiconsIcon } from "@hugeicons/react"
import { useIsMobile } from "@/hooks/use-mobile" import { useIsMobile } from "@/hooks/use-mobile"
import { getThemesForBaseColor, PRESETS, STYLES } from "@/registry/config" import { getThemesForBaseColor, PRESETS, STYLES } from "@/registry/config"
import { FieldGroup } from "@/registry/new-york-v4/ui/field" import { FieldGroup } from "@/registry/new-york-v4/ui/field"
import { Separator } from "@/registry/new-york-v4/ui/separator"
import { MenuAccentPicker } from "@/app/(create)/components/accent-picker" import { MenuAccentPicker } from "@/app/(create)/components/accent-picker"
import { BaseColorPicker } from "@/app/(create)/components/base-color-picker" import { BaseColorPicker } from "@/app/(create)/components/base-color-picker"
import { BasePicker } from "@/app/(create)/components/base-picker" import { BasePicker } from "@/app/(create)/components/base-picker"
import { CustomizerControls } from "@/app/(create)/components/customizer-controls"
import { FontPicker } from "@/app/(create)/components/font-picker" import { FontPicker } from "@/app/(create)/components/font-picker"
import { IconLibraryPicker } from "@/app/(create)/components/icon-library-picker" import { IconLibraryPicker } from "@/app/(create)/components/icon-library-picker"
import { MenuColorPicker } from "@/app/(create)/components/menu-picker" import { MenuColorPicker } from "@/app/(create)/components/menu-picker"
import { PresetPicker } from "@/app/(create)/components/preset-picker" import { PresetPicker } from "@/app/(create)/components/preset-picker"
import { RadiusPicker } from "@/app/(create)/components/radius-picker" import { RadiusPicker } from "@/app/(create)/components/radius-picker"
import { RandomButton } from "@/app/(create)/components/random-button"
import { ResetButton } from "@/app/(create)/components/reset-button"
import { StylePicker } from "@/app/(create)/components/style-picker" import { StylePicker } from "@/app/(create)/components/style-picker"
import { ThemePicker } from "@/app/(create)/components/theme-picker" import { ThemePicker } from "@/app/(create)/components/theme-picker"
import { FONTS } from "@/app/(create)/lib/fonts" import { FONTS } from "@/app/(create)/lib/fonts"
@@ -38,6 +36,21 @@ export function Customizer() {
className="no-scrollbar -mx-2.5 flex flex-col overflow-y-auto p-1 md:mx-0 md:h-[calc(100svh-var(--header-height)-2rem)] md:w-48 md:gap-0 md:py-0" className="no-scrollbar -mx-2.5 flex flex-col overflow-y-auto p-1 md:mx-0 md:h-[calc(100svh-var(--header-height)-2rem)] md:w-48 md:gap-0 md:py-0"
ref={anchorRef} ref={anchorRef}
> >
<div className="hidden items-center gap-2 px-[calc(--spacing(2.5))] pb-1 md:flex md:flex-col md:items-start">
<HugeiconsIcon
icon={Settings05Icon}
className="size-4"
strokeWidth={2}
/>
<div className="relative flex flex-col gap-1 rounded-lg text-[13px]/snug">
<div className="flex items-center gap-1 font-medium text-balance">
Build your own shadcn/ui
</div>
<div className="hidden md:flex">
When you&apos;re done, click Create Project to start a new project.
</div>
</div>
</div>
<div className="no-scrollbar h-14 overflow-x-auto overflow-y-hidden p-px md:h-full md:overflow-x-hidden md:overflow-y-auto"> <div className="no-scrollbar h-14 overflow-x-auto overflow-y-hidden p-px md:h-full md:overflow-x-hidden md:overflow-y-auto">
<FieldGroup className="flex h-full flex-1 flex-row gap-2 md:flex-col md:gap-0"> <FieldGroup className="flex h-full flex-1 flex-row gap-2 md:flex-col md:gap-0">
<PresetPicker <PresetPicker
@@ -62,10 +75,7 @@ export function Customizer() {
<RadiusPicker isMobile={isMobile} anchorRef={anchorRef} /> <RadiusPicker isMobile={isMobile} anchorRef={anchorRef} />
<MenuColorPicker isMobile={isMobile} anchorRef={anchorRef} /> <MenuColorPicker isMobile={isMobile} anchorRef={anchorRef} />
<MenuAccentPicker isMobile={isMobile} anchorRef={anchorRef} /> <MenuAccentPicker isMobile={isMobile} anchorRef={anchorRef} />
<div className="mt-auto hidden w-full flex-col items-center gap-0 md:flex"> <CustomizerControls className="mt-auto hidden w-full flex-col md:flex" />
<RandomButton />
<ResetButton />
</div>
</FieldGroup> </FieldGroup>
</div> </div>
</div> </div>

View File

@@ -59,7 +59,7 @@ export function FontPicker({
anchor={isMobile ? anchorRef : undefined} anchor={isMobile ? anchorRef : undefined}
side={isMobile ? "top" : "right"} side={isMobile ? "top" : "right"}
align={isMobile ? "center" : "start"} align={isMobile ? "center" : "start"}
className="max-h-96 md:w-72" className="max-h-80 md:w-72"
> >
<PickerRadioGroup <PickerRadioGroup
value={currentFont?.value} value={currentFont?.value}

View File

@@ -45,12 +45,6 @@ const IconPhosphor = lazy(() =>
})) }))
) )
const IconRemixicon = lazy(() =>
import("@/registry/icons/icon-remixicon").then((mod) => ({
default: mod.IconRemixicon,
}))
)
const PREVIEW_ICONS = { const PREVIEW_ICONS = {
lucide: [ lucide: [
"CopyIcon", "CopyIcon",
@@ -116,22 +110,6 @@ const PREVIEW_ICONS = {
"CaretDownIcon", "CaretDownIcon",
"CaretRightIcon", "CaretRightIcon",
], ],
remixicon: [
"RiFileCopyLine",
"RiErrorWarningLine",
"RiDeleteBinLine",
"RiShareLine",
"RiShoppingBagLine",
"RiMoreLine",
"RiLoaderLine",
"RiAddLine",
"RiSubtractLine",
"RiArrowLeftLine",
"RiArrowRightLine",
"RiCheckLine",
"RiArrowDownSLine",
"RiArrowRightSLine",
],
} }
const logos = { const logos = {
@@ -216,17 +194,6 @@ const logos = {
/> />
</svg> </svg>
), ),
remixicon: (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="currentColor"
>
<path d="M12 2C17.5228 2 22 6.47715 22 12C22 15.3137 19.3137 18 16 18C12.6863 18 10 15.3137 10 12C10 11.4477 9.55228 11 9 11C8.44772 11 8 11.4477 8 12C8 16.4183 11.5817 20 16 20C16.8708 20 17.7084 19.8588 18.4932 19.6016C16.7458 21.0956 14.4792 22 12 22C6.6689 22 2.3127 17.8283 2.0166 12.5713C2.23647 9.45772 4.83048 7 8 7C11.3137 7 14 9.68629 14 13C14 13.5523 14.4477 14 15 14C15.5523 14 16 13.5523 16 13C16 8.58172 12.4183 5 8 5C6.50513 5 5.1062 5.41032 3.90918 6.12402C5.72712 3.62515 8.67334 2 12 2Z" />
</svg>
),
} }
export function IconLibraryPicker({ export function IconLibraryPicker({
@@ -334,9 +301,7 @@ const IconLibraryPreview = memo(function IconLibraryPreview({
? IconTabler ? IconTabler
: iconLibrary === "hugeicons" : iconLibrary === "hugeicons"
? IconHugeicons ? IconHugeicons
: iconLibrary === "phosphor" : IconPhosphor
? IconPhosphor
: IconRemixicon
return ( return (
<Suspense <Suspense

View File

@@ -30,12 +30,6 @@ const IconPhosphor = lazy(() =>
})) }))
) )
const IconRemixicon = lazy(() =>
import("@/registry/icons/icon-remixicon").then((mod) => ({
default: mod.IconRemixicon,
}))
)
export function IconPlaceholder({ export function IconPlaceholder({
...props ...props
}: { }: {
@@ -58,9 +52,6 @@ export function IconPlaceholder({
{iconLibrary === "phosphor" && ( {iconLibrary === "phosphor" && (
<IconPhosphor name={iconName} {...props} /> <IconPhosphor name={iconName} {...props} />
)} )}
{iconLibrary === "remixicon" && (
<IconRemixicon name={iconName} {...props} />
)}
</Suspense> </Suspense>
) )
} }

View File

@@ -79,7 +79,7 @@ export function ItemExplorer({
)} )}
<SidebarMenuButton <SidebarMenuButton
onClick={() => setParams({ item: item.name })} onClick={() => setParams({ item: item.name })}
className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[26px] w-fit cursor-pointer overflow-visible border border-transparent text-[0.8rem] font-normal after:absolute after:inset-x-0 after:-inset-y-1 after:z-0 after:rounded-md" className="data-[active=true]:bg-accent data-[active=true]:border-accent 3xl:fixed:w-full 3xl:fixed:max-w-48 relative h-[26px] w-fit cursor-pointer overflow-visible border border-transparent text-[0.8rem] font-normal after:absolute after:inset-x-0 after:-inset-y-1 after:-z-0 after:rounded-md"
data-active={item.name === currentItem?.name} data-active={item.name === currentItem?.name}
isActive={item.name === currentItem?.name} isActive={item.name === currentItem?.name}
> >

View File

@@ -99,7 +99,7 @@ export function ItemPicker({
variant="outline" variant="outline"
aria-label="Select item" aria-label="Select item"
size="sm" size="sm"
className="data-popup-open:bg-muted dark:data-popup-open:bg-muted/50 bg-muted/50 sm:bg-background md:dark:bg-background border-foreground/10 dark:bg-muted/50 h-[calc(--spacing(13.5))] flex-1 touch-manipulation justify-between gap-2 rounded-xl pr-4! pl-2.5 text-left shadow-none select-none *:data-[slot=combobox-trigger-icon]:hidden sm:h-8 sm:max-w-56 sm:rounded-lg sm:pr-2! xl:max-w-64" className="data-popup-open:bg-muted dark:data-popup-open:bg-muted/50 bg-muted/50 sm:bg-background md:dark:bg-background border-foreground/10 dark:bg-muted/50 h-[calc(--spacing(13.5))] flex-1 touch-manipulation justify-between gap-2 rounded-xl pr-4! pl-2.5 text-left shadow-none select-none *:data-[slot=combobox-trigger-icon]:hidden sm:h-8 sm:max-w-56 sm:rounded-lg sm:pr-2! xl:max-w-md"
/> />
} }
> >
@@ -123,9 +123,9 @@ export function ItemPicker({
<HugeiconsIcon icon={Search01Icon} /> <HugeiconsIcon icon={Search01Icon} />
</ComboboxTrigger> </ComboboxTrigger>
<ComboboxContent <ComboboxContent
className="ring-foreground/10 min-w-[calc(var(--available-width)---spacing(4))] translate-x-2 animate-none rounded-xl border-0 ring-1 data-open:animate-none sm:min-w-[calc(var(--anchor-width)+--spacing(7))] sm:translate-x-0 xl:w-96" className="ring-foreground/10 min-w-[calc(var(--available-width)---spacing(4))] translate-x-2 animate-none rounded-xl border-0 ring-1 data-open:animate-none sm:min-w-[calc(var(--anchor-width)+--spacing(7))] sm:translate-x-0"
side="bottom" side="bottom"
align="end" align="center"
> >
<ComboboxInput <ComboboxInput
showTrigger={false} showTrigger={false}

View File

@@ -80,7 +80,7 @@ function PickerLabel({
data-slot="dropdown-menu-label" data-slot="dropdown-menu-label"
data-inset={inset} data-inset={inset}
className={cn( className={cn(
"text-muted-foreground px-2 py-1.5 text-xs font-medium data-inset:pl-8", "text-muted-foreground px-2 py-1.5 text-xs font-medium data-[inset]:pl-8",
className className
)} )}
{...props} {...props}
@@ -103,7 +103,7 @@ function PickerItem({
data-inset={inset} data-inset={inset}
data-variant={variant} data-variant={variant}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}
@@ -128,7 +128,7 @@ function PickerSubTrigger({
data-slot="dropdown-menu-sub-trigger" data-slot="dropdown-menu-sub-trigger"
data-inset={inset} data-inset={inset}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}
@@ -139,7 +139,6 @@ function PickerSubTrigger({
tabler="IconChevronRight" tabler="IconChevronRight"
hugeicons="ArrowRight01Icon" hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon" phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="ml-auto" className="ml-auto"
/> />
</MenuPrimitive.SubmenuTrigger> </MenuPrimitive.SubmenuTrigger>
@@ -180,7 +179,7 @@ function PickerCheckboxItem({
<MenuPrimitive.CheckboxItem <MenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item" data-slot="dropdown-menu-checkbox-item"
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
checked={checked} checked={checked}
@@ -193,7 +192,6 @@ function PickerCheckboxItem({
tabler="IconCheck" tabler="IconCheck"
hugeicons="Tick02Icon" hugeicons="Tick02Icon"
phosphor="CheckIcon" phosphor="CheckIcon"
remixicon="RiCheckLine"
/> />
</MenuPrimitive.CheckboxItemIndicator> </MenuPrimitive.CheckboxItemIndicator>
</span> </span>
@@ -220,7 +218,7 @@ function PickerRadioItem({
<MenuPrimitive.RadioItem <MenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item" data-slot="dropdown-menu-radio-item"
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 pointer-coarse:gap-3 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 pointer-coarse:gap-3 pointer-coarse:py-2.5 pointer-coarse:pl-3 pointer-coarse:text-base [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}
@@ -235,7 +233,6 @@ function PickerRadioItem({
tabler="IconCheck" tabler="IconCheck"
hugeicons="Tick02Icon" hugeicons="Tick02Icon"
phosphor="CheckIcon" phosphor="CheckIcon"
remixicon="RiCheckLine"
className="size-4 pointer-coarse:size-5" className="size-4 pointer-coarse:size-5"
/> />
</MenuPrimitive.RadioItemIndicator> </MenuPrimitive.RadioItemIndicator>

View File

@@ -1,12 +1,12 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { type PanelImperativeHandle } from "react-resizable-panels" import { type ImperativePanelHandle } from "react-resizable-panels"
import { DARK_MODE_FORWARD_TYPE } from "@/components/mode-switcher" import { DARK_MODE_FORWARD_TYPE } from "@/components/mode-switcher"
import { Badge } from "@/registry/new-york-v4/ui/badge" import { Badge } from "@/registry/new-york-v4/ui/badge"
import { RANDOMIZE_FORWARD_TYPE } from "@/app/(create)/components/customizer-controls"
import { CMD_K_FORWARD_TYPE } from "@/app/(create)/components/item-picker" import { CMD_K_FORWARD_TYPE } from "@/app/(create)/components/item-picker"
import { RANDOMIZE_FORWARD_TYPE } from "@/app/(create)/components/random-button"
import { sendToIframe } from "@/app/(create)/hooks/use-iframe-sync" import { sendToIframe } from "@/app/(create)/hooks/use-iframe-sync"
import { import {
serializeDesignSystemSearchParams, serializeDesignSystemSearchParams,
@@ -16,7 +16,7 @@ import {
export function Preview() { export function Preview() {
const [params] = useDesignSystemSearchParams() const [params] = useDesignSystemSearchParams()
const iframeRef = React.useRef<HTMLIFrameElement>(null) const iframeRef = React.useRef<HTMLIFrameElement>(null)
const resizablePanelRef = React.useRef<PanelImperativeHandle>(null) const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
// Sync resizable panel with URL param changes. // Sync resizable panel with URL param changes.
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,75 +0,0 @@
"use client"
import * as React from "react"
import { Undo02Icon } from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react"
import { DEFAULT_CONFIG } from "@/registry/config"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/new-york-v4/ui/alert-dialog"
import { Button } from "@/registry/new-york-v4/ui/button"
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
export function ResetButton() {
const [params, setParams] = useDesignSystemSearchParams()
const handleReset = React.useCallback(() => {
setParams({
base: params.base, // Keep the current base value.
style: DEFAULT_CONFIG.style,
baseColor: DEFAULT_CONFIG.baseColor,
theme: DEFAULT_CONFIG.theme,
iconLibrary: DEFAULT_CONFIG.iconLibrary,
font: DEFAULT_CONFIG.font,
menuAccent: DEFAULT_CONFIG.menuAccent,
menuColor: DEFAULT_CONFIG.menuColor,
radius: DEFAULT_CONFIG.radius,
template: DEFAULT_CONFIG.template,
item: "preview",
})
}, [setParams, params.base])
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
className="border-foreground/10 bg-muted/50 hidden h-[calc(--spacing(13.5))] w-[140px] touch-manipulation justify-between rounded-xl border select-none focus-visible:border-transparent focus-visible:ring-1 sm:rounded-lg md:flex md:w-full md:rounded-lg md:border-transparent md:bg-transparent md:pr-3.5! md:pl-2!"
>
<div className="flex flex-col justify-start text-left">
<div className="text-muted-foreground text-xs">Reset</div>
<div className="text-foreground text-sm font-medium">
Start Over
</div>
</div>
<HugeiconsIcon icon={Undo02Icon} className="-translate-x-0.5" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent className="dialog-ring p-4 sm:max-w-sm">
<AlertDialogHeader>
<AlertDialogTitle>Reset to defaults?</AlertDialogTitle>
<AlertDialogDescription>
This will reset all customization options to their default values.
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="rounded-lg">Cancel</AlertDialogCancel>
<AlertDialogAction className="rounded-lg" onClick={handleReset}>
Reset
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}

View File

@@ -56,7 +56,7 @@ export function ShareButton() {
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
className="rounded-lg shadow-none lg:w-8 xl:w-fit" className="rounded-lg shadow-none"
onClick={handleCopy} onClick={handleCopy}
> >
{hasCopied ? ( {hasCopied ? (
@@ -64,7 +64,7 @@ export function ShareButton() {
) : ( ) : (
<HugeiconsIcon icon={Share03Icon} strokeWidth={2} /> <HugeiconsIcon icon={Share03Icon} strokeWidth={2} />
)} )}
<span className="lg:hidden xl:block">Share</span> Share
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Copy Link</TooltipContent> <TooltipContent>Copy Link</TooltipContent>

View File

@@ -76,7 +76,7 @@ export function ThemePicker({
anchor={isMobile ? anchorRef : undefined} anchor={isMobile ? anchorRef : undefined}
side={isMobile ? "top" : "right"} side={isMobile ? "top" : "right"}
align={isMobile ? "center" : "start"} align={isMobile ? "center" : "start"}
className="max-h-[23rem]" className="max-h-96"
> >
<PickerRadioGroup <PickerRadioGroup
value={currentTheme?.name} value={currentTheme?.name}

View File

@@ -1,7 +1,6 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import Link from "next/link"
import { import {
ComputerTerminal01Icon, ComputerTerminal01Icon,
Copy01Icon, Copy01Icon,
@@ -24,8 +23,6 @@ import {
} from "@/registry/new-york-v4/ui/dialog" } from "@/registry/new-york-v4/ui/dialog"
import { import {
Field, Field,
FieldContent,
FieldDescription,
FieldGroup, FieldGroup,
FieldLabel, FieldLabel,
FieldTitle, FieldTitle,
@@ -34,7 +31,6 @@ import {
RadioGroup, RadioGroup,
RadioGroupItem, RadioGroupItem,
} from "@/registry/new-york-v4/ui/radio-group" } from "@/registry/new-york-v4/ui/radio-group"
import { Switch } from "@/registry/new-york-v4/ui/switch"
import { import {
Tabs, Tabs,
TabsContent, TabsContent,
@@ -75,15 +71,14 @@ export function ToolbarControls() {
const packageManager = config.packageManager || "pnpm" const packageManager = config.packageManager || "pnpm"
const commands = React.useMemo(() => { const commands = React.useMemo(() => {
const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:4000" const origin = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"
const url = `${origin}/init?base=${params.base}&style=${params.style}&baseColor=${params.baseColor}&theme=${params.theme}&iconLibrary=${params.iconLibrary}&font=${params.font}&menuAccent=${params.menuAccent}&menuColor=${params.menuColor}&radius=${params.radius}&template=${params.template}&rtl=${params.rtl}` const url = `${origin}/init?base=${params.base}&style=${params.style}&baseColor=${params.baseColor}&theme=${params.theme}&iconLibrary=${params.iconLibrary}&font=${params.font}&menuAccent=${params.menuAccent}&menuColor=${params.menuColor}&radius=${params.radius}&template=${params.template}`
const templateFlag = params.template ? ` --template ${params.template}` : "" const templateFlag = params.template ? ` --template ${params.template}` : ""
const rtlFlag = params.rtl ? " --rtl" : ""
return { return {
pnpm: `pnpm dlx shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`, pnpm: `pnpm dlx shadcn@latest create --preset "${url}"${templateFlag}`,
npm: `npx shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`, npm: `npx shadcn@latest create --preset "${url}"${templateFlag}`,
yarn: `yarn dlx shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`, yarn: `yarn dlx shadcn@latest create --preset "${url}"${templateFlag}`,
bun: `bunx --bun shadcn@latest create${rtlFlag} --preset "${url}"${templateFlag}`, bun: `bunx --bun shadcn@latest create --preset "${url}"${templateFlag}`,
} }
}, [ }, [
params.base, params.base,
@@ -96,7 +91,6 @@ export function ToolbarControls() {
params.menuColor, params.menuColor,
params.radius, params.radius,
params.template, params.template,
params.rtl,
]) ])
const command = commands[packageManager] const command = commands[packageManager]
@@ -170,7 +164,7 @@ export function ToolbarControls() {
{selectedTemplate?.title} + shadcn/ui project. {selectedTemplate?.title} + shadcn/ui project.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<FieldGroup className="gap-3"> <FieldGroup>
<Field> <Field>
<FieldLabel htmlFor="template" className="sr-only"> <FieldLabel htmlFor="template" className="sr-only">
Template Template
@@ -189,7 +183,7 @@ export function ToolbarControls() {
<FieldLabel <FieldLabel
key={template.value} key={template.value}
htmlFor={template.value} htmlFor={template.value}
className="has-data-[state=checked]:border-primary/10 rounded-lg!" className="rounded-lg!"
> >
<Field className="flex min-w-0 flex-col items-center justify-center gap-2 p-3! text-center *:w-auto!"> <Field className="flex min-w-0 flex-col items-center justify-center gap-2 p-3! text-center *:w-auto!">
<RadioGroupItem <RadioGroupItem
@@ -211,81 +205,59 @@ export function ToolbarControls() {
))} ))}
</RadioGroup> </RadioGroup>
</Field> </Field>
<FieldLabel className="has-data-[state=checked]:border-primary/10 rounded-lg!">
<Field orientation="horizontal">
<FieldContent className="gap-1">
<FieldTitle>Enable RTL</FieldTitle>
<FieldDescription>
<a
href={`/docs/rtl/${params.template}`}
className="text-foreground underline"
target="_blank"
rel="noopener noreferrer"
>
View the RTL setup guide for {selectedTemplate?.title}.
</a>
</FieldDescription>
</FieldContent>
<Switch
checked={params.rtl}
onCheckedChange={(rtl) => setParams({ rtl })}
className="shadow-none"
/>
</Field>
</FieldLabel>
<Tabs
value={packageManager}
onValueChange={(value) => {
setConfig({
...config,
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
})
}}
className="bg-surface min-w-0 gap-0 overflow-hidden rounded-lg border"
>
<div className="flex items-center gap-2 p-2">
<TabsList className="*:data-[slot=tabs-trigger]:data-[state=active]:border-input h-auto rounded-none bg-transparent p-0 font-mono group-data-[orientation=horizontal]/tabs:h-8 *:data-[slot=tabs-trigger]:h-7 *:data-[slot=tabs-trigger]:border *:data-[slot=tabs-trigger]:border-transparent *:data-[slot=tabs-trigger]:pt-0.5 *:data-[slot=tabs-trigger]:shadow-none!">
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
<TabsTrigger value="npm">npm</TabsTrigger>
<TabsTrigger value="yarn">yarn</TabsTrigger>
<TabsTrigger value="bun">bun</TabsTrigger>
</TabsList>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon-sm"
variant="ghost"
className="ml-auto size-7 rounded-lg"
onClick={handleCopyFromTabs}
>
{hasCopied ? (
<HugeiconsIcon icon={Tick02Icon} className="size-4" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="size-4" />
)}
<span className="sr-only">Copy command</span>
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied!" : "Copy command"}
</TooltipContent>
</Tooltip>
</div>
{Object.entries(commands).map(([key, cmd]) => {
return (
<TabsContent key={key} value={key}>
<div className="bg-surface border-border/50 text-surface-foreground relative overflow-hidden border-t px-3 py-3">
<div className="no-scrollbar overflow-x-auto">
<code className="font-mono text-sm whitespace-nowrap">
{cmd}
</code>
</div>
</div>
</TabsContent>
)
})}
</Tabs>
</FieldGroup> </FieldGroup>
<Tabs
value={packageManager}
onValueChange={(value) => {
setConfig({
...config,
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
})
}}
className="bg-surface min-w-0 gap-0 overflow-hidden rounded-lg border"
>
<div className="flex items-center gap-2 p-2">
<TabsList className="*:data-[slot=tabs-trigger]:data-[state=active]:border-input h-auto rounded-none bg-transparent p-0 font-mono *:data-[slot=tabs-trigger]:border *:data-[slot=tabs-trigger]:border-transparent *:data-[slot=tabs-trigger]:pt-0.5 *:data-[slot=tabs-trigger]:shadow-none!">
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
<TabsTrigger value="npm">npm</TabsTrigger>
<TabsTrigger value="yarn">yarn</TabsTrigger>
<TabsTrigger value="bun">bun</TabsTrigger>
</TabsList>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon-sm"
variant="ghost"
className="ml-auto size-7 rounded-lg"
onClick={handleCopyFromTabs}
>
{hasCopied ? (
<HugeiconsIcon icon={Tick02Icon} className="size-4" />
) : (
<HugeiconsIcon icon={Copy01Icon} className="size-4" />
)}
<span className="sr-only">Copy command</span>
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied!" : "Copy command"}
</TooltipContent>
</Tooltip>
</div>
{Object.entries(commands).map(([key, cmd]) => {
return (
<TabsContent key={key} value={key}>
<div className="bg-surface border-border/50 text-surface-foreground relative overflow-hidden border-t px-3 py-3">
<div className="no-scrollbar overflow-x-auto">
<code className="font-mono text-sm whitespace-nowrap">
{cmd}
</code>
</div>
</div>
</TabsContent>
)
})}
</Tabs>
<DialogFooter className="bg-muted/50 -mx-6 mt-2 -mb-6 flex flex-col gap-2 border-t p-6 sm:flex-col"> <DialogFooter className="bg-muted/50 -mx-6 mt-2 -mb-6 flex flex-col gap-2 border-t p-6 sm:flex-col">
<Button <Button
size="sm" size="sm"

View File

@@ -14,7 +14,7 @@ import {
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params" import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
export function V0Button({ className }: { className?: string }) { export function V0Button({ className }: { className?: string }) {
const [params] = useDesignSystemSearchParams() const [params, setParams] = useDesignSystemSearchParams()
const isMobile = useIsMobile() const isMobile = useIsMobile()
const isMounted = useMounted() const isMounted = useMounted()
@@ -32,7 +32,7 @@ export function V0Button({ className }: { className?: string }) {
size="sm" size="sm"
variant={isMobile ? "default" : "outline"} variant={isMobile ? "default" : "outline"}
className={cn( className={cn(
"w-24 rounded-lg shadow-none data-[variant=default]:h-[31px] lg:w-8 xl:w-24", "w-24 rounded-lg shadow-none data-[variant=default]:h-[31px]",
className className
)} )}
asChild asChild
@@ -41,8 +41,7 @@ export function V0Button({ className }: { className?: string }) {
href={`${process.env.NEXT_PUBLIC_V0_URL}/chat/api/open?url=${encodeURIComponent(url)}&title=${params.item}`} href={`${process.env.NEXT_PUBLIC_V0_URL}/chat/api/open?url=${encodeURIComponent(url)}&title=${params.item}`}
target="_blank" target="_blank"
> >
<span className="lg:hidden xl:block">Open in</span> Open in <Icons.v0 className="size-5" />
<Icons.v0 className="size-5" />
</a> </a>
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>

View File

@@ -4,11 +4,7 @@ import { ArrowLeftIcon } from "lucide-react"
import type { SearchParams } from "nuqs/server" import type { SearchParams } from "nuqs/server"
import { siteConfig } from "@/lib/config" import { siteConfig } from "@/lib/config"
import { source } from "@/lib/source"
import { absoluteUrl } from "@/lib/utils" import { absoluteUrl } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { MainNav } from "@/components/main-nav"
import { MobileNav } from "@/components/mobile-nav"
import { ModeSwitcher } from "@/components/mode-switcher" import { ModeSwitcher } from "@/components/mode-switcher"
import { SiteConfig } from "@/components/site-config" import { SiteConfig } from "@/components/site-config"
import { BASES } from "@/registry/config" import { BASES } from "@/registry/config"
@@ -16,11 +12,10 @@ import { Button } from "@/registry/new-york-v4/ui/button"
import { Separator } from "@/registry/new-york-v4/ui/separator" import { Separator } from "@/registry/new-york-v4/ui/separator"
import { SidebarProvider } from "@/registry/new-york-v4/ui/sidebar" import { SidebarProvider } from "@/registry/new-york-v4/ui/sidebar"
import { Customizer } from "@/app/(create)/components/customizer" import { Customizer } from "@/app/(create)/components/customizer"
import { CustomizerControls } from "@/app/(create)/components/customizer-controls"
import { ItemExplorer } from "@/app/(create)/components/item-explorer" import { ItemExplorer } from "@/app/(create)/components/item-explorer"
import { ItemPicker } from "@/app/(create)/components/item-picker" import { ItemPicker } from "@/app/(create)/components/item-picker"
import { Preview } from "@/app/(create)/components/preview" import { Preview } from "@/app/(create)/components/preview"
import { RandomButton } from "@/app/(create)/components/random-button"
import { ResetButton } from "@/app/(create)/components/reset-button"
import { ShareButton } from "@/app/(create)/components/share-button" import { ShareButton } from "@/app/(create)/components/share-button"
import { ToolbarControls } from "@/app/(create)/components/toolbar-controls" import { ToolbarControls } from "@/app/(create)/components/toolbar-controls"
import { V0Button } from "@/app/(create)/components/v0-button" import { V0Button } from "@/app/(create)/components/v0-button"
@@ -68,7 +63,6 @@ export default async function CreatePage({
const params = await loadDesignSystemSearchParams(searchParams) const params = await loadDesignSystemSearchParams(searchParams)
const base = BASES.find((b) => b.name === params.base) ?? BASES[0] const base = BASES.find((b) => b.name === params.base) ?? BASES[0]
const pageTree = source.pageTree
const items = await getItemsForBase(base.name) const items = await getItemsForBase(base.name)
const filteredItems = items const filteredItems = items
@@ -78,7 +72,6 @@ export default async function CreatePage({
title: item.title, title: item.title,
type: item.type, type: item.type,
})) }))
.filter((item) => !/\d+$/.test(item.name))
return ( return (
<div <div
@@ -87,35 +80,36 @@ export default async function CreatePage({
> >
<header className="sticky top-0 z-50 w-full"> <header className="sticky top-0 z-50 w-full">
<div className="container-wrapper 3xl:fixed:px-0 px-6"> <div className="container-wrapper 3xl:fixed:px-0 px-6">
<div className="3xl:fixed:container flex h-(--header-height) items-center **:data-[slot=separator]:h-4!"> <div className="3xl:fixed:container flex h-(--header-height) items-center **:data-[slot=separator]:!h-4">
<div className="3xl:fixed:container flex h-(--header-height) items-center **:data-[slot=separator]:h-4!"> <div className="flex items-center xl:w-1/3">
<MobileNav
tree={pageTree}
items={siteConfig.navItems}
className="flex lg:hidden"
/>
<Button <Button
asChild asChild
variant="ghost" variant="outline"
size="icon" size="sm"
className="hidden size-8 lg:flex" className="rounded-lg shadow-none"
> >
<Link href="/"> <Link href="/">
<Icons.logo className="size-5" /> <ArrowLeftIcon />
<span className="sr-only">{siteConfig.name}</span> Back
</Link> </Link>
</Button> </Button>
<MainNav items={siteConfig.navItems} className="hidden lg:flex" /> <Separator
</div> orientation="vertical"
<div className="fixed inset-x-0 bottom-0 ml-auto flex flex-1 items-center justify-end gap-2 px-4.5 pb-4 sm:static sm:p-0 lg:ml-0"> className="mx-2 hidden sm:mx-4 lg:flex"
<ItemPicker items={filteredItems} /> />
<div className="items-center gap-0 sm:hidden"> <div className="text-muted-foreground hidden text-sm font-medium lg:flex">
<RandomButton /> New Project
<ResetButton />
</div> </div>
<Separator orientation="vertical" className="mr-2 flex" />
</div> </div>
<div className="ml-auto flex items-center gap-2 sm:ml-0 md:justify-end"> <div className="fixed inset-x-0 bottom-0 ml-auto flex flex-1 items-center gap-2 px-4.5 pb-4 sm:static sm:justify-end sm:p-0 lg:ml-0 xl:justify-center">
<ItemPicker items={filteredItems} />
<CustomizerControls className="sm:hidden" />
<Separator
orientation="vertical"
className="mr-2 hidden sm:flex xl:hidden"
/>
</div>
<div className="ml-auto flex items-center gap-2 sm:ml-0 md:justify-end xl:ml-auto xl:w-1/3">
<SiteConfig className="3xl:flex hidden" /> <SiteConfig className="3xl:flex hidden" />
<Separator orientation="vertical" className="3xl:flex hidden" /> <Separator orientation="vertical" className="3xl:flex hidden" />
<ModeSwitcher /> <ModeSwitcher />

View File

@@ -45,6 +45,18 @@ export async function GET(request: NextRequest) {
} }
const designSystemConfig = parseResult.data const designSystemConfig = parseResult.data
const registryBase = buildRegistryBase(designSystemConfig)
const validateResult = registryItemSchema.safeParse(registryBase)
if (!validateResult.success) {
return NextResponse.json(
{
error: "Invalid registry base item",
details: validateResult.error.format(),
},
{ status: 500 }
)
}
track("create_open_in_v0", designSystemConfig) track("create_open_in_v0", designSystemConfig)
@@ -63,23 +75,28 @@ export async function GET(request: NextRequest) {
} }
async function buildV0Payload(designSystemConfig: DesignSystemConfig) { async function buildV0Payload(designSystemConfig: DesignSystemConfig) {
const registryBase = buildRegistryBase(designSystemConfig) const files: z.infer<typeof registryItemFileSchema>[] = []
// Build all files in parallel. // Build globals.css file.
const [globalsCss, layoutFile, componentFiles] = await Promise.all([ files.push(buildGlobalsCss(designSystemConfig))
buildGlobalsCss(registryBase),
buildLayoutFile(designSystemConfig), // Build layout.tsx file.
buildComponentFiles(designSystemConfig), files.push(buildLayoutFile(designSystemConfig))
])
// Build component files.
const componentFiles = await buildComponentFiles(designSystemConfig)
files.push(...componentFiles)
return registryItemSchema.parse({ return registryItemSchema.parse({
name: designSystemConfig.item ?? "Item", name: designSystemConfig.item ?? "Item",
type: "registry:item", type: "registry:item",
files: [globalsCss, layoutFile, ...componentFiles], files,
}) })
} }
function buildGlobalsCss(registryBase: RegistryItem) { function buildGlobalsCss(designSystemConfig: DesignSystemConfig) {
const registryBase = buildRegistryBase(designSystemConfig)
const lightVars = Object.entries(registryBase.cssVars?.light ?? {}) const lightVars = Object.entries(registryBase.cssVars?.light ?? {})
.map(([key, value]) => ` --${key}: ${value};`) .map(([key, value]) => ` --${key}: ${value};`)
.join("\n") .join("\n")

View File

@@ -19,7 +19,6 @@ export async function GET(request: NextRequest) {
menuColor: searchParams.get("menuColor"), menuColor: searchParams.get("menuColor"),
radius: searchParams.get("radius"), radius: searchParams.get("radius"),
template: searchParams.get("template"), template: searchParams.get("template"),
rtl: searchParams.get("rtl") === "true",
}) })
if (!result.success) { if (!result.success) {

View File

@@ -38,15 +38,15 @@ const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono", variable: "--font-jetbrains-mono",
}) })
const geistSans = Geist({ // const geistSans = Geist({
subsets: ["latin"], // subsets: ["latin"],
variable: "--font-geist-sans", // variable: "--font-geist-sans",
}) // })
const geistMono = Geist_Mono({ // const geistMono = Geist_Mono({
subsets: ["latin"], // subsets: ["latin"],
variable: "--font-geist-mono", // variable: "--font-geist-mono",
}) // })
const roboto = Roboto({ const roboto = Roboto({
subsets: ["latin"], subsets: ["latin"],
@@ -74,12 +74,12 @@ const outfit = Outfit({
}) })
export const FONTS = [ export const FONTS = [
{ // {
name: "Geist", // name: "Geist Sans",
value: "geist", // value: "geist",
font: geistSans, // font: geistSans,
type: "sans", // type: "sans",
}, // },
{ {
name: "Inter", name: "Inter",
value: "inter", value: "inter",
@@ -134,18 +134,18 @@ export const FONTS = [
font: outfit, font: outfit,
type: "sans", type: "sans",
}, },
{
name: "Geist Mono",
value: "geist-mono",
font: geistMono,
type: "mono",
},
{ {
name: "JetBrains Mono", name: "JetBrains Mono",
value: "jetbrains-mono", value: "jetbrains-mono",
font: jetbrainsMono, font: jetbrainsMono,
type: "mono", type: "mono",
}, },
// {
// name: "Geist Mono",
// value: "geist-mono",
// font: geistMono,
// type: "mono",
// },
] as const ] as const
export type Font = (typeof FONTS)[number] export type Font = (typeof FONTS)[number]

View File

@@ -66,7 +66,6 @@ const designSystemSearchParams = {
"start", "start",
"vite", "vite",
] as const).withDefault("next"), ] as const).withDefault("next"),
rtl: parseAsBoolean.withDefault(false),
size: parseAsInteger.withDefault(100), size: parseAsInteger.withDefault(100),
custom: parseAsBoolean.withDefault(false), custom: parseAsBoolean.withDefault(false),
} }

View File

@@ -7,10 +7,10 @@ import { absoluteUrl } from "@/lib/utils"
import { DarkModeScript } from "@/components/mode-switcher" import { DarkModeScript } from "@/components/mode-switcher"
import { TailwindIndicator } from "@/components/tailwind-indicator" import { TailwindIndicator } from "@/components/tailwind-indicator"
import { BASES, type Base } from "@/registry/config" import { BASES, type Base } from "@/registry/config"
import { RandomizeScript } from "@/app/(create)/components/customizer-controls"
import { DesignSystemProvider } from "@/app/(create)/components/design-system-provider" import { DesignSystemProvider } from "@/app/(create)/components/design-system-provider"
import { ItemPickerScript } from "@/app/(create)/components/item-picker" import { ItemPickerScript } from "@/app/(create)/components/item-picker"
import { PreviewStyle } from "@/app/(create)/components/preview-style" import { PreviewStyle } from "@/app/(create)/components/preview-style"
import { RandomizeScript } from "@/app/(create)/components/random-button"
import { getBaseComponent, getBaseItem } from "@/app/(create)/lib/api" import { getBaseComponent, getBaseItem } from "@/app/(create)/lib/api"
import { ALLOWED_ITEM_TYPES } from "@/app/(create)/lib/constants" import { ALLOWED_ITEM_TYPES } from "@/app/(create)/lib/constants"

View File

@@ -77,7 +77,7 @@ export function ExampleForm() {
password: "", password: "",
}, },
validators: { validators: {
onBlur: exampleFormSchema, onChange: exampleFormSchema,
}, },
onSubmit: async ({ value }) => { onSubmit: async ({ value }) => {
setValues(value) setValues(value)

View File

@@ -1,5 +1,3 @@
import { TrashIcon } from "lucide-react"
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@@ -8,7 +6,6 @@ import {
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
} from "@/registry/new-york-v4/ui/alert-dialog" } from "@/registry/new-york-v4/ui/alert-dialog"
@@ -16,66 +13,23 @@ import { Button } from "@/registry/new-york-v4/ui/button"
export function AlertDialogDemo() { export function AlertDialogDemo() {
return ( return (
<div className="flex flex-wrap gap-4"> <AlertDialog>
<AlertDialog> <AlertDialogTrigger asChild>
<AlertDialogTrigger asChild> <Button variant="outline">Show Dialog</Button>
<Button variant="outline">Default</Button> </AlertDialogTrigger>
</AlertDialogTrigger> <AlertDialogContent>
<AlertDialogContent> <AlertDialogHeader>
<AlertDialogHeader> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> <AlertDialogDescription>
<AlertDialogDescription> This action cannot be undone. This will permanently delete your
This action cannot be undone. This will permanently delete your account and remove your data from our servers.
account and remove your data from our servers. </AlertDialogDescription>
</AlertDialogDescription> </AlertDialogHeader>
</AlertDialogHeader> <AlertDialogFooter>
<AlertDialogFooter> <AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogAction>Continue</AlertDialogAction>
<AlertDialogAction>Continue</AlertDialogAction> </AlertDialogFooter>
</AlertDialogFooter> </AlertDialogContent>
</AlertDialogContent> </AlertDialog>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">With Media</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia>
<TrashIcon className="size-8" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the
item from your account.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">Small Size</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia>
<TrashIcon className="size-8" />
</AlertDialogMedia>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
) )
} }

View File

@@ -1,173 +1,89 @@
import { PlusIcon } from "lucide-react"
import { import {
Avatar, Avatar,
AvatarBadge,
AvatarFallback, AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage, AvatarImage,
} from "@/registry/new-york-v4/ui/avatar" } from "@/registry/new-york-v4/ui/avatar"
export function AvatarDemo() { export function AvatarDemo() {
return ( return (
<div className="flex flex-col gap-6"> <div className="flex flex-row flex-wrap items-center gap-4">
{/* Sizes. */} <Avatar>
<div className="flex flex-row flex-wrap items-center gap-4"> <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<Avatar size="sm"> <AvatarFallback>CN</AvatarFallback>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> </Avatar>
<AvatarFallback>CN</AvatarFallback> <Avatar>
</Avatar> <AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="size-12">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar className="rounded-lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<Avatar> <Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback> <AvatarFallback>CN</AvatarFallback>
</Avatar> </Avatar>
<Avatar size="lg"> <Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> <AvatarImage
<AvatarFallback>CN</AvatarFallback> src="https://github.com/maxleiter.png"
</Avatar> alt="@maxleiter"
</div> />
{/* Fallback. */} <AvatarFallback>LR</AvatarFallback>
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarFallback>CN</AvatarFallback>
</Avatar> </Avatar>
<Avatar> <Avatar>
<AvatarFallback>CN</AvatarFallback> <AvatarImage
</Avatar> src="https://github.com/evilrabbit.png"
<Avatar size="lg"> alt="@evilrabbit"
<AvatarFallback>CN</AvatarFallback> />
<AvatarFallback>ER</AvatarFallback>
</Avatar> </Avatar>
</div> </div>
{/* With badge. */} <div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
<div className="flex flex-row flex-wrap items-center gap-4">
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar> <Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback> <AvatarFallback>CN</AvatarFallback>
<AvatarBadge />
</Avatar> </Avatar>
<Avatar size="lg"> <Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</div>
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback> <AvatarFallback>CN</AvatarFallback>
<AvatarBadge>
<PlusIcon />
</AvatarBadge>
</Avatar> </Avatar>
</div> <Avatar>
{/* Avatar group. */} <AvatarImage
<div className="flex flex-row flex-wrap items-center gap-4"> src="https://github.com/maxleiter.png"
<AvatarGroup> alt="@maxleiter"
<Avatar size="sm"> />
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" /> <AvatarFallback>LR</AvatarFallback>
<AvatarFallback>CN</AvatarFallback> </Avatar>
</Avatar> <Avatar>
<Avatar size="sm"> <AvatarImage
<AvatarImage src="https://github.com/evilrabbit.png"
src="https://github.com/maxleiter.png" alt="@evilrabbit"
alt="@maxleiter" />
/> <AvatarFallback>ER</AvatarFallback>
<AvatarFallback>ML</AvatarFallback> </Avatar>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</div>
{/* Avatar group with count. */}
<div className="flex flex-row flex-wrap items-center gap-4">
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>ML</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<PlusIcon />
</AvatarGroupCount>
</AvatarGroup>
</div> </div>
</div> </div>
) )

View File

@@ -10,10 +10,6 @@ export function BadgeDemo() {
<Badge variant="secondary">Secondary</Badge> <Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge> <Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge> <Badge variant="outline">Outline</Badge>
<Badge variant="ghost">Ghost</Badge>
<Badge variant="link">Link</Badge>
</div>
<div className="flex w-full flex-wrap gap-2">
<Badge variant="outline"> <Badge variant="outline">
<CheckIcon /> <CheckIcon />
Badge Badge
@@ -59,16 +55,6 @@ export function BadgeDemo() {
Link <ArrowRightIcon /> Link <ArrowRightIcon />
</a> </a>
</Badge> </Badge>
<Badge asChild variant="ghost">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
<Badge asChild variant="link">
<a href="#">
Link <ArrowRightIcon />
</a>
</Badge>
</div> </div>
</div> </div>
) )

View File

@@ -1,4 +1,4 @@
import { ArrowRightIcon, Loader2Icon, PlusIcon, SendIcon } from "lucide-react" import { ArrowRightIcon, Loader2Icon, SendIcon } from "lucide-react"
import { Button } from "@/registry/new-york-v4/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
@@ -6,25 +6,22 @@ export function ButtonDemo() {
return ( return (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<div className="flex flex-wrap items-center gap-2 md:flex-row"> <div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="xs">Extra Small</Button> <Button>Button</Button>
<Button variant="outline" size="xs"> <Button variant="outline">Outline</Button>
Outline <Button variant="ghost">Ghost</Button>
</Button> <Button variant="destructive">Destructive</Button>
<Button variant="ghost" size="xs"> <Button variant="secondary">Secondary</Button>
Ghost <Button variant="link">Link</Button>
</Button> <Button variant="outline">
<Button variant="destructive" size="xs">
Destructive
</Button>
<Button variant="secondary" size="xs">
Secondary
</Button>
<Button variant="link" size="xs">
Link
</Button>
<Button variant="outline" size="xs">
<SendIcon /> Send <SendIcon /> Send
</Button> </Button>
<Button variant="outline">
Learn More <ArrowRightIcon />
</Button>
<Button disabled variant="outline">
<Loader2Icon className="animate-spin" />
Please wait
</Button>
</div> </div>
<div className="flex flex-wrap items-center gap-2 md:flex-row"> <div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button size="sm">Small</Button> <Button size="sm">Small</Button>
@@ -46,21 +43,10 @@ export function ButtonDemo() {
<Button variant="outline" size="sm"> <Button variant="outline" size="sm">
<SendIcon /> Send <SendIcon /> Send
</Button> </Button>
</div> <Button variant="outline" size="sm">
<div className="flex flex-wrap items-center gap-2 md:flex-row">
<Button>Button</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="link">Link</Button>
<Button variant="outline">
<SendIcon /> Send
</Button>
<Button variant="outline">
Learn More <ArrowRightIcon /> Learn More <ArrowRightIcon />
</Button> </Button>
<Button disabled variant="outline"> <Button disabled size="sm" variant="outline">
<Loader2Icon className="animate-spin" /> <Loader2Icon className="animate-spin" />
Please wait Please wait
</Button> </Button>
@@ -85,19 +71,12 @@ export function ButtonDemo() {
<Button variant="outline" size="lg"> <Button variant="outline" size="lg">
<SendIcon /> Send <SendIcon /> Send
</Button> </Button>
</div> <Button variant="outline" size="lg">
<div className="flex flex-wrap items-center gap-2 md:flex-row"> Learn More <ArrowRightIcon />
<Button size="icon-xs" variant="outline">
<PlusIcon />
</Button> </Button>
<Button size="icon-sm" variant="outline"> <Button disabled size="lg" variant="outline">
<PlusIcon /> <Loader2Icon className="animate-spin" />
</Button> Please wait
<Button size="icon" variant="outline">
<PlusIcon />
</Button>
<Button size="icon-lg" variant="outline">
<PlusIcon />
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -1,183 +1,405 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import {
CheckIcon,
ChevronDownIcon,
ChevronsUpDown,
PlusCircleIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button" import { Button } from "@/registry/new-york-v4/ui/button"
import { import {
Combobox, Command,
ComboboxChip, CommandEmpty,
ComboboxChips, CommandGroup,
ComboboxChipsInput, CommandInput,
ComboboxCollection, CommandItem,
ComboboxContent, CommandList,
ComboboxEmpty, CommandSeparator,
ComboboxGroup, } from "@/registry/new-york-v4/ui/command"
ComboboxInput, import {
ComboboxItem, Popover,
ComboboxLabel, PopoverContent,
ComboboxList, PopoverTrigger,
ComboboxTrigger, } from "@/registry/new-york-v4/ui/popover"
ComboboxValue,
useComboboxAnchor,
} from "@/registry/new-york-v4/ui/combobox"
const frameworks = [ const frameworks = [
"Next.js", {
"SvelteKit", value: "next.js",
"Nuxt.js", label: "Next.js",
"Remix", },
"Astro", {
value: "sveltekit",
label: "SvelteKit",
},
{
value: "nuxt.js",
label: "Nuxt.js",
},
{
value: "remix",
label: "Remix",
},
{
value: "astro",
label: "Astro",
},
]
type Framework = (typeof frameworks)[number]
const users = [
{
id: "1",
username: "shadcn",
},
{
id: "2",
username: "maxleiter",
},
{
id: "3",
username: "evilrabbit",
},
] as const ] as const
type User = (typeof users)[number]
const timezones = [ const timezones = [
{ {
value: "Americas", label: "Americas",
items: ["(GMT-5) New York", "(GMT-8) Los Angeles", "(GMT-6) Chicago"], timezones: [
{ value: "America/New_York", label: "(GMT-5) New York" },
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
],
}, },
{ {
value: "Europe", label: "Europe",
items: ["(GMT+0) London", "(GMT+1) Paris", "(GMT+1) Berlin"], timezones: [
{ value: "Europe/London", label: "(GMT+0) London" },
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
],
}, },
{ {
value: "Asia/Pacific", label: "Asia/Pacific",
items: ["(GMT+9) Tokyo", "(GMT+8) Shanghai", "(GMT+8) Singapore"], timezones: [
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
],
}, },
] as const ] as const
const countries = [ type Timezone = (typeof timezones)[number]
{ code: "", value: "", label: "Select country" },
{ code: "us", value: "united-states", label: "United States" },
{ code: "ca", value: "canada", label: "Canada" },
{ code: "gb", value: "united-kingdom", label: "United Kingdom" },
{ code: "de", value: "germany", label: "Germany" },
{ code: "fr", value: "france", label: "France" },
{ code: "jp", value: "japan", label: "Japan" },
]
export function ComboboxDemo() { export function ComboboxDemo() {
return ( return (
<div className="flex w-full flex-col gap-6"> <div className="flex w-full flex-wrap items-start gap-4">
{/* Basic combobox. */} <FrameworkCombobox frameworks={[...frameworks]} />
<div className="flex flex-wrap items-start gap-4"> <UserCombobox users={[...users]} selectedUserId={users[0].id} />
<Combobox items={frameworks}> <TimezoneCombobox
<ComboboxInput placeholder="Select a framework" /> timezones={[...timezones]}
<ComboboxContent> selectedTimezone={timezones[0].timezones[0]}
<ComboboxEmpty>No items found.</ComboboxEmpty> />
<ComboboxList> <ComboboxWithCheckbox frameworks={[...frameworks]} />
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With clear button. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={frameworks} defaultValue={frameworks[0]}>
<ComboboxInput placeholder="Select a framework" showClear />
<ComboboxContent>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With groups. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={timezones}>
<ComboboxInput placeholder="Select a timezone" />
<ComboboxContent>
<ComboboxEmpty>No timezones found.</ComboboxEmpty>
<ComboboxList>
{(group) => (
<ComboboxGroup key={group.value} items={group.items}>
<ComboboxLabel>{group.value}</ComboboxLabel>
<ComboboxCollection>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxCollection>
</ComboboxGroup>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* With trigger button. */}
<div className="flex flex-wrap items-start gap-4">
<Combobox items={countries} defaultValue={countries[0]}>
<ComboboxTrigger
render={
<Button
variant="outline"
className="w-64 justify-between font-normal"
/>
}
>
<ComboboxValue />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput showTrigger={false} placeholder="Search" />
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.code} value={item}>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
{/* Multiple selection with chips. */}
<ComboboxMultiple />
</div> </div>
) )
} }
function ComboboxMultiple() { function FrameworkCombobox({ frameworks }: { frameworks: Framework[] }) {
const anchor = useComboboxAnchor() const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
return ( return (
<div className="flex flex-wrap items-start gap-4"> <Popover open={open} onOpenChange={setOpen}>
<Combobox <PopoverTrigger asChild>
multiple <Button
autoHighlight variant="outline"
items={frameworks} role="combobox"
defaultValue={[frameworks[0]]} aria-expanded={open}
> className="w-full justify-between md:max-w-[200px]"
<ComboboxChips ref={anchor}> >
<ComboboxValue> {value
{(values) => ( ? frameworks.find((framework) => framework.value === value)?.label
<React.Fragment> : "Select framework..."}
{values.map((value: string) => ( <ChevronsUpDown className="text-muted-foreground" />
<ComboboxChip key={value}>{value}</ComboboxChip> </Button>
))} </PopoverTrigger>
<ComboboxChipsInput placeholder="Add framework..." /> <PopoverContent className="w-(--radix-popover-trigger-width) p-0">
</React.Fragment> <Command>
)} <CommandInput placeholder="Search framework..." />
</ComboboxValue> <CommandList>
</ComboboxChips> <CommandEmpty>No framework found.</CommandEmpty>
<ComboboxContent anchor={anchor}> <CommandGroup>
<ComboboxEmpty>No items found.</ComboboxEmpty> {frameworks.map((framework) => (
<ComboboxList> <CommandItem
{(item) => ( key={framework.value}
<ComboboxItem key={item} value={item}> value={framework.value}
{item} onSelect={(currentValue) => {
</ComboboxItem> setValue(currentValue === value ? "" : currentValue)
)} setOpen(false)
</ComboboxList> }}
</ComboboxContent> >
</Combobox> {framework.label}
</div> <CheckIcon
className={cn(
"ml-auto",
value === framework.value ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
function UserCombobox({
users,
selectedUserId,
}: {
users: User[]
selectedUserId: string
}) {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState(selectedUserId)
const selectedUser = React.useMemo(
() => users.find((user) => user.id === value),
[value, users]
)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between px-2 md:max-w-[200px]"
>
{selectedUser ? (
<div className="flex items-center gap-2">
<Avatar className="size-5">
<AvatarImage
src={`https://github.com/${selectedUser.username}.png`}
/>
<AvatarFallback>{selectedUser.username[0]}</AvatarFallback>
</Avatar>
{selectedUser.username}
</div>
) : (
"Select user..."
)}
<ChevronsUpDown className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
<Command>
<CommandInput placeholder="Search user..." />
<CommandList>
<CommandEmpty>No user found.</CommandEmpty>
<CommandGroup>
{users.map((user) => (
<CommandItem
key={user.id}
value={user.id}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
>
<Avatar className="size-5">
<AvatarImage
src={`https://github.com/${user.username}.png`}
/>
<AvatarFallback>{user.username[0]}</AvatarFallback>
</Avatar>
{user.username}
<CheckIcon
className={cn(
"ml-auto",
value === user.id ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
<CommandSeparator />
<CommandGroup>
<CommandItem>
<PlusCircleIcon />
Create user
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
function TimezoneCombobox({
timezones,
selectedTimezone,
}: {
timezones: Timezone[]
selectedTimezone: Timezone["timezones"][number]
}) {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState(selectedTimezone.value)
const selectedGroup = React.useMemo(
() =>
timezones.find((group) =>
group.timezones.find((tz) => tz.value === value)
),
[value, timezones]
)
const selectedTimezoneLabel = React.useMemo(
() => selectedGroup?.timezones.find((tz) => tz.value === value)?.label,
[value, selectedGroup]
)
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="h-12 w-full justify-between px-2.5 md:max-w-[200px]"
>
{selectedTimezone ? (
<div className="flex flex-col items-start gap-0.5">
<span className="text-muted-foreground text-xs font-normal">
{selectedGroup?.label}
</span>
<span>{selectedTimezoneLabel}</span>
</div>
) : (
"Select timezone"
)}
<ChevronDownIcon className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput placeholder="Search timezone..." />
<CommandList className="scroll-pb-12">
<CommandEmpty>No timezone found.</CommandEmpty>
{timezones.map((region) => (
<CommandGroup key={region.label} heading={region.label}>
{region.timezones.map((timezone) => (
<CommandItem
key={timezone.value}
value={timezone.value}
onSelect={(currentValue) => {
setValue(
currentValue as Timezone["timezones"][number]["value"]
)
setOpen(false)
}}
>
{timezone.label}
<CheckIcon
className="ml-auto opacity-0 data-[selected=true]:opacity-100"
data-selected={value === timezone.value}
/>
</CommandItem>
))}
</CommandGroup>
))}
<CommandSeparator className="sticky bottom-10" />
<CommandGroup className="bg-popover sticky bottom-0">
<CommandItem>
<PlusCircleIcon />
Create timezone
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
function ComboboxWithCheckbox({ frameworks }: { frameworks: Framework[] }) {
const [open, setOpen] = React.useState(false)
const [selectedFrameworks, setSelectedFrameworks] = React.useState<
Framework[]
>([])
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-fit min-w-[280px] justify-between"
>
{selectedFrameworks.length > 0
? selectedFrameworks.map((framework) => framework.label).join(", ")
: "Select frameworks (multi-select)..."}
<ChevronsUpDown className="text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
value={framework.value}
onSelect={(currentValue) => {
setSelectedFrameworks(
selectedFrameworks.some((f) => f.value === currentValue)
? selectedFrameworks.filter(
(f) => f.value !== currentValue
)
: [...selectedFrameworks, framework]
)
}}
>
<div
className="border-input data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground pointer-events-none size-4 shrink-0 rounded-[4px] border transition-all select-none *:[svg]:opacity-0 data-[selected=true]:*:[svg]:opacity-100"
data-selected={selectedFrameworks.some(
(f) => f.value === framework.value
)}
>
<CheckIcon className="size-3.5 text-current" />
</div>
{framework.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
) )
} }

View File

@@ -118,7 +118,6 @@ function DropdownMenuCheckboxes() {
tabler="IconUser" tabler="IconUser"
hugeicons="UserIcon" hugeicons="UserIcon"
phosphor="UserIcon" phosphor="UserIcon"
remixicon="RiUserLine"
/> />
Profile Profile
</DropdownMenuItem> </DropdownMenuItem>
@@ -128,7 +127,6 @@ function DropdownMenuCheckboxes() {
tabler="IconCreditCard" tabler="IconCreditCard"
hugeicons="CreditCardIcon" hugeicons="CreditCardIcon"
phosphor="CreditCardIcon" phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/> />
Billing Billing
</DropdownMenuItem> </DropdownMenuItem>
@@ -138,7 +136,6 @@ function DropdownMenuCheckboxes() {
tabler="IconSettings" tabler="IconSettings"
hugeicons="SettingsIcon" hugeicons="SettingsIcon"
phosphor="GearIcon" phosphor="GearIcon"
remixicon="RiSettings3Line"
/> />
Settings Settings
</DropdownMenuItem> </DropdownMenuItem>
@@ -174,7 +171,6 @@ function DropdownMenuCheckboxes() {
tabler="IconLogout" tabler="IconLogout"
hugeicons="LogoutIcon" hugeicons="LogoutIcon"
phosphor="SignOutIcon" phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/> />
Sign Out Sign Out
</DropdownMenuItem> </DropdownMenuItem>
@@ -231,7 +227,6 @@ function DropdownMenuWithAvatar() {
tabler="IconChevronsUpDown" tabler="IconChevronsUpDown"
hugeicons="ChevronUpDownIcon" hugeicons="ChevronUpDownIcon"
phosphor="CaretUpDownIcon" phosphor="CaretUpDownIcon"
remixicon="RiExpandUpDownLine"
className="text-muted-foreground ml-auto" className="text-muted-foreground ml-auto"
/> />
</Button> </Button>
@@ -262,7 +257,6 @@ function DropdownMenuWithAvatar() {
tabler="IconSparkles" tabler="IconSparkles"
hugeicons="SparklesIcon" hugeicons="SparklesIcon"
phosphor="SparklesIcon" phosphor="SparklesIcon"
remixicon="RiSparklingLine"
/> />
Upgrade to Pro Upgrade to Pro
</DropdownMenuItem> </DropdownMenuItem>
@@ -275,7 +269,6 @@ function DropdownMenuWithAvatar() {
tabler="IconBadgeCheck" tabler="IconBadgeCheck"
hugeicons="BadgeCheckIcon" hugeicons="BadgeCheckIcon"
phosphor="CheckCircleIcon" phosphor="CheckCircleIcon"
remixicon="RiVerifiedBadgeLine"
/> />
Account Account
</DropdownMenuItem> </DropdownMenuItem>
@@ -285,7 +278,6 @@ function DropdownMenuWithAvatar() {
tabler="IconCreditCard" tabler="IconCreditCard"
hugeicons="CreditCardIcon" hugeicons="CreditCardIcon"
phosphor="CreditCardIcon" phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/> />
Billing Billing
</DropdownMenuItem> </DropdownMenuItem>
@@ -295,7 +287,6 @@ function DropdownMenuWithAvatar() {
tabler="IconBell" tabler="IconBell"
hugeicons="BellIcon" hugeicons="BellIcon"
phosphor="BellIcon" phosphor="BellIcon"
remixicon="RiNotification3Line"
/> />
Notifications Notifications
</DropdownMenuItem> </DropdownMenuItem>
@@ -307,7 +298,6 @@ function DropdownMenuWithAvatar() {
tabler="IconLogout" tabler="IconLogout"
hugeicons="LogoutIcon" hugeicons="LogoutIcon"
phosphor="SignOutIcon" phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/> />
Sign Out Sign Out
</DropdownMenuItem> </DropdownMenuItem>
@@ -362,7 +352,6 @@ function DropdownMenuAvatarOnly() {
tabler="IconSparkles" tabler="IconSparkles"
hugeicons="SparklesIcon" hugeicons="SparklesIcon"
phosphor="SparklesIcon" phosphor="SparklesIcon"
remixicon="RiSparklingLine"
/> />
Upgrade to Pro Upgrade to Pro
</DropdownMenuItem> </DropdownMenuItem>
@@ -375,7 +364,6 @@ function DropdownMenuAvatarOnly() {
tabler="IconBadgeCheck" tabler="IconBadgeCheck"
hugeicons="BadgeCheckIcon" hugeicons="BadgeCheckIcon"
phosphor="CheckCircleIcon" phosphor="CheckCircleIcon"
remixicon="RiVerifiedBadgeLine"
/> />
Account Account
</DropdownMenuItem> </DropdownMenuItem>
@@ -385,7 +373,6 @@ function DropdownMenuAvatarOnly() {
tabler="IconCreditCard" tabler="IconCreditCard"
hugeicons="CreditCardIcon" hugeicons="CreditCardIcon"
phosphor="CreditCardIcon" phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/> />
Billing Billing
</DropdownMenuItem> </DropdownMenuItem>
@@ -395,7 +382,6 @@ function DropdownMenuAvatarOnly() {
tabler="IconBell" tabler="IconBell"
hugeicons="BellIcon" hugeicons="BellIcon"
phosphor="BellIcon" phosphor="BellIcon"
remixicon="RiNotification3Line"
/> />
Notifications Notifications
</DropdownMenuItem> </DropdownMenuItem>
@@ -407,7 +393,6 @@ function DropdownMenuAvatarOnly() {
tabler="IconLogout" tabler="IconLogout"
hugeicons="LogoutIcon" hugeicons="LogoutIcon"
phosphor="SignOutIcon" phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/> />
Sign Out Sign Out
</DropdownMenuItem> </DropdownMenuItem>
@@ -426,7 +411,6 @@ function DropdownMenuIconColor() {
tabler="IconDots" tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon" hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon" phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/> />
<span className="sr-only">Toggle menu</span> <span className="sr-only">Toggle menu</span>
</Button> </Button>
@@ -439,7 +423,6 @@ function DropdownMenuIconColor() {
tabler="IconPencil" tabler="IconPencil"
hugeicons="EditIcon" hugeicons="EditIcon"
phosphor="PencilIcon" phosphor="PencilIcon"
remixicon="RiPencilLine"
/> />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
@@ -449,7 +432,6 @@ function DropdownMenuIconColor() {
tabler="IconShare" tabler="IconShare"
hugeicons="ShareIcon" hugeicons="ShareIcon"
phosphor="ShareIcon" phosphor="ShareIcon"
remixicon="RiShareLine"
/> />
Share Share
</DropdownMenuItem> </DropdownMenuItem>
@@ -460,7 +442,6 @@ function DropdownMenuIconColor() {
tabler="IconTrash" tabler="IconTrash"
hugeicons="DeleteIcon" hugeicons="DeleteIcon"
phosphor="TrashIcon" phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/> />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>

View File

@@ -4,64 +4,59 @@ import { Label } from "@/registry/new-york-v4/ui/label"
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger, PopoverTrigger,
} from "@/registry/new-york-v4/ui/popover" } from "@/registry/new-york-v4/ui/popover"
export function PopoverDemo() { export function PopoverDemo() {
return ( return (
<div className="flex gap-4"> <Popover>
<Popover> <PopoverTrigger asChild>
<PopoverTrigger asChild> <Button variant="outline">Open popover</Button>
<Button variant="outline">Open popover</Button> </PopoverTrigger>
</PopoverTrigger> <PopoverContent className="w-80" align="start">
<PopoverContent className="w-80" align="start"> <div className="grid gap-4">
<div className="grid gap-4"> <div className="grid gap-1.5">
<PopoverHeader> <h4 className="leading-none font-medium">Dimensions</h4>
<PopoverTitle>Dimensions</PopoverTitle> <p className="text-muted-foreground text-sm">
<PopoverDescription> Set the dimensions for the layer.
Set the dimensions for the layer. </p>
</PopoverDescription> </div>
</PopoverHeader> <div className="grid gap-2">
<div className="grid gap-2"> <div className="grid grid-cols-3 items-center gap-4">
<div className="grid grid-cols-3 items-center gap-4"> <Label htmlFor="width">Width</Label>
<Label htmlFor="width">Width</Label> <Input
<Input id="width"
id="width" defaultValue="100%"
defaultValue="100%" className="col-span-2 h-8"
className="col-span-2 h-8" />
/> </div>
</div> <div className="grid grid-cols-3 items-center gap-4">
<div className="grid grid-cols-3 items-center gap-4"> <Label htmlFor="maxWidth">Max. width</Label>
<Label htmlFor="maxWidth">Max. width</Label> <Input
<Input id="maxWidth"
id="maxWidth" defaultValue="300px"
defaultValue="300px" className="col-span-2 h-8"
className="col-span-2 h-8" />
/> </div>
</div> <div className="grid grid-cols-3 items-center gap-4">
<div className="grid grid-cols-3 items-center gap-4"> <Label htmlFor="height">Height</Label>
<Label htmlFor="height">Height</Label> <Input
<Input id="height"
id="height" defaultValue="25px"
defaultValue="25px" className="col-span-2 h-8"
className="col-span-2 h-8" />
/> </div>
</div> <div className="grid grid-cols-3 items-center gap-4">
<div className="grid grid-cols-3 items-center gap-4"> <Label htmlFor="maxHeight">Max. height</Label>
<Label htmlFor="maxHeight">Max. height</Label> <Input
<Input id="maxHeight"
id="maxHeight" defaultValue="none"
defaultValue="none" className="col-span-2 h-8"
className="col-span-2 h-8" />
/>
</div>
</div> </div>
</div> </div>
</PopoverContent> </div>
</Popover> </PopoverContent>
</div> </Popover>
) )
} }

View File

@@ -8,24 +8,24 @@ export function ResizableDemo() {
return ( return (
<div className="flex w-full flex-col gap-6"> <div className="flex w-full flex-col gap-6">
<ResizablePanelGroup <ResizablePanelGroup
orientation="horizontal" direction="horizontal"
className="max-w-md rounded-lg border md:min-w-[450px]" className="max-w-md rounded-lg border md:min-w-[450px]"
> >
<ResizablePanel defaultSize="50%"> <ResizablePanel defaultSize={50}>
<div className="flex h-[200px] items-center justify-center p-6"> <div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">One</span> <span className="font-semibold">One</span>
</div> </div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle /> <ResizableHandle />
<ResizablePanel defaultSize="50%"> <ResizablePanel defaultSize={50}>
<ResizablePanelGroup orientation="vertical"> <ResizablePanelGroup direction="vertical">
<ResizablePanel defaultSize="25%"> <ResizablePanel defaultSize={25}>
<div className="flex h-full items-center justify-center p-6"> <div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Two</span> <span className="font-semibold">Two</span>
</div> </div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle /> <ResizableHandle />
<ResizablePanel defaultSize="75%"> <ResizablePanel defaultSize={75}>
<div className="flex h-full items-center justify-center p-6"> <div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Three</span> <span className="font-semibold">Three</span>
</div> </div>
@@ -34,32 +34,32 @@ export function ResizableDemo() {
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
<ResizablePanelGroup <ResizablePanelGroup
orientation="horizontal" direction="horizontal"
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]" className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
> >
<ResizablePanel defaultSize="25%"> <ResizablePanel defaultSize={25}>
<div className="flex h-full items-center justify-center p-6"> <div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span> <span className="font-semibold">Sidebar</span>
</div> </div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle />
<ResizablePanel defaultSize="75%"> <ResizablePanel defaultSize={75}>
<div className="flex h-full items-center justify-center p-6"> <div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span> <span className="font-semibold">Content</span>
</div> </div>
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
<ResizablePanelGroup <ResizablePanelGroup
orientation="vertical" direction="vertical"
className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]" className="min-h-[200px] max-w-md rounded-lg border md:min-w-[450px]"
> >
<ResizablePanel defaultSize="25%"> <ResizablePanel defaultSize={25}>
<div className="flex h-full items-center justify-center p-6"> <div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Header</span> <span className="font-semibold">Header</span>
</div> </div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle /> <ResizableHandle />
<ResizablePanel defaultSize="75%"> <ResizablePanel defaultSize={75}>
<div className="flex h-full items-center justify-center p-6"> <div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span> <span className="font-semibold">Content</span>
</div> </div>

View File

@@ -47,27 +47,6 @@ export function SheetDemo() {
</SheetFooter> </SheetFooter>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">No Close Button</Button>
</SheetTrigger>
<SheetContent showCloseButton={false}>
<SheetHeader>
<SheetTitle>Custom Close</SheetTitle>
<SheetDescription>
This sheet has no default close button. Use the footer buttons
instead.
</SheetDescription>
</SheetHeader>
<div className="flex-1 px-4" />
<SheetFooter>
<SheetClose asChild>
<Button variant="outline">Cancel</Button>
</SheetClose>
<Button type="submit">Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<div className="flex gap-2"> <div className="flex gap-2">
{SHEET_SIDES.map((side) => ( {SHEET_SIDES.map((side) => (
<Sheet key={side}> <Sheet key={side}>

View File

@@ -4,17 +4,6 @@ import { Switch } from "@/registry/new-york-v4/ui/switch"
export function SwitchDemo() { export function SwitchDemo() {
return ( return (
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{/* Sizes. */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Switch id="switch-demo-sm" size="sm" />
<Label htmlFor="switch-demo-sm">Small</Label>
</div>
<div className="flex items-center gap-2">
<Switch id="switch-demo-default" />
<Label htmlFor="switch-demo-default">Default</Label>
</div>
</div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch id="switch-demo-airplane-mode" /> <Switch id="switch-demo-airplane-mode" />
<Label htmlFor="switch-demo-airplane-mode">Airplane Mode</Label> <Label htmlFor="switch-demo-airplane-mode">Airplane Mode</Label>

View File

@@ -101,45 +101,6 @@ export function TabsDemo() {
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</Tabs> </Tabs>
{/* Line variant. */}
<Tabs defaultValue="preview">
<TabsList variant="line">
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
</Tabs>
{/* Vertical orientation. */}
<Tabs defaultValue="preview" orientation="vertical">
<TabsList>
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
</Tabs>
{/* Vertical orientation with line variant. */}
<Tabs defaultValue="preview" orientation="vertical">
<TabsList variant="line">
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
</Tabs>
</div> </div>
) )
} }

View File

@@ -1,13 +1,5 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
export function ComponentPreview({ children }: { children: React.ReactNode }) { export function ComponentPreview({ children }: { children: React.ReactNode }) {
return ( return <div className={cn("bg-background")}>{children}</div>
<div
className={cn(
"bg-background *:data-[slot=card]:has-[[data-slot=chart]]:shadow-none"
)}
>
{children}
</div>
)
} }

View File

@@ -4,16 +4,10 @@ import { type Metadata } from "next"
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { siteConfig } from "@/lib/config" import { siteConfig } from "@/lib/config"
import { import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
getDemoItem,
getRegistryComponent,
getRegistryItem,
} from "@/lib/registry"
import { absoluteUrl } from "@/lib/utils" import { absoluteUrl } from "@/lib/utils"
import { getStyle, legacyStyles, type Style } from "@/registry/_legacy-styles" import { getStyle, legacyStyles, type Style } from "@/registry/_legacy-styles"
import "@/styles/legacy-themes.css"
import { ComponentPreview } from "./component-preview" import { ComponentPreview } from "./component-preview"
export const revalidate = false export const revalidate = false
@@ -22,12 +16,7 @@ export const dynamicParams = false
const getCachedRegistryItem = React.cache( const getCachedRegistryItem = React.cache(
async (name: string, styleName: Style["name"]) => { async (name: string, styleName: Style["name"]) => {
// Try registry item first, then fallback to demo item (for examples). return await getRegistryItem(name, styleName)
const item = await getRegistryItem(name, styleName)
if (item) {
return item
}
return await getDemoItem(name, styleName)
} }
) )
@@ -84,54 +73,9 @@ export async function generateMetadata({
export async function generateStaticParams() { export async function generateStaticParams() {
const { Index } = await import("@/registry/__index__") const { Index } = await import("@/registry/__index__")
// const { Index: BasesIndex } = await import("@/registry/bases/__index__")
const { ExamplesIndex } = await import("@/examples/__index__")
const params: Array<{ style: string; name: string }> = [] const params: Array<{ style: string; name: string }> = []
for (const style of legacyStyles) { for (const style of legacyStyles) {
// Check if this is a base-prefixed style (e.g., base-nova, radix-nova).
const baseMatch = style.name.match(/^(base|radix)-/)
if (baseMatch) {
const baseName = baseMatch[1]
// Add examples from ExamplesIndex.
const examples = ExamplesIndex[baseName]
if (examples) {
for (const exampleName of Object.keys(examples)) {
if (exampleName.startsWith("sidebar-")) {
params.push({
style: style.name,
name: exampleName,
})
}
}
}
// // Add UI components from BasesIndex.
// const baseIndex = BasesIndex[baseName]
// if (baseIndex) {
// for (const itemName in baseIndex) {
// const item = baseIndex[itemName]
// if (
// [
// "registry:block",
// "registry:component",
// "registry:example",
// "registry:internal",
// ].includes(item.type)
// ) {
// params.push({
// style: style.name,
// name: item.name,
// })
// }
// }
// }
continue
}
// Handle legacy styles (e.g., new-york-v4).
if (!Index[style.name]) { if (!Index[style.name]) {
continue continue
} }

View File

@@ -9,9 +9,7 @@ import { ActiveThemeProvider } from "@/components/active-theme"
import { Analytics } from "@/components/analytics" import { Analytics } from "@/components/analytics"
import { TailwindIndicator } from "@/components/tailwind-indicator" import { TailwindIndicator } from "@/components/tailwind-indicator"
import { ThemeProvider } from "@/components/theme-provider" import { ThemeProvider } from "@/components/theme-provider"
import { TooltipProvider as BaseTooltipProvider } from "@/registry/bases/base/ui/tooltip"
import { Toaster } from "@/registry/bases/radix/ui/sonner" import { Toaster } from "@/registry/bases/radix/ui/sonner"
import { TooltipProvider as RadixTooltipProvider } from "@/registry/bases/radix/ui/tooltip"
import "@/styles/globals.css" import "@/styles/globals.css"
@@ -59,11 +57,6 @@ export const metadata: Metadata = {
apple: "/apple-touch-icon.png", apple: "/apple-touch-icon.png",
}, },
manifest: `${siteConfig.url}/site.webmanifest`, manifest: `${siteConfig.url}/site.webmanifest`,
alternates: {
types: {
"application/rss+xml": `${siteConfig.url}/rss.xml`,
},
},
} }
export default function RootLayout({ export default function RootLayout({
@@ -99,12 +92,8 @@ export default function RootLayout({
<LayoutProvider> <LayoutProvider>
<ActiveThemeProvider> <ActiveThemeProvider>
<NuqsAdapter> <NuqsAdapter>
<BaseTooltipProvider delay={0}> {children}
<RadixTooltipProvider delayDuration={0}> <Toaster position="top-center" />
{children}
<Toaster position="top-center" />
</RadixTooltipProvider>
</BaseTooltipProvider>
</NuqsAdapter> </NuqsAdapter>
<TailwindIndicator /> <TailwindIndicator />
<Analytics /> <Analytics />

View File

@@ -1,44 +0,0 @@
import { NextResponse } from "next/server"
import { getChangelogPages, type ChangelogPageData } from "@/lib/changelog"
import { siteConfig } from "@/lib/config"
export const revalidate = false
export async function GET() {
const pages = getChangelogPages()
const items = pages
.map((page) => {
const data = page.data as ChangelogPageData
const date = page.date?.toUTCString() ?? new Date().toUTCString()
const link = `${siteConfig.url}/docs/${page.slugs.join("/")}`
return ` <item>
<title><![CDATA[${data.title}]]></title>
<link>${link}</link>
<guid>${link}</guid>
<description><![CDATA[${data.description || ""}]]></description>
<pubDate>${date}</pubDate>
</item>`
})
.join("\n")
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${siteConfig.name} Changelog</title>
<link>${siteConfig.url}</link>
<description>${siteConfig.description}</description>
<language>en-us</language>
<atom:link href="${siteConfig.url}/rss.xml" rel="self" type="application/rss+xml"/>
${items}
</channel>
</rss>`
return new NextResponse(xml, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
},
})
}

View File

@@ -3,26 +3,12 @@ import { ArrowRightIcon } from "lucide-react"
import { Badge } from "@/registry/new-york-v4/ui/badge" import { Badge } from "@/registry/new-york-v4/ui/badge"
function BaseUILogo() {
return (
<svg width="17" height="24" viewBox="0 0 17 24" className="size-3">
<path
fill="currentColor"
d="M9.5001 7.01537C9.2245 6.99837 9 7.22385 9 7.49999V23C13.4183 23 17 19.4183 17 15C17 10.7497 13.6854 7.27351 9.5001 7.01537Z"
/>
<path
fill="currentColor"
d="M8 9.8V12V23C3.58172 23 0 19.0601 0 14.2V12V1C4.41828 1 8 4.93989 8 9.8Z"
/>
</svg>
)
}
export function Announcement() { export function Announcement() {
return ( return (
<Badge asChild variant="secondary" className="bg-muted"> <Badge asChild variant="secondary" className="bg-transparent">
<Link href="/docs/changelog/2026-01-rtl"> <Link href="/docs/changelog">
RTL Support <ArrowRightIcon /> <span className="flex size-2 rounded-full bg-blue-500" title="New" />
npx shadcn create <ArrowRightIcon />
</Link> </Link>
</Badge> </Badge>
) )

View File

@@ -16,7 +16,7 @@ import {
Tablet, Tablet,
Terminal, Terminal,
} from "lucide-react" } from "lucide-react"
import { type PanelImperativeHandle } from "react-resizable-panels" import { type ImperativePanelHandle } from "react-resizable-panels"
import { import {
type registryItemFileSchema, type registryItemFileSchema,
type registryItemSchema, type registryItemSchema,
@@ -68,7 +68,7 @@ type BlockViewerContext = {
setView: (view: "code" | "preview") => void setView: (view: "code" | "preview") => void
activeFile: string | null activeFile: string | null
setActiveFile: (file: string) => void setActiveFile: (file: string) => void
resizablePanelRef: React.RefObject<PanelImperativeHandle | null> | null resizablePanelRef: React.RefObject<ImperativePanelHandle | null> | null
tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null
highlightedFiles: highlightedFiles:
| (z.infer<typeof registryItemFileSchema> & { | (z.infer<typeof registryItemFileSchema> & {
@@ -101,7 +101,7 @@ function BlockViewerProvider({
const [activeFile, setActiveFile] = React.useState< const [activeFile, setActiveFile] = React.useState<
BlockViewerContext["activeFile"] BlockViewerContext["activeFile"]
>(highlightedFiles?.[0].target ?? null) >(highlightedFiles?.[0].target ?? null)
const resizablePanelRef = React.useRef<PanelImperativeHandle>(null) const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
const [iframeKey, setIframeKey] = React.useState(0) const [iframeKey, setIframeKey] = React.useState(0)
return ( return (
@@ -154,12 +154,12 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
value={view} value={view}
onValueChange={(value) => setView(value as "preview" | "code")} onValueChange={(value) => setView(value as "preview" | "code")}
> >
<TabsList className="grid h-8! grid-cols-2 items-center rounded-lg p-1 *:data-[slot=tabs-trigger]:h-6 *:data-[slot=tabs-trigger]:rounded-sm *:data-[slot=tabs-trigger]:px-2 *:data-[slot=tabs-trigger]:text-xs"> <TabsList className="grid h-8 grid-cols-2 items-center rounded-md p-1 *:data-[slot=tabs-trigger]:h-6 *:data-[slot=tabs-trigger]:rounded-sm *:data-[slot=tabs-trigger]:px-2 *:data-[slot=tabs-trigger]:text-xs">
<TabsTrigger value="preview">Preview</TabsTrigger> <TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="code">Code</TabsTrigger> <TabsTrigger value="code">Code</TabsTrigger>
</TabsList> </TabsList>
</Tabs> </Tabs>
<Separator orientation="vertical" className="mx-2 h-4!" /> <Separator orientation="vertical" className="mx-2 !h-4" />
<a <a
href={`#${item.name}`} href={`#${item.name}`}
className="flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left" className="flex-1 text-center text-sm font-medium underline-offset-2 hover:underline md:flex-auto md:text-left"
@@ -167,7 +167,7 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
{item.description?.replace(/\.$/, "")} {item.description?.replace(/\.$/, "")}
</a> </a>
<div className="ml-auto flex items-center gap-2"> <div className="ml-auto flex items-center gap-2">
<div className="h-8 items-center gap-1.5 rounded-md border p-[3px] shadow-none"> <div className="h-8 items-center gap-1.5 rounded-md border p-1 shadow-none">
<ToggleGroup <ToggleGroup
type="single" type="single"
defaultValue="100" defaultValue="100"
@@ -177,7 +177,7 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
resizablePanelRef.current.resize(parseInt(value)) resizablePanelRef.current.resize(parseInt(value))
} }
}} }}
className="gap-1 *:data-[slot=toggle-group-item]:size-6! *:data-[slot=toggle-group-item]:rounded-sm!" className="gap-1 *:data-[slot=toggle-group-item]:!size-6 *:data-[slot=toggle-group-item]:!rounded-sm"
> >
<ToggleGroupItem value="100" title="Desktop"> <ToggleGroupItem value="100" title="Desktop">
<Monitor /> <Monitor />
@@ -188,7 +188,7 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
<ToggleGroupItem value="30" title="Mobile"> <ToggleGroupItem value="30" title="Mobile">
<Smartphone /> <Smartphone />
</ToggleGroupItem> </ToggleGroupItem>
<Separator orientation="vertical" className="h-4!" /> <Separator orientation="vertical" className="!h-4" />
<Button <Button
size="icon" size="icon"
variant="ghost" variant="ghost"
@@ -201,7 +201,7 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
<Fullscreen /> <Fullscreen />
</Link> </Link>
</Button> </Button>
<Separator orientation="vertical" className="h-4!" /> <Separator orientation="vertical" className="!h-4" />
<Button <Button
size="icon" size="icon"
variant="ghost" variant="ghost"
@@ -218,7 +218,7 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
</Button> </Button>
</ToggleGroup> </ToggleGroup>
</div> </div>
<Separator orientation="vertical" className="mx-1 h-4!" /> <Separator orientation="vertical" className="mx-1 !h-4" />
<Button <Button
variant="outline" variant="outline"
className="w-fit gap-1 px-2 shadow-none" className="w-fit gap-1 px-2 shadow-none"
@@ -230,7 +230,7 @@ function BlockViewerToolbar({ styleName }: { styleName: Style["name"] }) {
{isCopied ? <Check /> : <Terminal />} {isCopied ? <Check /> : <Terminal />}
<span>npx shadcn add {item.name}</span> <span>npx shadcn add {item.name}</span>
</Button> </Button>
<Separator orientation="vertical" className="mx-1 h-4!" /> <Separator orientation="vertical" className="mx-1 !h-4" />
<OpenInV0Button name={item.name} /> <OpenInV0Button name={item.name} />
</div> </div>
</div> </div>
@@ -268,19 +268,19 @@ function BlockViewerView({ styleName }: { styleName: Style["name"] }) {
<div className="relative grid w-full gap-4"> <div className="relative grid w-full gap-4">
<div className="absolute inset-0 right-4 [background-image:radial-gradient(#d4d4d4_1px,transparent_1px)] [background-size:20px_20px] dark:[background-image:radial-gradient(#404040_1px,transparent_1px)]"></div> <div className="absolute inset-0 right-4 [background-image:radial-gradient(#d4d4d4_1px,transparent_1px)] [background-size:20px_20px] dark:[background-image:radial-gradient(#404040_1px,transparent_1px)]"></div>
<ResizablePanelGroup <ResizablePanelGroup
orientation="horizontal" direction="horizontal"
className="after:bg-surface/50 relative z-10 after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-xl" className="after:bg-surface/50 relative z-10 after:absolute after:inset-0 after:right-3 after:z-0 after:rounded-xl"
> >
<ResizablePanel <ResizablePanel
panelRef={resizablePanelRef} ref={resizablePanelRef}
className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl" className="bg-background relative aspect-[4/2.5] overflow-hidden rounded-lg border md:aspect-auto md:rounded-xl"
defaultSize="100%" defaultSize={100}
minSize="30%" minSize={30}
> >
<BlockViewerIframe styleName={styleName} /> <BlockViewerIframe styleName={styleName} />
</ResizablePanel> </ResizablePanel>
<ResizableHandle className="after:bg-border relative hidden w-3 bg-transparent p-0 after:absolute after:top-1/2 after:right-0 after:h-8 after:w-[6px] after:translate-x-[-1px] after:-translate-y-1/2 after:rounded-full after:transition-all after:hover:h-10 md:block" /> <ResizableHandle className="after:bg-border relative hidden w-3 bg-transparent p-0 after:absolute after:top-1/2 after:right-0 after:h-8 after:w-[6px] after:translate-x-[-1px] after:-translate-y-1/2 after:rounded-full after:transition-all after:hover:h-10 md:block" />
<ResizablePanel defaultSize="0%" minSize="0%" /> <ResizablePanel defaultSize={0} minSize={0} />
</ResizablePanelGroup> </ResizablePanelGroup>
</div> </div>
</div> </div>

View File

@@ -20,7 +20,7 @@ export function Callout({
<Alert <Alert
data-variant={variant} data-variant={variant}
className={cn( className={cn(
"bg-surface text-surface-foreground border-surface mt-6 w-auto rounded-xl md:-mx-1 **:[code]:border", "bg-background text-foreground mt-6 w-auto border md:-mx-1",
className className
)} )}
{...props} {...props}

View File

@@ -5,7 +5,6 @@ import { type z } from "zod"
import { highlightCode } from "@/lib/highlight-code" import { highlightCode } from "@/lib/highlight-code"
import { getRegistryItem } from "@/lib/registry" import { getRegistryItem } from "@/lib/registry"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { ChartIframe } from "@/components/chart-iframe"
import { ChartToolbar } from "@/components/chart-toolbar" import { ChartToolbar } from "@/components/chart-toolbar"
import { type Style } from "@/registry/_legacy-styles" import { type Style } from "@/registry/_legacy-styles"
@@ -13,43 +12,50 @@ export type Chart = z.infer<typeof registryItemSchema> & {
highlightedCode: string highlightedCode: string
} }
export function ChartDisplay({ export async function ChartDisplay({
chart, name,
style, styleName,
children,
className, className,
}: { }: {
chart: Chart name: string
style: string styleName: Style["name"]
} & React.ComponentProps<"div">) { } & React.ComponentProps<"div">) {
const chart = await getCachedRegistryItem(name, styleName)
const highlightedCode = await getChartHighlightedCode(
chart?.files?.[0]?.content ?? ""
)
if (!chart || !highlightedCode) {
return null
}
return ( return (
<div <div
className={cn( className={cn(
"themes-wrapper group relative flex flex-col overflow-hidden rounded-xl transition-all duration-200 ease-in-out hover:z-30", "themes-wrapper group relative flex flex-col overflow-hidden rounded-xl border transition-all duration-200 ease-in-out hover:z-30",
className className
)} )}
> >
<ChartToolbar <ChartToolbar
chart={chart} chart={{ ...chart, highlightedCode }}
className="relative z-20 flex justify-end px-3 py-2.5" className="bg-card text-card-foreground relative z-20 flex justify-end border-b px-3 py-2.5"
/> >
<div className="bg-background relative z-10 overflow-hidden rounded-xl"> {children}
<ChartIframe </ChartToolbar>
src={`/view/${style}/${chart.name}`} <div className="relative z-10 [&>div]:rounded-none [&>div]:border-none [&>div]:shadow-none">
height={460} {children}
title={chart.name}
/>
</div> </div>
</div> </div>
) )
} }
// Exported for parallel prefetching in page components. const getCachedRegistryItem = React.cache(
export const getCachedRegistryItem = React.cache(
async (name: string, styleName: Style["name"]) => { async (name: string, styleName: Style["name"]) => {
return await getRegistryItem(name, styleName) return await getRegistryItem(name, styleName)
} }
) )
export const getChartHighlightedCode = React.cache(async (content: string) => { const getChartHighlightedCode = React.cache(async (content: string) => {
return await highlightCode(content) return await highlightCode(content)
}) })

View File

@@ -1,31 +0,0 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
export function ChartIframe({
src,
height,
title,
}: {
src: string
height: number
title: string
}) {
const [loaded, setLoaded] = React.useState(false)
return (
<iframe
src={src}
className={cn(
"w-full border-none transition-opacity duration-300",
loaded ? "opacity-100" : "opacity-0"
)}
height={height}
loading="lazy"
title={title}
onLoad={() => setLoaded(true)}
/>
)
}

View File

@@ -46,7 +46,7 @@ export function ChartToolbar({
} }
function ChartTitle({ chart }: { chart: Chart }) { function ChartTitle({ chart }: { chart: Chart }) {
if (chart.name.includes("chart-line")) { if (chart.name.includes("charts-line")) {
return ( return (
<> <>
<LineChartIcon /> Line Chart <LineChartIcon /> Line Chart

View File

@@ -88,7 +88,7 @@ export function CodeBlockCommand({
<TabsTrigger <TabsTrigger
key={key} key={key}
value={key} value={key}
className="data-[state=active]:bg-background! data-[state=active]:border-input h-7 border border-transparent pt-0.5 shadow-none!" className="data-[state=active]:bg-accent data-[state=active]:border-input h-7 border border-transparent pt-0.5 data-[state=active]:shadow-none"
> >
{key} {key}
</TabsTrigger> </TabsTrigger>
@@ -113,16 +113,23 @@ export function CodeBlockCommand({
})} })}
</div> </div>
</Tabs> </Tabs>
<Button <Tooltip>
data-slot="copy-button" <TooltipTrigger asChild>
size="icon" <Button
variant="ghost" data-slot="copy-button"
className="absolute top-2 right-2 z-10 size-7 opacity-70 hover:opacity-100 focus-visible:opacity-100" size="icon"
onClick={copyCommand} variant="ghost"
> className="absolute top-2 right-2 z-10 size-7 opacity-70 hover:opacity-100 focus-visible:opacity-100"
<span className="sr-only">Copy</span> onClick={copyCommand}
{hasCopied ? <IconCheck /> : <IconCopy />} >
</Button> <span className="sr-only">Copy</span>
{hasCopied ? <IconCheck /> : <IconCopy />}
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied" : "Copy to Clipboard"}
</TooltipContent>
</Tooltip>
</div> </div>
) )
} }

View File

@@ -39,7 +39,7 @@ export function CodeCollapsibleWrapper({
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent <CollapsibleContent
forceMount forceMount
className="relative mt-6 overflow-hidden data-[state=closed]:max-h-64 data-[state=closed]:[content-visibility:auto] [&>figure]:mt-0 [&>figure]:md:!mx-0" className="relative mt-6 overflow-hidden data-[state=closed]:max-h-64 [&>figure]:mt-0 [&>figure]:md:!mx-0"
> >
{children} {children}
</CollapsibleContent> </CollapsibleContent>

View File

@@ -18,7 +18,7 @@ export function CodeTabs({ children }: React.ComponentProps<typeof Tabs>) {
onValueChange={(value) => onValueChange={(value) =>
setConfig({ ...config, installationType: value as "cli" | "manual" }) setConfig({ ...config, installationType: value as "cli" | "manual" })
} }
className="relative mt-6 w-full *:data-[slot=tabs-list]:gap-6" className="relative mt-6 w-full"
> >
{children} {children}
</Tabs> </Tabs>

Some files were not shown because too many files have changed in this diff Show More