mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-15 03:41:33 +00:00
Compare commits
2 Commits
shadcn@3.8
...
fix/cli-va
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbf7b3708a | ||
|
|
9da12c13f9 |
5
.changeset/rude-clowns-retire.md
Normal file
5
.changeset/rude-clowns-retire.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"shadcn": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
validate app name on create
|
||||||
63
.github/ISSUE_TEMPLATE/registry_directory.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/registry_directory.yml
vendored
Normal 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
|
||||||
10
.github/changeset-version.js
vendored
10
.github/changeset-version.js
vendored
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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,17 +8,16 @@ 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 (
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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,7 +190,7 @@ 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
|
||||||
@@ -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>
|
||||||
@@ -301,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>
|
||||||
@@ -337,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
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}`}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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>({
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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"
|
||||||
@@ -77,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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ 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,
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -87,34 +81,35 @@ 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 />
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,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({
|
||||||
|
|||||||
@@ -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",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { usePathname, useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
import { IconArrowRight } from "@tabler/icons-react"
|
import { IconArrowRight } from "@tabler/icons-react"
|
||||||
import { useDocsSearch } from "fumadocs-core/search/client"
|
import { useDocsSearch } from "fumadocs-core/search/client"
|
||||||
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
|
import { CornerDownLeftIcon, SquareDashedIcon } from "lucide-react"
|
||||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { type Color, type ColorPalette } from "@/lib/colors"
|
import { type Color, type ColorPalette } from "@/lib/colors"
|
||||||
import { trackEvent } from "@/lib/events"
|
import { trackEvent } from "@/lib/events"
|
||||||
import { showMcpDocs } from "@/lib/flags"
|
import { showMcpDocs } from "@/lib/flags"
|
||||||
import { getCurrentBase, getPagesFromFolder } from "@/lib/page-tree"
|
|
||||||
import { type source } from "@/lib/source"
|
import { type source } from "@/lib/source"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useConfig } from "@/hooks/use-config"
|
import { useConfig } from "@/hooks/use-config"
|
||||||
@@ -27,13 +26,13 @@ import {
|
|||||||
} from "@/registry/new-york-v4/ui/command"
|
} from "@/registry/new-york-v4/ui/command"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogOverlay,
|
|
||||||
DialogPortal,
|
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/registry/new-york-v4/ui/dialog"
|
} from "@/registry/new-york-v4/ui/dialog"
|
||||||
|
import { Kbd, KbdGroup } from "@/registry/new-york-v4/ui/kbd"
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
||||||
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
|
import { Spinner } from "@/registry/new-york-v4/ui/spinner"
|
||||||
|
|
||||||
@@ -43,18 +42,15 @@ export function CommandMenu({
|
|||||||
blocks,
|
blocks,
|
||||||
navItems,
|
navItems,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Dialog> & {
|
}: DialogProps & {
|
||||||
tree: typeof source.pageTree
|
tree: typeof source.pageTree
|
||||||
colors: ColorPalette[]
|
colors: ColorPalette[]
|
||||||
blocks?: { name: string; description: string; categories: string[] }[]
|
blocks?: { name: string; description: string; categories: string[] }[]
|
||||||
navItems?: { href: string; label: string }[]
|
navItems?: { href: string; label: string }[]
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
|
||||||
const [config] = useConfig()
|
const [config] = useConfig()
|
||||||
const currentBase = getCurrentBase(pathname)
|
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
const [renderDelayedGroups, setRenderDelayedGroups] = React.useState(false)
|
|
||||||
const [selectedType, setSelectedType] = React.useState<
|
const [selectedType, setSelectedType] = React.useState<
|
||||||
"color" | "page" | "component" | "block" | null
|
"color" | "page" | "component" | "block" | null
|
||||||
>(null)
|
>(null)
|
||||||
@@ -94,30 +90,14 @@ export function CommandMenu({
|
|||||||
|
|
||||||
// Set new timeout to debounce both search and tracking.
|
// Set new timeout to debounce both search and tracking.
|
||||||
searchTimeoutRef.current = setTimeout(() => {
|
searchTimeoutRef.current = setTimeout(() => {
|
||||||
React.startTransition(() => {
|
setSearch(value)
|
||||||
setSearch(value)
|
trackSearchQuery(value)
|
||||||
trackSearchQuery(value)
|
|
||||||
})
|
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
[setSearch, trackSearchQuery]
|
[setSearch, trackSearchQuery]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cleanup timeout on unmount.
|
// Cleanup timeout on unmount.
|
||||||
React.useEffect(() => {
|
|
||||||
if (open) {
|
|
||||||
const frame = requestAnimationFrame(() => {
|
|
||||||
setRenderDelayedGroups(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelAnimationFrame(frame)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setRenderDelayedGroups(false)
|
|
||||||
}, [open])
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (searchTimeoutRef.current) {
|
if (searchTimeoutRef.current) {
|
||||||
@@ -126,17 +106,6 @@ export function CommandMenu({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const commandFilter = React.useCallback(
|
|
||||||
(value: string, searchValue: string, keywords?: string[]) => {
|
|
||||||
const extendValue = value + " " + (keywords?.join(" ") || "")
|
|
||||||
if (extendValue.toLowerCase().includes(searchValue.toLowerCase())) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handlePageHighlight = React.useCallback(
|
const handlePageHighlight = React.useCallback(
|
||||||
(isComponent: boolean, item: { url: string; name?: React.ReactNode }) => {
|
(isComponent: boolean, item: { url: string; name?: React.ReactNode }) => {
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
@@ -169,175 +138,10 @@ export function CommandMenu({
|
|||||||
[setSelectedType, setCopyPayload, packageManager]
|
[setSelectedType, setCopyPayload, packageManager]
|
||||||
)
|
)
|
||||||
|
|
||||||
const runCommand = React.useCallback(
|
const runCommand = React.useCallback((command: () => unknown) => {
|
||||||
(command: () => unknown) => {
|
setOpen(false)
|
||||||
setOpen(false)
|
command()
|
||||||
command()
|
}, [])
|
||||||
},
|
|
||||||
[setOpen]
|
|
||||||
)
|
|
||||||
|
|
||||||
const navItemsSection = React.useMemo(() => {
|
|
||||||
if (!navItems || navItems.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandGroup
|
|
||||||
heading="Pages"
|
|
||||||
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
|
|
||||||
>
|
|
||||||
{navItems.map((item) => (
|
|
||||||
<CommandMenuItem
|
|
||||||
key={item.href}
|
|
||||||
value={`Navigation ${item.label}`}
|
|
||||||
keywords={["nav", "navigation", item.label.toLowerCase()]}
|
|
||||||
onHighlight={() => {
|
|
||||||
setSelectedType("page")
|
|
||||||
setCopyPayload("")
|
|
||||||
}}
|
|
||||||
onSelect={() => {
|
|
||||||
runCommand(() => router.push(item.href))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconArrowRight />
|
|
||||||
{item.label}
|
|
||||||
</CommandMenuItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
)
|
|
||||||
}, [navItems, runCommand, router])
|
|
||||||
|
|
||||||
const pageGroupsSection = React.useMemo(() => {
|
|
||||||
return tree.children.map((group) => {
|
|
||||||
if (group.type !== "folder") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const pages = getPagesFromFolder(group, currentBase).filter((item) => {
|
|
||||||
if (!showMcpDocs && item.url.includes("/mcp")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (pages.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandGroup
|
|
||||||
key={group.$id}
|
|
||||||
heading={group.name}
|
|
||||||
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
|
|
||||||
>
|
|
||||||
{pages.map((item) => {
|
|
||||||
const isComponent = item.url.includes("/components/")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandMenuItem
|
|
||||||
key={item.url}
|
|
||||||
value={
|
|
||||||
item.name?.toString() ? `${group.name} ${item.name}` : ""
|
|
||||||
}
|
|
||||||
keywords={isComponent ? ["component"] : undefined}
|
|
||||||
onHighlight={() => handlePageHighlight(isComponent, item)}
|
|
||||||
onSelect={() => {
|
|
||||||
runCommand(() => router.push(item.url))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isComponent ? (
|
|
||||||
<div className="border-muted-foreground aspect-square size-4 rounded-full border border-dashed" />
|
|
||||||
) : (
|
|
||||||
<IconArrowRight />
|
|
||||||
)}
|
|
||||||
{item.name}
|
|
||||||
</CommandMenuItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</CommandGroup>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}, [tree.children, currentBase, handlePageHighlight, runCommand, router])
|
|
||||||
|
|
||||||
const colorGroupsSection = React.useMemo(() => {
|
|
||||||
return colors.map((colorPalette) => (
|
|
||||||
<CommandGroup
|
|
||||||
key={colorPalette.name}
|
|
||||||
heading={
|
|
||||||
colorPalette.name.charAt(0).toUpperCase() + colorPalette.name.slice(1)
|
|
||||||
}
|
|
||||||
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
|
|
||||||
>
|
|
||||||
{colorPalette.colors.map((color) => (
|
|
||||||
<CommandMenuItem
|
|
||||||
key={color.hex}
|
|
||||||
value={color.className}
|
|
||||||
keywords={["color", color.name, color.className]}
|
|
||||||
onHighlight={() => handleColorHighlight(color)}
|
|
||||||
onSelect={() => {
|
|
||||||
runCommand(() =>
|
|
||||||
copyToClipboardWithMeta(color.oklch, {
|
|
||||||
name: "copy_color",
|
|
||||||
properties: { color: color.oklch },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="border-ghost aspect-square size-4 rounded-sm bg-(--color) after:rounded-sm"
|
|
||||||
style={{ "--color": color.oklch } as React.CSSProperties}
|
|
||||||
/>
|
|
||||||
{color.className}
|
|
||||||
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
|
|
||||||
{color.oklch}
|
|
||||||
</span>
|
|
||||||
</CommandMenuItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
))
|
|
||||||
}, [colors, handleColorHighlight, runCommand])
|
|
||||||
|
|
||||||
const blocksSection = React.useMemo(() => {
|
|
||||||
if (!blocks || blocks.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandGroup
|
|
||||||
heading="Blocks"
|
|
||||||
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
|
|
||||||
>
|
|
||||||
{blocks.map((block) => (
|
|
||||||
<CommandMenuItem
|
|
||||||
key={block.name}
|
|
||||||
value={block.name}
|
|
||||||
onHighlight={() => {
|
|
||||||
handleBlockHighlight(block)
|
|
||||||
}}
|
|
||||||
keywords={[
|
|
||||||
"block",
|
|
||||||
block.name,
|
|
||||||
block.description,
|
|
||||||
...block.categories,
|
|
||||||
]}
|
|
||||||
onSelect={() => {
|
|
||||||
runCommand(() =>
|
|
||||||
router.push(`/blocks/${block.categories[0]}#${block.name}`)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SquareDashedIcon />
|
|
||||||
{block.description}
|
|
||||||
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
|
|
||||||
{block.name}
|
|
||||||
</span>
|
|
||||||
</CommandMenuItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
)
|
|
||||||
}, [blocks, handleBlockHighlight, runCommand, router])
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
@@ -391,29 +195,39 @@ export function CommandMenu({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground dark:bg-card hover:bg-muted/50 relative h-8 w-full justify-start rounded-lg pl-3 font-normal shadow-none sm:pr-12 md:w-48 lg:w-56 xl:w-64"
|
"text-foreground dark:bg-card hover:bg-muted/50 relative h-8 w-full justify-start pl-3 font-normal shadow-none sm:pr-12 md:w-48 lg:w-56 xl:w-64"
|
||||||
)}
|
)}
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="hidden lg:inline-flex">Search documentation...</span>
|
<span className="hidden lg:inline-flex">Search documentation...</span>
|
||||||
<span className="inline-flex lg:hidden">Search...</span>
|
<span className="inline-flex lg:hidden">Search...</span>
|
||||||
|
<div className="absolute top-1.5 right-1.5 hidden gap-1 group-has-[[data-slot=designer]]/body:hidden sm:flex">
|
||||||
|
<Kbd>⌘K</Kbd>
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="rounded-xl border-none bg-clip-padding p-2 pb-11 shadow-2xl ring-4 ring-neutral-200/80 dark:bg-neutral-900 dark:ring-neutral-800">
|
<DialogContent
|
||||||
|
showCloseButton={false}
|
||||||
|
className="rounded-xl border-none bg-clip-padding p-2 pb-11 shadow-2xl ring-4 ring-neutral-200/80 dark:bg-neutral-900 dark:ring-neutral-800"
|
||||||
|
>
|
||||||
<DialogHeader className="sr-only">
|
<DialogHeader className="sr-only">
|
||||||
<DialogTitle>Search documentation...</DialogTitle>
|
<DialogTitle>Search documentation...</DialogTitle>
|
||||||
<DialogDescription>Search for a command to run...</DialogDescription>
|
<DialogDescription>Search for a command to run...</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Command
|
<Command
|
||||||
className="**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border"
|
className="**:data-[slot=command-input-wrapper]:bg-input/50 **:data-[slot=command-input-wrapper]:border-input rounded-none bg-transparent **:data-[slot=command-input]:!h-9 **:data-[slot=command-input]:py-0 **:data-[slot=command-input-wrapper]:mb-0 **:data-[slot=command-input-wrapper]:!h-9 **:data-[slot=command-input-wrapper]:rounded-md **:data-[slot=command-input-wrapper]:border"
|
||||||
filter={commandFilter}
|
filter={(value, search, keywords) => {
|
||||||
|
handleSearchChange(search)
|
||||||
|
const extendValue = value + " " + (keywords?.join(" ") || "")
|
||||||
|
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<CommandInput
|
<CommandInput placeholder="Search documentation..." />
|
||||||
placeholder="Search documentation..."
|
|
||||||
onValueChange={handleSearchChange}
|
|
||||||
/>
|
|
||||||
{query.isLoading && (
|
{query.isLoading && (
|
||||||
<div className="pointer-events-none absolute top-1/2 right-3 z-10 flex -translate-y-1/2 items-center justify-center">
|
<div className="pointer-events-none absolute top-1/2 right-3 z-10 flex -translate-y-1/2 items-center justify-center">
|
||||||
<Spinner className="text-muted-foreground size-4" />
|
<Spinner className="text-muted-foreground size-4" />
|
||||||
@@ -424,19 +238,151 @@ export function CommandMenu({
|
|||||||
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
|
<CommandEmpty className="text-muted-foreground py-12 text-center text-sm">
|
||||||
{query.isLoading ? "Searching..." : "No results found."}
|
{query.isLoading ? "Searching..." : "No results found."}
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
{navItemsSection}
|
{navItems && navItems.length > 0 && (
|
||||||
{renderDelayedGroups ? (
|
<CommandGroup
|
||||||
<>
|
heading="Pages"
|
||||||
{pageGroupsSection}
|
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
|
||||||
{colorGroupsSection}
|
>
|
||||||
{blocksSection}
|
{navItems.map((item) => (
|
||||||
<SearchResults
|
<CommandMenuItem
|
||||||
setOpen={setOpen}
|
key={item.href}
|
||||||
query={query}
|
value={`Navigation ${item.label}`}
|
||||||
search={search}
|
keywords={["nav", "navigation", item.label.toLowerCase()]}
|
||||||
/>
|
onHighlight={() => {
|
||||||
</>
|
setSelectedType("page")
|
||||||
|
setCopyPayload("")
|
||||||
|
}}
|
||||||
|
onSelect={() => {
|
||||||
|
runCommand(() => router.push(item.href))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconArrowRight />
|
||||||
|
{item.label}
|
||||||
|
</CommandMenuItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
|
{tree.children.map((group) => (
|
||||||
|
<CommandGroup
|
||||||
|
key={group.$id}
|
||||||
|
heading={group.name}
|
||||||
|
className="!p-0 [&_[cmdk-group-heading]]:scroll-mt-16 [&_[cmdk-group-heading]]:!p-3 [&_[cmdk-group-heading]]:!pb-1"
|
||||||
|
>
|
||||||
|
{group.type === "folder" &&
|
||||||
|
group.children.map((item) => {
|
||||||
|
if (item.type === "page") {
|
||||||
|
const isComponent = item.url.includes("/components/")
|
||||||
|
|
||||||
|
if (!showMcpDocs && item.url.includes("/mcp")) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandMenuItem
|
||||||
|
key={item.url}
|
||||||
|
value={
|
||||||
|
item.name?.toString()
|
||||||
|
? `${group.name} ${item.name}`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
keywords={isComponent ? ["component"] : undefined}
|
||||||
|
onHighlight={() =>
|
||||||
|
handlePageHighlight(isComponent, item)
|
||||||
|
}
|
||||||
|
onSelect={() => {
|
||||||
|
runCommand(() => router.push(item.url))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isComponent ? (
|
||||||
|
<div className="border-muted-foreground aspect-square size-4 rounded-full border border-dashed" />
|
||||||
|
) : (
|
||||||
|
<IconArrowRight />
|
||||||
|
)}
|
||||||
|
{item.name}
|
||||||
|
</CommandMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
|
{colors.map((colorPalette) => (
|
||||||
|
<CommandGroup
|
||||||
|
key={colorPalette.name}
|
||||||
|
heading={
|
||||||
|
colorPalette.name.charAt(0).toUpperCase() +
|
||||||
|
colorPalette.name.slice(1)
|
||||||
|
}
|
||||||
|
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
|
||||||
|
>
|
||||||
|
{colorPalette.colors.map((color) => (
|
||||||
|
<CommandMenuItem
|
||||||
|
key={color.hex}
|
||||||
|
value={color.className}
|
||||||
|
keywords={["color", color.name, color.className]}
|
||||||
|
onHighlight={() => handleColorHighlight(color)}
|
||||||
|
onSelect={() => {
|
||||||
|
runCommand(() =>
|
||||||
|
copyToClipboardWithMeta(color.oklch, {
|
||||||
|
name: "copy_color",
|
||||||
|
properties: { color: color.oklch },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="border-ghost aspect-square size-4 rounded-sm bg-(--color) after:rounded-sm"
|
||||||
|
style={{ "--color": color.oklch } as React.CSSProperties}
|
||||||
|
/>
|
||||||
|
{color.className}
|
||||||
|
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
|
||||||
|
{color.oklch}
|
||||||
|
</span>
|
||||||
|
</CommandMenuItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
|
{blocks?.length ? (
|
||||||
|
<CommandGroup
|
||||||
|
heading="Blocks"
|
||||||
|
className="!p-0 [&_[cmdk-group-heading]]:!p-3"
|
||||||
|
>
|
||||||
|
{blocks.map((block) => (
|
||||||
|
<CommandMenuItem
|
||||||
|
key={block.name}
|
||||||
|
value={block.name}
|
||||||
|
onHighlight={() => {
|
||||||
|
handleBlockHighlight(block)
|
||||||
|
}}
|
||||||
|
keywords={[
|
||||||
|
"block",
|
||||||
|
block.name,
|
||||||
|
block.description,
|
||||||
|
...block.categories,
|
||||||
|
]}
|
||||||
|
onSelect={() => {
|
||||||
|
runCommand(() =>
|
||||||
|
router.push(
|
||||||
|
`/blocks/${block.categories[0]}#${block.name}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SquareDashedIcon />
|
||||||
|
{block.description}
|
||||||
|
<span className="text-muted-foreground ml-auto font-mono text-xs font-normal tabular-nums">
|
||||||
|
{block.name}
|
||||||
|
</span>
|
||||||
|
</CommandMenuItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
) : null}
|
) : null}
|
||||||
|
<SearchResults
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
query={query}
|
||||||
|
search={search}
|
||||||
|
/>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
<div className="text-muted-foreground absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 text-xs font-medium dark:border-t-neutral-700 dark:bg-neutral-800">
|
<div className="text-muted-foreground absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 text-xs font-medium dark:border-t-neutral-700 dark:bg-neutral-800">
|
||||||
@@ -522,24 +468,23 @@ function SearchResults({
|
|||||||
query,
|
query,
|
||||||
search,
|
search,
|
||||||
}: {
|
}: {
|
||||||
|
open: boolean
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void
|
||||||
query: Query
|
query: Query
|
||||||
search: string
|
search: string
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const uniqueResults = React.useMemo(() => {
|
const uniqueResults =
|
||||||
if (!query.data || !Array.isArray(query.data)) {
|
query.data && Array.isArray(query.data)
|
||||||
return []
|
? query.data.filter(
|
||||||
}
|
(item, index, self) =>
|
||||||
|
!(
|
||||||
return query.data.filter(
|
item.type === "text" &&
|
||||||
(item, index, self) =>
|
item.content.trim().split(/\s+/).length <= 1
|
||||||
!(
|
) && index === self.findIndex((t) => t.content === item.content)
|
||||||
item.type === "text" && item.content.trim().split(/\s+/).length <= 1
|
)
|
||||||
) && index === self.findIndex((t) => t.content === item.content)
|
: []
|
||||||
)
|
|
||||||
}, [query.data])
|
|
||||||
|
|
||||||
if (!search.trim()) {
|
if (!search.trim()) {
|
||||||
return null
|
return null
|
||||||
@@ -578,27 +523,3 @@ function SearchResults({
|
|||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogContent({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
||||||
showCloseButton?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DialogPortal data-slot="dialog-portal">
|
|
||||||
{/* <DialogOverlay /> */}
|
|
||||||
<DialogPrimitive.Content
|
|
||||||
data-slot="dialog-content"
|
|
||||||
className={cn(
|
|
||||||
"bg-background fixed top-1/3 left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</DialogPrimitive.Content>
|
|
||||||
</DialogPortal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,267 +1,51 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import Link from "next/link"
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/examples/base/ui/popover"
|
|
||||||
import { IconAlertCircle } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import {
|
|
||||||
LanguageProvider,
|
|
||||||
LanguageSelector,
|
|
||||||
useLanguageContext,
|
|
||||||
useTranslation,
|
|
||||||
type Translations,
|
|
||||||
} from "@/components/language-selector"
|
|
||||||
import { DirectionProvider as BaseDirectionProvider } from "@/registry/bases/base/ui/direction"
|
|
||||||
import { DirectionProvider as RadixDirectionProvider } from "@/registry/bases/radix/ui/direction"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
|
|
||||||
export function ComponentPreviewTabs({
|
export function ComponentPreviewTabs({
|
||||||
className,
|
className,
|
||||||
previewClassName,
|
|
||||||
align = "center",
|
align = "center",
|
||||||
hideCode = false,
|
hideCode = false,
|
||||||
chromeLessOnMobile = false,
|
chromeLessOnMobile = false,
|
||||||
component,
|
component,
|
||||||
source,
|
source,
|
||||||
sourcePreview,
|
|
||||||
direction = "ltr",
|
|
||||||
styleName,
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
previewClassName?: string
|
|
||||||
align?: "center" | "start" | "end"
|
align?: "center" | "start" | "end"
|
||||||
hideCode?: boolean
|
hideCode?: boolean
|
||||||
chromeLessOnMobile?: boolean
|
chromeLessOnMobile?: boolean
|
||||||
component: React.ReactNode
|
component: React.ReactNode
|
||||||
source: React.ReactNode
|
source: React.ReactNode
|
||||||
sourcePreview?: React.ReactNode
|
|
||||||
direction?: "ltr" | "rtl"
|
|
||||||
styleName?: string
|
|
||||||
}) {
|
}) {
|
||||||
const [isMobileCodeVisible, setIsMobileCodeVisible] = React.useState(false)
|
|
||||||
const base = styleName?.split("-")[0]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="component-preview"
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative mt-4 mb-12 flex flex-col overflow-hidden rounded-xl border",
|
"group relative mt-4 mb-12 flex flex-col gap-2 rounded-lg border",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{direction === "rtl" ? (
|
<div data-slot="preview">
|
||||||
<LanguageProvider defaultLanguage="ar">
|
|
||||||
<div className="flex h-16 items-center border-b px-4">
|
|
||||||
<RtlLanguageSelector />
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon-sm"
|
|
||||||
className="ml-auto size-7"
|
|
||||||
>
|
|
||||||
<IconAlertCircle />
|
|
||||||
<span className="sr-only">Toggle</span>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
></PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
side="bottom"
|
|
||||||
align="end"
|
|
||||||
className="w-56 text-xs"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
I used AI to translate the text for demonstration purposes.
|
|
||||||
It's not perfect and may contain errors.
|
|
||||||
</div>
|
|
||||||
<Separator className="-mx-2.5 w-auto!" />
|
|
||||||
<div data-lang="ar">
|
|
||||||
لقد استخدمت الذكاء الاصطناعي لترجمة النص للأغراض التجريبية
|
|
||||||
فقط. قد لا تكون الترجمة دقيقة وقد تحتوي على أخطاء.
|
|
||||||
</div>
|
|
||||||
<Separator className="-mx-2.5 w-auto!" />
|
|
||||||
<div data-lang="he">
|
|
||||||
השתמשתי בבינה מלאכותית כדי לתרגם את הטקסט למטרות הדגמה. זה לא
|
|
||||||
מושלם ויכול להכיל שגיאות.
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<PreviewWrapper
|
|
||||||
align={align}
|
|
||||||
chromeLessOnMobile={chromeLessOnMobile}
|
|
||||||
previewClassName={previewClassName}
|
|
||||||
>
|
|
||||||
<DirectionProviderWrapper base={base}>
|
|
||||||
{component}
|
|
||||||
</DirectionProviderWrapper>
|
|
||||||
</PreviewWrapper>
|
|
||||||
</LanguageProvider>
|
|
||||||
) : (
|
|
||||||
<DirectionProviderWrapper base={base} dir="ltr">
|
|
||||||
<PreviewWrapper
|
|
||||||
align={align}
|
|
||||||
chromeLessOnMobile={chromeLessOnMobile}
|
|
||||||
previewClassName={previewClassName}
|
|
||||||
dir="ltr"
|
|
||||||
>
|
|
||||||
{component}
|
|
||||||
</PreviewWrapper>
|
|
||||||
</DirectionProviderWrapper>
|
|
||||||
)}
|
|
||||||
{!hideCode && (
|
|
||||||
<div
|
<div
|
||||||
data-slot="code"
|
data-align={align}
|
||||||
data-mobile-code-visible={isMobileCodeVisible}
|
className={cn(
|
||||||
className="relative overflow-hidden **:data-[slot=copy-button]:right-4 **:data-[slot=copy-button]:hidden data-[mobile-code-visible=true]:**:data-[slot=copy-button]:flex [&_[data-rehype-pretty-code-figure]]:!m-0 [&_[data-rehype-pretty-code-figure]]:rounded-t-none [&_[data-rehype-pretty-code-figure]]:border-t [&_pre]:max-h-72"
|
"preview flex w-full justify-center data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start",
|
||||||
>
|
chromeLessOnMobile ? "sm:p-10" : "h-[450px] p-10"
|
||||||
{isMobileCodeVisible ? (
|
|
||||||
<>
|
|
||||||
{direction === "rtl" && (
|
|
||||||
<div className="bg-code text-muted-foreground no-scrollbar relative z-10 overflow-x-auto border-t p-6 font-mono text-sm">
|
|
||||||
<pre>{`// You will notice this example uses dir and data-lang attributes.
|
|
||||||
// This is because this site is not RTL by default.
|
|
||||||
// In your application, you won't need these.`}</pre>
|
|
||||||
<span>
|
|
||||||
{"// See the "}
|
|
||||||
<Link
|
|
||||||
href="/docs/rtl"
|
|
||||||
className="underline underline-offset-4"
|
|
||||||
>
|
|
||||||
RTL guide
|
|
||||||
</Link>
|
|
||||||
{" for more information."}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{source}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="relative">
|
|
||||||
{sourcePreview}
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center pb-4">
|
|
||||||
<div
|
|
||||||
className="absolute inset-0"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
"linear-gradient(to top, var(--color-code), color-mix(in oklab, var(--color-code) 60%, transparent), transparent)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="bg-background text-foreground dark:bg-background dark:text-foreground hover:bg-muted dark:hover:bg-muted relative z-10 rounded-lg shadow-none"
|
|
||||||
onClick={() => {
|
|
||||||
setIsMobileCodeVisible(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View Code
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
{component}
|
||||||
</div>
|
</div>
|
||||||
)}
|
{!hideCode && (
|
||||||
</div>
|
<div
|
||||||
)
|
data-slot="code"
|
||||||
}
|
className="overflow-hidden [&_[data-rehype-pretty-code-figure]]:!m-0 [&_[data-rehype-pretty-code-figure]]:rounded-t-none [&_[data-rehype-pretty-code-figure]]:border-t [&_pre]:max-h-[400px]"
|
||||||
|
>
|
||||||
const directionTranslations: Translations<Record<string, never>> = {
|
{source}
|
||||||
en: {
|
</div>
|
||||||
dir: "ltr",
|
|
||||||
values: {},
|
|
||||||
},
|
|
||||||
ar: {
|
|
||||||
dir: "rtl",
|
|
||||||
values: {},
|
|
||||||
},
|
|
||||||
he: {
|
|
||||||
dir: "rtl",
|
|
||||||
values: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function RtlLanguageSelector({ className }: { className?: string }) {
|
|
||||||
const context = useLanguageContext()
|
|
||||||
if (!context) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<LanguageSelector
|
|
||||||
value={context.language}
|
|
||||||
onValueChange={context.setLanguage}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PreviewWrapper({
|
|
||||||
align,
|
|
||||||
chromeLessOnMobile,
|
|
||||||
previewClassName,
|
|
||||||
dir: explicitDir,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
align: "center" | "start" | "end"
|
|
||||||
chromeLessOnMobile: boolean
|
|
||||||
previewClassName?: string
|
|
||||||
dir?: "ltr" | "rtl"
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
// useTranslation handles the case when there's no LanguageProvider context.
|
|
||||||
// It will fall back to local state with defaultLanguage.
|
|
||||||
const translation = useTranslation(directionTranslations, "ar")
|
|
||||||
const dir = explicitDir ?? translation.dir
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="preview"
|
|
||||||
dir={dir}
|
|
||||||
data-lang={dir === "rtl" ? translation.language : undefined}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
data-align={align}
|
|
||||||
data-chromeless={chromeLessOnMobile}
|
|
||||||
className={cn(
|
|
||||||
"preview relative flex h-72 w-full justify-center p-10 data-[align=center]:items-center data-[align=end]:items-end data-[align=start]:items-start data-[chromeless=true]:h-auto data-[chromeless=true]:p-0",
|
|
||||||
previewClassName
|
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DirectionProviderWrapper({
|
|
||||||
base,
|
|
||||||
dir: explicitDir,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
base?: string
|
|
||||||
dir?: "ltr" | "rtl"
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
// useTranslation handles the case when there's no LanguageProvider context.
|
|
||||||
// It will fall back to local state with defaultLanguage.
|
|
||||||
const translation = useTranslation(directionTranslations, "ar")
|
|
||||||
const dir = explicitDir ?? translation.dir
|
|
||||||
|
|
||||||
if (base === "base") {
|
|
||||||
return (
|
|
||||||
<BaseDirectionProvider direction={dir}>{children}</BaseDirectionProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <RadixDirectionProvider dir={dir}>{children}</RadixDirectionProvider>
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,37 +1,45 @@
|
|||||||
import * as React from "react"
|
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
|
|
||||||
import { getRegistryComponent } from "@/lib/registry"
|
|
||||||
import { ComponentPreviewTabs } from "@/components/component-preview-tabs"
|
import { ComponentPreviewTabs } from "@/components/component-preview-tabs"
|
||||||
import { ComponentSource } from "@/components/component-source"
|
import { ComponentSource } from "@/components/component-source"
|
||||||
|
import { Index } from "@/registry/__index__"
|
||||||
|
import { type Style } from "@/registry/_legacy-styles"
|
||||||
|
|
||||||
export function ComponentPreview({
|
export function ComponentPreview({
|
||||||
name,
|
name,
|
||||||
|
styleName = "new-york-v4",
|
||||||
type,
|
type,
|
||||||
className,
|
className,
|
||||||
previewClassName,
|
|
||||||
align = "center",
|
align = "center",
|
||||||
hideCode = false,
|
hideCode = false,
|
||||||
chromeLessOnMobile = false,
|
chromeLessOnMobile = false,
|
||||||
styleName = "new-york-v4",
|
|
||||||
direction = "ltr",
|
|
||||||
caption,
|
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
name: string
|
name: string
|
||||||
styleName?: string
|
styleName?: Style["name"]
|
||||||
align?: "center" | "start" | "end"
|
align?: "center" | "start" | "end"
|
||||||
description?: string
|
description?: string
|
||||||
hideCode?: boolean
|
hideCode?: boolean
|
||||||
type?: "block" | "component" | "example"
|
type?: "block" | "component" | "example"
|
||||||
chromeLessOnMobile?: boolean
|
chromeLessOnMobile?: boolean
|
||||||
previewClassName?: string
|
|
||||||
direction?: "ltr" | "rtl"
|
|
||||||
caption?: string
|
|
||||||
}) {
|
}) {
|
||||||
|
const Component = Index[styleName]?.[name]?.component
|
||||||
|
|
||||||
|
if (!Component) {
|
||||||
|
return (
|
||||||
|
<p className="text-muted-foreground mt-6 text-sm">
|
||||||
|
Component{" "}
|
||||||
|
<code className="bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm">
|
||||||
|
{name}
|
||||||
|
</code>{" "}
|
||||||
|
not found in registry.
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (type === "block") {
|
if (type === "block") {
|
||||||
const content = (
|
return (
|
||||||
<div className="relative mt-6 aspect-[4/2.5] w-full overflow-hidden rounded-xl border md:-mx-1">
|
<div className="relative aspect-[4/2.5] w-full overflow-hidden rounded-md border md:-mx-1">
|
||||||
<Image
|
<Image
|
||||||
src={`/r/styles/new-york-v4/${name}-light.png`}
|
src={`/r/styles/new-york-v4/${name}-light.png`}
|
||||||
alt={name}
|
alt={name}
|
||||||
@@ -51,42 +59,14 @@ export function ComponentPreview({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (caption) {
|
|
||||||
return (
|
|
||||||
<figure className="flex flex-col gap-4">
|
|
||||||
{content}
|
|
||||||
<figcaption className="text-muted-foreground text-center text-sm">
|
|
||||||
{caption}
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Component = getRegistryComponent(name, styleName)
|
return (
|
||||||
|
|
||||||
if (!Component) {
|
|
||||||
return (
|
|
||||||
<p className="text-muted-foreground mt-6 text-sm">
|
|
||||||
Component{" "}
|
|
||||||
<code className="bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm">
|
|
||||||
{name}
|
|
||||||
</code>{" "}
|
|
||||||
not found in registry.
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = (
|
|
||||||
<ComponentPreviewTabs
|
<ComponentPreviewTabs
|
||||||
className={className}
|
className={className}
|
||||||
previewClassName={previewClassName}
|
|
||||||
align={align}
|
align={align}
|
||||||
hideCode={hideCode}
|
hideCode={hideCode}
|
||||||
component={React.createElement(Component)}
|
component={<Component />}
|
||||||
source={
|
source={
|
||||||
<ComponentSource
|
<ComponentSource
|
||||||
name={name}
|
name={name}
|
||||||
@@ -94,34 +74,8 @@ export function ComponentPreview({
|
|||||||
styleName={styleName}
|
styleName={styleName}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
sourcePreview={
|
|
||||||
<ComponentSource
|
|
||||||
name={name}
|
|
||||||
collapsible={false}
|
|
||||||
styleName={styleName}
|
|
||||||
maxLines={3}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
chromeLessOnMobile={chromeLessOnMobile}
|
chromeLessOnMobile={chromeLessOnMobile}
|
||||||
direction={direction}
|
|
||||||
styleName={styleName}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (caption) {
|
|
||||||
return (
|
|
||||||
<figure
|
|
||||||
data-hide-code={hideCode}
|
|
||||||
className="flex flex-col data-[hide-code=true]:gap-4"
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
<figcaption className="text-muted-foreground -mt-8 text-center text-sm data-[hide-code=true]:mt-0">
|
|
||||||
{caption}
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return content
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import path from "node:path"
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { highlightCode } from "@/lib/highlight-code"
|
import { highlightCode } from "@/lib/highlight-code"
|
||||||
import { getDemoItem, getRegistryItem } from "@/lib/registry"
|
import { getRegistryItem } from "@/lib/registry"
|
||||||
import { formatCode } from "@/lib/rehype"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { CodeCollapsibleWrapper } from "@/components/code-collapsible-wrapper"
|
import { CodeCollapsibleWrapper } from "@/components/code-collapsible-wrapper"
|
||||||
import { CopyButton } from "@/components/copy-button"
|
import { CopyButton } from "@/components/copy-button"
|
||||||
import { getIconForLanguageExtension } from "@/components/icons"
|
import { getIconForLanguageExtension } from "@/components/icons"
|
||||||
|
import { type Style } from "@/registry/_legacy-styles"
|
||||||
|
|
||||||
export async function ComponentSource({
|
export async function ComponentSource({
|
||||||
name,
|
name,
|
||||||
@@ -18,15 +18,13 @@ export async function ComponentSource({
|
|||||||
collapsible = true,
|
collapsible = true,
|
||||||
className,
|
className,
|
||||||
styleName = "new-york-v4",
|
styleName = "new-york-v4",
|
||||||
maxLines,
|
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
name?: string
|
name?: string
|
||||||
src?: string
|
src?: string
|
||||||
title?: string
|
title?: string
|
||||||
language?: string
|
language?: string
|
||||||
collapsible?: boolean
|
collapsible?: boolean
|
||||||
styleName?: string
|
styleName?: Style["name"]
|
||||||
maxLines?: number
|
|
||||||
}) {
|
}) {
|
||||||
if (!name && !src) {
|
if (!name && !src) {
|
||||||
return null
|
return null
|
||||||
@@ -35,9 +33,7 @@ export async function ComponentSource({
|
|||||||
let code: string | undefined
|
let code: string | undefined
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
const item =
|
const item = await getRegistryItem(name, styleName)
|
||||||
(await getDemoItem(name, styleName)) ??
|
|
||||||
(await getRegistryItem(name, styleName))
|
|
||||||
code = item?.files?.[0]?.content
|
code = item?.files?.[0]?.content
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,13 +46,13 @@ export async function ComponentSource({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
code = await formatCode(code, styleName)
|
// Fix imports.
|
||||||
code = code.replaceAll("/* eslint-disable react/no-children-prop */\n", "")
|
// Replace @/registry/${style}/ with @/components/.
|
||||||
|
code = code.replaceAll(`@/registry/${styleName}/`, "@/components/")
|
||||||
|
|
||||||
// Truncate code if maxLines is set.
|
// Replace export default with export.
|
||||||
if (maxLines) {
|
code = code.replaceAll("export default", "export")
|
||||||
code = code.split("\n").slice(0, maxLines).join("\n")
|
code = code.replaceAll("/* eslint-disable react/no-children-prop */\n", "")
|
||||||
}
|
|
||||||
|
|
||||||
const lang = language ?? title?.split(".").pop() ?? "tsx"
|
const lang = language ?? title?.split(".").pop() ?? "tsx"
|
||||||
const highlightedCode = await highlightCode(code, lang)
|
const highlightedCode = await highlightCode(code, lang)
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
import { PAGES_NEW } from "@/lib/docs"
|
import { PAGES_NEW } from "@/lib/docs"
|
||||||
import { getPagesFromFolder, type PageTreeFolder } from "@/lib/page-tree"
|
import { source } from "@/lib/source"
|
||||||
|
|
||||||
export function ComponentsList({
|
export function ComponentsList() {
|
||||||
componentsFolder,
|
const components = source.pageTree.children.find(
|
||||||
currentBase,
|
(page) => page.$id === "components"
|
||||||
}: {
|
)
|
||||||
componentsFolder: PageTreeFolder
|
|
||||||
currentBase: string
|
if (components?.type !== "folder") {
|
||||||
}) {
|
return
|
||||||
const list = getPagesFromFolder(componentsFolder, currentBase)
|
}
|
||||||
|
|
||||||
|
const list = components.children.filter(
|
||||||
|
(component) => component.type === "page"
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 md:gap-x-8 lg:gap-x-16 lg:gap-y-6 xl:gap-x-20">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 md:gap-x-8 lg:gap-x-16 lg:gap-y-6 xl:gap-x-20">
|
||||||
{list.map((component) => (
|
{list.map((component) => (
|
||||||
<Link
|
<Link
|
||||||
key={component.$id}
|
key={component.$id}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { IconCheck, IconCopy, IconPlus } from "@tabler/icons-react"
|
import Link from "next/link"
|
||||||
|
import { IconCheck } from "@tabler/icons-react"
|
||||||
|
|
||||||
import { useConfig } from "@/hooks/use-config"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||||
import { useIsMobile } from "@/hooks/use-mobile"
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
import { CopyButton } from "@/components/copy-button"
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -27,114 +29,77 @@ import {
|
|||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
DrawerTrigger,
|
DrawerTrigger,
|
||||||
} from "@/registry/new-york-v4/ui/drawer"
|
} from "@/registry/new-york-v4/ui/drawer"
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/tabs"
|
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/tooltip"
|
|
||||||
|
|
||||||
export function DirectoryAddButton({
|
export function DirectoryAddButton({
|
||||||
registry,
|
registry,
|
||||||
}: {
|
}: {
|
||||||
registry: { name: string }
|
registry: {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
const [config, setConfig] = useConfig()
|
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||||
const [hasCopied, setHasCopied] = React.useState(false)
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
const packageManager = config.packageManager || "pnpm"
|
const jsonValue = `{
|
||||||
|
"registries": {
|
||||||
const commands = React.useMemo(() => {
|
"${registry.name}": "${registry.url}"
|
||||||
return {
|
}
|
||||||
pnpm: `pnpm dlx shadcn@latest registry add ${registry.name}`,
|
}`
|
||||||
npm: `npx shadcn@latest registry add ${registry.name}`,
|
|
||||||
yarn: `yarn dlx shadcn@latest registry add ${registry.name}`,
|
|
||||||
bun: `bunx --bun shadcn@latest registry add ${registry.name}`,
|
|
||||||
}
|
|
||||||
}, [registry.name])
|
|
||||||
|
|
||||||
const command = commands[packageManager]
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (hasCopied) {
|
|
||||||
const timer = setTimeout(() => setHasCopied(false), 2000)
|
|
||||||
return () => clearTimeout(timer)
|
|
||||||
}
|
|
||||||
}, [hasCopied])
|
|
||||||
|
|
||||||
const handleCopy = React.useCallback(() => {
|
|
||||||
copyToClipboardWithMeta(command, {
|
|
||||||
name: "copy_registry_add_command",
|
|
||||||
properties: {
|
|
||||||
command,
|
|
||||||
registry: registry.name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setHasCopied(true)
|
|
||||||
}, [command, registry.name])
|
|
||||||
|
|
||||||
const Trigger = (
|
const Trigger = (
|
||||||
<Button size="sm" variant="outline" className="relative z-10">
|
<Button
|
||||||
Add <IconPlus />
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="relative z-10"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
{isCopied ? (
|
||||||
|
<IconCheck />
|
||||||
|
) : (
|
||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>Model Context Protocol</title>
|
||||||
|
<path
|
||||||
|
d="M13.85 0a4.16 4.16 0 0 0-2.95 1.217L1.456 10.66a.835.835 0 0 0 0 1.18.835.835 0 0 0 1.18 0l9.442-9.442a2.49 2.49 0 0 1 3.541 0 2.49 2.49 0 0 1 0 3.541L8.59 12.97l-.1.1a.835.835 0 0 0 0 1.18.835.835 0 0 0 1.18 0l.1-.098 7.03-7.034a2.49 2.49 0 0 1 3.542 0l.049.05a2.49 2.49 0 0 1 0 3.54l-8.54 8.54a1.96 1.96 0 0 0 0 2.755l1.753 1.753a.835.835 0 0 0 1.18 0 .835.835 0 0 0 0-1.18l-1.753-1.753a.266.266 0 0 1 0-.394l8.54-8.54a4.185 4.185 0 0 0 0-5.9l-.05-.05a4.16 4.16 0 0 0-2.95-1.218c-.2 0-.401.02-.6.048a4.17 4.17 0 0 0-1.17-3.552A4.16 4.16 0 0 0 13.85 0m0 3.333a.84.84 0 0 0-.59.245L6.275 10.56a4.186 4.186 0 0 0 0 5.902 4.186 4.186 0 0 0 5.902 0L19.16 9.48a.835.835 0 0 0 0-1.18.835.835 0 0 0-1.18 0l-6.985 6.984a2.49 2.49 0 0 1-3.54 0 2.49 2.49 0 0 1 0-3.54l6.983-6.985a.835.835 0 0 0 0-1.18.84.84 0 0 0-.59-.245"
|
||||||
|
className="fill-foreground"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
MCP
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Content = (
|
const Content = (
|
||||||
<Tabs
|
<>
|
||||||
value={packageManager}
|
<figure
|
||||||
onValueChange={(value) => {
|
data-rehype-pretty-code-figure
|
||||||
setConfig({
|
className={cn(
|
||||||
...config,
|
"group relative mt-0",
|
||||||
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
|
!isMobile &&
|
||||||
})
|
"dark:bg-background dark:[&_[data-line]:not([data-highlighted-line]):before]:bg-background!"
|
||||||
}}
|
)}
|
||||||
className="gap-0 overflow-hidden rounded-lg border"
|
>
|
||||||
>
|
<CopyButton
|
||||||
<div className="flex items-center gap-2 border-b p-2">
|
value={jsonValue}
|
||||||
<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!">
|
className="top-3 right-2"
|
||||||
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
|
tooltip="Copy Code"
|
||||||
<TabsTrigger value="npm">npm</TabsTrigger>
|
/>
|
||||||
<TabsTrigger value="yarn">yarn</TabsTrigger>
|
<div data-rehype-pretty-code-title>components.json</div>
|
||||||
<TabsTrigger value="bun">bun</TabsTrigger>
|
<pre className="no-scrollbar min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0">
|
||||||
</TabsList>
|
<code data-line-numbers data-language="json">
|
||||||
<Tooltip>
|
<span data-line>{"{"}</span>
|
||||||
<TooltipTrigger asChild>
|
<span data-line>{' "registries": {'}</span>
|
||||||
<Button
|
<span
|
||||||
size="icon-sm"
|
data-line
|
||||||
variant="ghost"
|
data-highlighted-line
|
||||||
className="ml-auto size-7 rounded-lg"
|
>{` "${registry.name}": "${registry.url}"`}</span>
|
||||||
onClick={handleCopy}
|
<span data-line>{" }"}</span>
|
||||||
>
|
<span data-line>{"}"}</span>
|
||||||
{hasCopied ? (
|
</code>
|
||||||
<IconCheck className="size-4" />
|
</pre>
|
||||||
) : (
|
</figure>
|
||||||
<IconCopy 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]) => (
|
|
||||||
<TabsContent key={key} value={key} className="mt-0">
|
|
||||||
<div className="bg-surface text-surface-foreground 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>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
@@ -143,16 +108,20 @@ export function DirectoryAddButton({
|
|||||||
<DrawerTrigger asChild>{Trigger}</DrawerTrigger>
|
<DrawerTrigger asChild>{Trigger}</DrawerTrigger>
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
<DrawerTitle>Add Registry</DrawerTitle>
|
<DrawerTitle>Configure MCP</DrawerTitle>
|
||||||
<DrawerDescription>
|
<DrawerDescription>
|
||||||
Run this command to add {registry.name} to your project.
|
Copy and paste the following code into your project's
|
||||||
|
components.json.
|
||||||
</DrawerDescription>
|
</DrawerDescription>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<div className="px-4">{Content}</div>
|
<div className="px-6">{Content}</div>
|
||||||
<DrawerFooter>
|
<DrawerFooter>
|
||||||
<DrawerClose asChild>
|
<DrawerClose asChild>
|
||||||
<Button size="sm">Done</Button>
|
<Button size="sm">Close</Button>
|
||||||
</DrawerClose>
|
</DrawerClose>
|
||||||
|
<Button size="sm" asChild variant="outline">
|
||||||
|
<Link href="/docs/mcp">Read the docs</Link>
|
||||||
|
</Button>
|
||||||
</DrawerFooter>
|
</DrawerFooter>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
@@ -162,15 +131,22 @@ export function DirectoryAddButton({
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>{Trigger}</DialogTrigger>
|
<DialogTrigger asChild>{Trigger}</DialogTrigger>
|
||||||
<DialogContent className="dialog-ring animate-none! rounded-xl sm:max-w-md">
|
<DialogContent
|
||||||
|
className="rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 sm:max-w-[600px] dark:bg-neutral-900 dark:ring-neutral-800"
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add Registry</DialogTitle>
|
<DialogTitle>Configure MCP</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Run this command to add {registry.name} to your project.
|
Copy and paste the following code into your project's
|
||||||
|
components.json.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{Content}
|
{Content}
|
||||||
<DialogFooter>
|
<DialogFooter className="justify-between!">
|
||||||
|
<Button size="sm" asChild variant="ghost">
|
||||||
|
<Link href="/docs/mcp">Read the docs</Link>
|
||||||
|
</Button>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button size="sm">Done</Button>
|
<Button size="sm">Done</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
|
|||||||
@@ -50,10 +50,8 @@ export function DirectoryList() {
|
|||||||
href={getHomepageUrl(registry.homepage)}
|
href={getHomepageUrl(registry.homepage)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer external"
|
rel="noopener noreferrer external"
|
||||||
className="group flex items-center gap-1"
|
|
||||||
>
|
>
|
||||||
{registry.name}{" "}
|
{registry.name}
|
||||||
<IconArrowUpRight className="size-4 opacity-0 group-hover:opacity-100" />
|
|
||||||
</a>
|
</a>
|
||||||
</ItemTitle>
|
</ItemTitle>
|
||||||
{registry.description && (
|
{registry.description && (
|
||||||
@@ -63,6 +61,15 @@ export function DirectoryList() {
|
|||||||
)}
|
)}
|
||||||
</ItemContent>
|
</ItemContent>
|
||||||
<ItemActions className="relative z-10 hidden self-start sm:flex">
|
<ItemActions className="relative z-10 hidden self-start sm:flex">
|
||||||
|
<Button size="sm" variant="outline" asChild>
|
||||||
|
<a
|
||||||
|
href={getHomepageUrl(registry.homepage)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer external"
|
||||||
|
>
|
||||||
|
View <IconArrowUpRight />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
<DirectoryAddButton registry={registry} />
|
<DirectoryAddButton registry={registry} />
|
||||||
</ItemActions>
|
</ItemActions>
|
||||||
<ItemFooter className="justify-start pl-16 sm:hidden">
|
<ItemFooter className="justify-start pl-16 sm:hidden">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user