mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-12 18:31:46 +00:00
Compare commits
53 Commits
shadcn@2.1
...
shadcn-ui@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
729b9ec8ca | ||
|
|
a1bed464f3 | ||
|
|
805ed4120a | ||
|
|
600a593c87 | ||
|
|
500dbe2664 | ||
|
|
c577ee0666 | ||
|
|
d5bf0018fd | ||
|
|
fb36ca4159 | ||
|
|
824a35ada1 | ||
|
|
8d520c8d49 | ||
|
|
0873835339 | ||
|
|
4a0d4cfdb9 | ||
|
|
c4c5d8d419 | ||
|
|
9253682b87 | ||
|
|
c7cd16a637 | ||
|
|
eff1918d41 | ||
|
|
366d6b656b | ||
|
|
e489c5e08e | ||
|
|
d87003e0a4 | ||
|
|
a8633075f7 | ||
|
|
432d5e6e28 | ||
|
|
8f0c26f22a | ||
|
|
149b321c1b | ||
|
|
c1ae5a57cc | ||
|
|
b8ed303d8c | ||
|
|
13c97acf9f | ||
|
|
bed277c54d | ||
|
|
f7c42169a6 | ||
|
|
2c2fe97eb9 | ||
|
|
d64374d009 | ||
|
|
e24e51a2fa | ||
|
|
cdfecd1d97 | ||
|
|
2c043e709f | ||
|
|
aed19aa911 | ||
|
|
9e35d229ae | ||
|
|
db1975ef4d | ||
|
|
961e0b62d7 | ||
|
|
70c684c224 | ||
|
|
4ff64ba818 | ||
|
|
500a353816 | ||
|
|
c830780d62 | ||
|
|
debd51a854 | ||
|
|
78426dd862 | ||
|
|
6e47a94a8f | ||
|
|
ab6a856930 | ||
|
|
b33d3868e9 | ||
|
|
9e0a86122a | ||
|
|
2b276de95a | ||
|
|
64739f8399 | ||
|
|
f0cff7e0eb | ||
|
|
e242adaa9c | ||
|
|
986c00ee0e | ||
|
|
d0eece06d4 |
@@ -91,6 +91,42 @@ pnpm --filter=www dev
|
||||
pnpm --filter=shadcn-ui dev
|
||||
```
|
||||
|
||||
## Running the CLI Locally
|
||||
|
||||
To run the CLI locally, you can follow the workflow:
|
||||
|
||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
||||
|
||||
```bash
|
||||
pnpm www:dev
|
||||
```
|
||||
|
||||
2. Run the development script for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm shadcn:dev
|
||||
```
|
||||
|
||||
3. In another terminal tab, test the CLI by running:
|
||||
|
||||
```bash
|
||||
pnpm shadcn
|
||||
```
|
||||
|
||||
To test the CLI in a specific app, use a command like:
|
||||
|
||||
```bash
|
||||
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
||||
```
|
||||
|
||||
4. To run the tests for the CLI:
|
||||
|
||||
```bash
|
||||
pnpm --filter=shadcn test
|
||||
```
|
||||
|
||||
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
||||
|
||||
273
apps/www/__registry__/default/block/_sink/page.tsx
Normal file
273
apps/www/__registry__/default/block/_sink/page.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import { AccordionDemo } from "@/registry/default/block/_sink/components/accordion-demo"
|
||||
import { AlertDemo } from "@/registry/default/block/_sink/components/alert-demo"
|
||||
import { AlertDialogDemo } from "@/registry/default/block/_sink/components/alert-dialog-demo"
|
||||
import { AppSidebar } from "@/registry/default/block/_sink/components/app-sidebar"
|
||||
import { AspectRatioDemo } from "@/registry/default/block/_sink/components/aspect-ratio-demo"
|
||||
import { AvatarDemo } from "@/registry/default/block/_sink/components/avatar-demo"
|
||||
import { BadgeDemo } from "@/registry/default/block/_sink/components/badge-demo"
|
||||
import { BadgeDestructive } from "@/registry/default/block/_sink/components/badge-destructive"
|
||||
import { BadgeOutline } from "@/registry/default/block/_sink/components/badge-outline"
|
||||
import { BadgeSecondary } from "@/registry/default/block/_sink/components/badge-secondary"
|
||||
import { BreadcrumbDemo } from "@/registry/default/block/_sink/components/breadcrumb-demo"
|
||||
import { ButtonDemo } from "@/registry/default/block/_sink/components/button-demo"
|
||||
import { ButtonDestructive } from "@/registry/default/block/_sink/components/button-destructive"
|
||||
import { ButtonGhost } from "@/registry/default/block/_sink/components/button-ghost"
|
||||
import { ButtonLink } from "@/registry/default/block/_sink/components/button-link"
|
||||
import { ButtonLoading } from "@/registry/default/block/_sink/components/button-loading"
|
||||
import { ButtonOutline } from "@/registry/default/block/_sink/components/button-outline"
|
||||
import { ButtonSecondary } from "@/registry/default/block/_sink/components/button-secondary"
|
||||
import { ButtonWithIcon } from "@/registry/default/block/_sink/components/button-with-icon"
|
||||
import { CalendarDemo } from "@/registry/default/block/_sink/components/calendar-demo"
|
||||
import { CardDemo } from "@/registry/default/block/_sink/components/card-demo"
|
||||
import { CarouselDemo } from "@/registry/default/block/_sink/components/carousel-demo"
|
||||
import { CheckboxDemo } from "@/registry/default/block/_sink/components/checkbox-demo"
|
||||
import { CollapsibleDemo } from "@/registry/default/block/_sink/components/collapsible-demo"
|
||||
import { ComboboxDemo } from "@/registry/default/block/_sink/components/combobox-demo"
|
||||
import { CommandDemo } from "@/registry/default/block/_sink/components/command-demo"
|
||||
import { ComponentWrapper } from "@/registry/default/block/_sink/components/component-wrapper"
|
||||
import { ContextMenuDemo } from "@/registry/default/block/_sink/components/context-menu-demo"
|
||||
import { DatePickerDemo } from "@/registry/default/block/_sink/components/date-picker-demo"
|
||||
import { DialogDemo } from "@/registry/default/block/_sink/components/dialog-demo"
|
||||
import { DrawerDemo } from "@/registry/default/block/_sink/components/drawer-demo"
|
||||
import { DropdownMenuDemo } from "@/registry/default/block/_sink/components/dropdown-menu-demo"
|
||||
import { HoverCardDemo } from "@/registry/default/block/_sink/components/hover-card-demo"
|
||||
import { InputDemo } from "@/registry/default/block/_sink/components/input-demo"
|
||||
import { InputOTPDemo } from "@/registry/default/block/_sink/components/input-otp-demo"
|
||||
import { LabelDemo } from "@/registry/default/block/_sink/components/label-demo"
|
||||
import { MenubarDemo } from "@/registry/default/block/_sink/components/menubar-demo"
|
||||
import { NavigationMenuDemo } from "@/registry/default/block/_sink/components/navigation-menu-demo"
|
||||
import { PaginationDemo } from "@/registry/default/block/_sink/components/pagination-demo"
|
||||
import { PopoverDemo } from "@/registry/default/block/_sink/components/popover-demo"
|
||||
import { ProgressDemo } from "@/registry/default/block/_sink/components/progress-demo"
|
||||
import { RadioGroupDemo } from "@/registry/default/block/_sink/components/radio-group-demo"
|
||||
import { ResizableHandleDemo } from "@/registry/default/block/_sink/components/resizable-handle"
|
||||
import { ScrollAreaDemo } from "@/registry/default/block/_sink/components/scroll-area-demo"
|
||||
import { SelectDemo } from "@/registry/default/block/_sink/components/select-demo"
|
||||
import { SeparatorDemo } from "@/registry/default/block/_sink/components/separator-demo"
|
||||
import { SheetDemo } from "@/registry/default/block/_sink/components/sheet-demo"
|
||||
import { SkeletonDemo } from "@/registry/default/block/_sink/components/skeleton-demo"
|
||||
import { SliderDemo } from "@/registry/default/block/_sink/components/slider-demo"
|
||||
import { SonnerDemo } from "@/registry/default/block/_sink/components/sonner-demo"
|
||||
import { SwitchDemo } from "@/registry/default/block/_sink/components/switch-demo"
|
||||
import { TableDemo } from "@/registry/default/block/_sink/components/table-demo"
|
||||
import { TabsDemo } from "@/registry/default/block/_sink/components/tabs-demo"
|
||||
import { TextareaDemo } from "@/registry/default/block/_sink/components/textarea-demo"
|
||||
import { ToastDemo } from "@/registry/default/block/_sink/components/toast-demo"
|
||||
import { ToggleDemo } from "@/registry/default/block/_sink/components/toggle-demo"
|
||||
import { ToggleDisabled } from "@/registry/default/block/_sink/components/toggle-disabled"
|
||||
import { ToggleGroupDemo } from "@/registry/default/block/_sink/components/toggle-group-demo"
|
||||
import { ToggleOutline } from "@/registry/default/block/_sink/components/toggle-outline"
|
||||
import { ToggleWithText } from "@/registry/default/block/_sink/components/toggle-with-text"
|
||||
import { TooltipDemo } from "@/registry/default/block/_sink/components/tooltip-demo"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/default/ui/breadcrumb"
|
||||
import { Separator } from "@/registry/default/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/registry/default/ui/sidebar"
|
||||
|
||||
export default function SinkPage() {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">
|
||||
Building Your Application
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<ComponentWrapper name="Accordion">
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Alert">
|
||||
<AlertDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="AlertDialog">
|
||||
<AlertDialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="AspectRatio">
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Avatar">
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Badge">
|
||||
<BadgeDemo />
|
||||
<BadgeDestructive />
|
||||
<BadgeOutline />
|
||||
<BadgeSecondary />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Breadcrumb">
|
||||
<BreadcrumbDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Button">
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonDemo />
|
||||
<ButtonDestructive />
|
||||
<ButtonGhost />
|
||||
<ButtonLink />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonLoading />
|
||||
<ButtonOutline />
|
||||
<ButtonSecondary />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonWithIcon />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Calendar">
|
||||
<CalendarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Card">
|
||||
<CardDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
name="Carousel"
|
||||
className="[&_.max-w-xs]:max-w-[70%]"
|
||||
>
|
||||
<CarouselDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Checkbox">
|
||||
<CheckboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Collapsible">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Combobox">
|
||||
<ComboboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
name="Command"
|
||||
className="[&_[cmdk-root]]:md:min-w-max"
|
||||
>
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="ContextMenu">
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="DatePicker">
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Dialog">
|
||||
<DialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Drawer">
|
||||
<DrawerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="DropdownMenu">
|
||||
<DropdownMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="HoverCard">
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Input">
|
||||
<InputDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="InputOTP">
|
||||
<InputOTPDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Label">
|
||||
<LabelDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Menubar">
|
||||
<MenubarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="NavigationMenu" className="col-span-2">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Pagination">
|
||||
<PaginationDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Popover">
|
||||
<PopoverDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Progress">
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="RadioGroup">
|
||||
<RadioGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Resizable" className="col-span-2">
|
||||
<ResizableHandleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="ScrollArea">
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Select">
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Separator">
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Sheet">
|
||||
<SheetDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Skeleton">
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Slider">
|
||||
<SliderDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Sonner">
|
||||
<SonnerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Switch">
|
||||
<SwitchDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Table" className="col-span-2">
|
||||
<TableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Tabs">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Textarea">
|
||||
<TextareaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Toast">
|
||||
<ToastDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Toggle">
|
||||
<div className="flex items-center gap-2">
|
||||
<ToggleDemo />
|
||||
<ToggleDisabled />
|
||||
<ToggleOutline />
|
||||
<ToggleWithText />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="ToggleGroup">
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Tooltip">
|
||||
<TooltipDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
451
apps/www/__registry__/icons.tsx
Normal file
451
apps/www/__registry__/icons.tsx
Normal file
@@ -0,0 +1,451 @@
|
||||
// @ts-nocheck
|
||||
// This file is autogenerated by scripts/build-registry.ts
|
||||
// Do not edit this file directly.
|
||||
import * as React from "react"
|
||||
|
||||
export const Icons = {
|
||||
AlertCircle: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.AlertCircle,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ExclamationTriangleIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ArrowLeft: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ArrowLeft,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ArrowLeftIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ArrowRight: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ArrowRight,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ArrowRightIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ArrowUpDown: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ArrowUpDown,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.CaretSortIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
BellRing: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.BellRing,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.BellIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Bold: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Bold,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.FontBoldIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Calculator: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Calculator,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ComponentPlaceholderIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Calendar: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Calendar,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.CalendarIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Check: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Check,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.CheckIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ChevronDown: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ChevronDown,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ChevronDownIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ChevronLeft: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ChevronLeft,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ChevronLeftIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ChevronRight: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ChevronRight,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ChevronRightIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ChevronUp: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ChevronUp,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ChevronUpIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
ChevronsUpDown: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.ChevronsUpDown,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.CaretSortIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Circle: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Circle,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.DotFilledIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Copy: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Copy,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.CopyIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
CreditCard: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.CreditCard,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ComponentPlaceholderIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
GripVertical: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.GripVertical,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.DragHandleDots2Icon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Italic: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Italic,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.FontItalicIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Loader2: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Loader2,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ReloadIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Mail: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Mail,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.EnvelopeClosedIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
MailOpen: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.MailOpen,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.EnvelopeOpenIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Minus: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Minus,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.MinusIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Moon: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Moon,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.MoonIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
MoreHorizontal: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.MoreHorizontal,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.DotsHorizontalIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
PanelLeft: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.PanelLeft,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.ViewVerticalIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Plus: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Plus,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.PlusIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Search: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Search,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.MagnifyingGlassIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Send: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Send,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.PaperPlaneIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Settings: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Settings,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.GearIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Slash: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Slash,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.SlashIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Smile: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Smile,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.FaceIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Sun: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Sun,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.SunIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Terminal: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Terminal,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.RocketIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
Underline: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.Underline,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.UnderlineIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
User: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.User,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.PersonIcon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
X: {
|
||||
lucide: React.lazy(() =>
|
||||
import("lucide-react").then((mod) => ({
|
||||
default: mod.X,
|
||||
}))
|
||||
),
|
||||
radix: React.lazy(() =>
|
||||
import("@radix-ui/react-icons").then((mod) => ({
|
||||
default: mod.Cross2Icon,
|
||||
}))
|
||||
),
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
273
apps/www/__registry__/new-york/block/_sink/page.tsx
Normal file
273
apps/www/__registry__/new-york/block/_sink/page.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import { AccordionDemo } from "@/registry/new-york/block/_sink/components/accordion-demo"
|
||||
import { AlertDemo } from "@/registry/new-york/block/_sink/components/alert-demo"
|
||||
import { AlertDialogDemo } from "@/registry/new-york/block/_sink/components/alert-dialog-demo"
|
||||
import { AppSidebar } from "@/registry/new-york/block/_sink/components/app-sidebar"
|
||||
import { AspectRatioDemo } from "@/registry/new-york/block/_sink/components/aspect-ratio-demo"
|
||||
import { AvatarDemo } from "@/registry/new-york/block/_sink/components/avatar-demo"
|
||||
import { BadgeDemo } from "@/registry/new-york/block/_sink/components/badge-demo"
|
||||
import { BadgeDestructive } from "@/registry/new-york/block/_sink/components/badge-destructive"
|
||||
import { BadgeOutline } from "@/registry/new-york/block/_sink/components/badge-outline"
|
||||
import { BadgeSecondary } from "@/registry/new-york/block/_sink/components/badge-secondary"
|
||||
import { BreadcrumbDemo } from "@/registry/new-york/block/_sink/components/breadcrumb-demo"
|
||||
import { ButtonDemo } from "@/registry/new-york/block/_sink/components/button-demo"
|
||||
import { ButtonDestructive } from "@/registry/new-york/block/_sink/components/button-destructive"
|
||||
import { ButtonGhost } from "@/registry/new-york/block/_sink/components/button-ghost"
|
||||
import { ButtonLink } from "@/registry/new-york/block/_sink/components/button-link"
|
||||
import { ButtonLoading } from "@/registry/new-york/block/_sink/components/button-loading"
|
||||
import { ButtonOutline } from "@/registry/new-york/block/_sink/components/button-outline"
|
||||
import { ButtonSecondary } from "@/registry/new-york/block/_sink/components/button-secondary"
|
||||
import { ButtonWithIcon } from "@/registry/new-york/block/_sink/components/button-with-icon"
|
||||
import { CalendarDemo } from "@/registry/new-york/block/_sink/components/calendar-demo"
|
||||
import { CardDemo } from "@/registry/new-york/block/_sink/components/card-demo"
|
||||
import { CarouselDemo } from "@/registry/new-york/block/_sink/components/carousel-demo"
|
||||
import { CheckboxDemo } from "@/registry/new-york/block/_sink/components/checkbox-demo"
|
||||
import { CollapsibleDemo } from "@/registry/new-york/block/_sink/components/collapsible-demo"
|
||||
import { ComboboxDemo } from "@/registry/new-york/block/_sink/components/combobox-demo"
|
||||
import { CommandDemo } from "@/registry/new-york/block/_sink/components/command-demo"
|
||||
import { ComponentWrapper } from "@/registry/new-york/block/_sink/components/component-wrapper"
|
||||
import { ContextMenuDemo } from "@/registry/new-york/block/_sink/components/context-menu-demo"
|
||||
import { DatePickerDemo } from "@/registry/new-york/block/_sink/components/date-picker-demo"
|
||||
import { DialogDemo } from "@/registry/new-york/block/_sink/components/dialog-demo"
|
||||
import { DrawerDemo } from "@/registry/new-york/block/_sink/components/drawer-demo"
|
||||
import { DropdownMenuDemo } from "@/registry/new-york/block/_sink/components/dropdown-menu-demo"
|
||||
import { HoverCardDemo } from "@/registry/new-york/block/_sink/components/hover-card-demo"
|
||||
import { InputDemo } from "@/registry/new-york/block/_sink/components/input-demo"
|
||||
import { InputOTPDemo } from "@/registry/new-york/block/_sink/components/input-otp-demo"
|
||||
import { LabelDemo } from "@/registry/new-york/block/_sink/components/label-demo"
|
||||
import { MenubarDemo } from "@/registry/new-york/block/_sink/components/menubar-demo"
|
||||
import { NavigationMenuDemo } from "@/registry/new-york/block/_sink/components/navigation-menu-demo"
|
||||
import { PaginationDemo } from "@/registry/new-york/block/_sink/components/pagination-demo"
|
||||
import { PopoverDemo } from "@/registry/new-york/block/_sink/components/popover-demo"
|
||||
import { ProgressDemo } from "@/registry/new-york/block/_sink/components/progress-demo"
|
||||
import { RadioGroupDemo } from "@/registry/new-york/block/_sink/components/radio-group-demo"
|
||||
import { ResizableHandleDemo } from "@/registry/new-york/block/_sink/components/resizable-handle"
|
||||
import { ScrollAreaDemo } from "@/registry/new-york/block/_sink/components/scroll-area-demo"
|
||||
import { SelectDemo } from "@/registry/new-york/block/_sink/components/select-demo"
|
||||
import { SeparatorDemo } from "@/registry/new-york/block/_sink/components/separator-demo"
|
||||
import { SheetDemo } from "@/registry/new-york/block/_sink/components/sheet-demo"
|
||||
import { SkeletonDemo } from "@/registry/new-york/block/_sink/components/skeleton-demo"
|
||||
import { SliderDemo } from "@/registry/new-york/block/_sink/components/slider-demo"
|
||||
import { SonnerDemo } from "@/registry/new-york/block/_sink/components/sonner-demo"
|
||||
import { SwitchDemo } from "@/registry/new-york/block/_sink/components/switch-demo"
|
||||
import { TableDemo } from "@/registry/new-york/block/_sink/components/table-demo"
|
||||
import { TabsDemo } from "@/registry/new-york/block/_sink/components/tabs-demo"
|
||||
import { TextareaDemo } from "@/registry/new-york/block/_sink/components/textarea-demo"
|
||||
import { ToastDemo } from "@/registry/new-york/block/_sink/components/toast-demo"
|
||||
import { ToggleDemo } from "@/registry/new-york/block/_sink/components/toggle-demo"
|
||||
import { ToggleDisabled } from "@/registry/new-york/block/_sink/components/toggle-disabled"
|
||||
import { ToggleGroupDemo } from "@/registry/new-york/block/_sink/components/toggle-group-demo"
|
||||
import { ToggleOutline } from "@/registry/new-york/block/_sink/components/toggle-outline"
|
||||
import { ToggleWithText } from "@/registry/new-york/block/_sink/components/toggle-with-text"
|
||||
import { TooltipDemo } from "@/registry/new-york/block/_sink/components/tooltip-demo"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/registry/new-york/ui/breadcrumb"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from "@/registry/new-york/ui/sidebar"
|
||||
|
||||
export default function SinkPage() {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<SidebarInset>
|
||||
<header className="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2 px-4">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">
|
||||
Building Your Application
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<ComponentWrapper name="Accordion">
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Alert">
|
||||
<AlertDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="AlertDialog">
|
||||
<AlertDialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="AspectRatio">
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Avatar">
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Badge">
|
||||
<BadgeDemo />
|
||||
<BadgeDestructive />
|
||||
<BadgeOutline />
|
||||
<BadgeSecondary />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Breadcrumb">
|
||||
<BreadcrumbDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Button">
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonDemo />
|
||||
<ButtonDestructive />
|
||||
<ButtonGhost />
|
||||
<ButtonLink />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonLoading />
|
||||
<ButtonOutline />
|
||||
<ButtonSecondary />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonWithIcon />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Calendar">
|
||||
<CalendarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Card">
|
||||
<CardDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
name="Carousel"
|
||||
className="[&_.max-w-xs]:max-w-[70%]"
|
||||
>
|
||||
<CarouselDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Checkbox">
|
||||
<CheckboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Collapsible">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Combobox">
|
||||
<ComboboxDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
name="Command"
|
||||
className="[&_[cmdk-root]]:md:min-w-max"
|
||||
>
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="ContextMenu">
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="DatePicker">
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Dialog">
|
||||
<DialogDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Drawer">
|
||||
<DrawerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="DropdownMenu">
|
||||
<DropdownMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="HoverCard">
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Input">
|
||||
<InputDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="InputOTP">
|
||||
<InputOTPDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Label">
|
||||
<LabelDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Menubar">
|
||||
<MenubarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="NavigationMenu" className="col-span-2">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Pagination">
|
||||
<PaginationDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Popover">
|
||||
<PopoverDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Progress">
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="RadioGroup">
|
||||
<RadioGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Resizable" className="col-span-2">
|
||||
<ResizableHandleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="ScrollArea">
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Select">
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Separator">
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Sheet">
|
||||
<SheetDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Skeleton">
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Slider">
|
||||
<SliderDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Sonner">
|
||||
<SonnerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Switch">
|
||||
<SwitchDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Table" className="col-span-2">
|
||||
<TableDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Tabs">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Textarea">
|
||||
<TextareaDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Toast">
|
||||
<ToastDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Toggle">
|
||||
<div className="flex items-center gap-2">
|
||||
<ToggleDemo />
|
||||
<ToggleDisabled />
|
||||
<ToggleOutline />
|
||||
<ToggleWithText />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="ToggleGroup">
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper name="Tooltip">
|
||||
<TooltipDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
@@ -141,15 +141,16 @@ export default function Component() {
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
const now = new Date()
|
||||
const referenceDate = new Date("2024-06-30")
|
||||
let daysToSubtract = 90
|
||||
if (timeRange === "30d") {
|
||||
daysToSubtract = 30
|
||||
} else if (timeRange === "7d") {
|
||||
daysToSubtract = 7
|
||||
}
|
||||
now.setDate(now.getDate() - daysToSubtract)
|
||||
return date >= now
|
||||
const startDate = new Date(referenceDate)
|
||||
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||
return date >= startDate
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function BlocksLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Building Blocks for the Web</PageHeaderHeading>
|
||||
@@ -42,9 +42,11 @@ export default function BlocksLayout({
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section id="blocks" className="scroll-mt-24">
|
||||
{children}
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section id="blocks" className="scroll-mt-24">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
import * as React from "react"
|
||||
import { unstable_cache } from "next/cache"
|
||||
|
||||
import { getAllBlockIds } from "@/lib/blocks"
|
||||
import { BlockDisplay } from "@/components/block-display"
|
||||
|
||||
const BLOCKS_WHITELIST_PREFIXES = ["sidebar", "login"]
|
||||
|
||||
const getBlocks = unstable_cache(async () => {
|
||||
return (await getAllBlockIds()).filter((name) =>
|
||||
BLOCKS_WHITELIST_PREFIXES.some((prefix) => name.startsWith(prefix))
|
||||
)
|
||||
}, ["blocks"])
|
||||
import "@/styles/mdx.css"
|
||||
|
||||
export default async function BlocksPage() {
|
||||
const blocks = await getBlocks()
|
||||
const blocks = await getAllBlockIds()
|
||||
|
||||
return (
|
||||
<div className="gap-3 md:flex md:flex-row-reverse md:items-start">
|
||||
<div className="grid flex-1 gap-12 md:gap-24 lg:gap-48">
|
||||
{blocks.map((name, index) => (
|
||||
<React.Suspense key={`${name}-${index}`}>
|
||||
<BlockDisplay name={name} />
|
||||
</React.Suspense>
|
||||
{blocks.map((name) => (
|
||||
<BlockDisplay key={name} name={name} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function ChartsLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Beautiful Charts</PageHeaderHeading>
|
||||
@@ -38,9 +38,11 @@ export default function ChartsLayout({
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section id="charts" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section id="charts" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ export const metadata: Metadata = {
|
||||
description: "All colors in all formats.",
|
||||
}
|
||||
|
||||
export default function ChartsLayout({
|
||||
export default function ColorsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Tailwind Colors</PageHeaderHeading>
|
||||
@@ -37,9 +37,11 @@ export default function ChartsLayout({
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section id="charts" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section id="colors" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { allDocs } from "contentlayer/generated"
|
||||
import "@/styles/mdx.css"
|
||||
import type { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
import { ChevronRightIcon, ExternalLinkIcon } from "@radix-ui/react-icons"
|
||||
import { ChevronRight, ExternalLink } from "lucide-react"
|
||||
import Balancer from "react-wrap-balancer"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
@@ -15,7 +15,6 @@ import { OpenInV0Cta } from "@/components/open-in-v0-cta"
|
||||
import { DocsPager } from "@/components/pager"
|
||||
import { DashboardTableOfContents } from "@/components/toc"
|
||||
import { badgeVariants } from "@/registry/new-york/ui/badge"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
|
||||
interface DocPageProps {
|
||||
params: {
|
||||
@@ -89,10 +88,10 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
|
||||
return (
|
||||
<main className="relative py-6 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_300px]">
|
||||
<div className="mx-auto w-full min-w-0">
|
||||
<div className="mx-auto w-full min-w-0 max-w-3xl">
|
||||
<div className="mb-4 flex items-center space-x-1 text-sm leading-none text-muted-foreground">
|
||||
<div className="truncate">Docs</div>
|
||||
<ChevronRightIcon className="h-3.5 w-3.5" />
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
<div className="text-foreground">{doc.title}</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -115,7 +114,7 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
className={cn(badgeVariants({ variant: "secondary" }), "gap-1")}
|
||||
>
|
||||
Docs
|
||||
<ExternalLinkIcon className="h-3 w-3" />
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Link>
|
||||
)}
|
||||
{doc.links?.api && (
|
||||
@@ -126,7 +125,7 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
className={cn(badgeVariants({ variant: "secondary" }), "gap-1")}
|
||||
>
|
||||
API Reference
|
||||
<ExternalLinkIcon className="h-3 w-3" />
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
@@ -137,11 +136,11 @@ export default async function DocPage({ params }: DocPageProps) {
|
||||
<DocsPager doc={doc} />
|
||||
</div>
|
||||
<div className="hidden text-sm xl:block">
|
||||
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] pt-4">
|
||||
<ScrollArea className="h-full pb-10">
|
||||
<div className="sticky top-20 -mt-6 h-[calc(100vh-3.5rem)] pt-4">
|
||||
<div className="no-scrollbar h-full overflow-auto pb-10">
|
||||
{doc.toc && <DashboardTableOfContents toc={toc} />}
|
||||
<OpenInV0Cta className="mt-6 max-w-[80%]" />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -8,15 +8,13 @@ interface DocsLayoutProps {
|
||||
|
||||
export default function DocsLayout({ children }: DocsLayoutProps) {
|
||||
return (
|
||||
<div className="border-b">
|
||||
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-14 z-30 -ml-2 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
|
||||
<ScrollArea className="h-full py-6 pr-6 lg:py-8">
|
||||
<DocsSidebarNav config={docsConfig} />
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 border-r border-border/40 dark:border-border md:sticky md:block">
|
||||
<div className="no-scrollbar h-full overflow-auto py-6 pr-6 lg:py-8">
|
||||
<DocsSidebarNav config={docsConfig} />
|
||||
</div>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
CircleIcon,
|
||||
PlusIcon,
|
||||
StarIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import { ChevronDown, Circle, Plus, Star } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -37,14 +32,14 @@ export function DemoGithub() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-1 rounded-md bg-secondary text-secondary-foreground">
|
||||
<Button variant="secondary" className="px-3 shadow-none">
|
||||
<StarIcon className="mr-2 h-4 w-4" />
|
||||
<Star />
|
||||
Star
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="h-[20px]" />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary" className="px-2 shadow-none">
|
||||
<ChevronDownIcon className="h-4 w-4 text-secondary-foreground" />
|
||||
<ChevronDown className="text-secondary-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
@@ -62,7 +57,7 @@ export function DemoGithub() {
|
||||
<DropdownMenuCheckboxItem>Inspiration</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<PlusIcon className="mr-2 h-4 w-4" /> Create List
|
||||
<Plus /> Create List
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -71,11 +66,11 @@ export function DemoGithub() {
|
||||
<CardContent>
|
||||
<div className="flex space-x-4 text-sm text-muted-foreground">
|
||||
<div className="flex items-center">
|
||||
<CircleIcon className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
|
||||
<Circle className="mr-1 h-3 w-3 fill-sky-400 text-sky-400" />
|
||||
TypeScript
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<StarIcon className="mr-1 h-3 w-3" />
|
||||
<Star className="mr-1 h-3 w-3" />
|
||||
20k
|
||||
</div>
|
||||
<div>Updated April 2023</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BellIcon, EyeNoneIcon, PersonIcon } from "@radix-ui/react-icons"
|
||||
import { Bell, EyeOff, User } from "lucide-react"
|
||||
|
||||
import {
|
||||
Card,
|
||||
@@ -19,7 +19,7 @@ export function DemoNotifications() {
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-1">
|
||||
<div className="-mx-2 flex items-start space-x-4 rounded-md p-2 transition-all hover:bg-accent hover:text-accent-foreground">
|
||||
<BellIcon className="mt-px h-5 w-5" />
|
||||
<Bell className="mt-px h-5 w-5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Everything</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -28,7 +28,7 @@ export function DemoNotifications() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mx-2 flex items-start space-x-4 rounded-md bg-accent p-2 text-accent-foreground transition-all">
|
||||
<PersonIcon className="mt-px h-5 w-5" />
|
||||
<User className="mt-px h-5 w-5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Available</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -37,7 +37,7 @@ export function DemoNotifications() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mx-2 flex items-start space-x-4 rounded-md p-2 transition-all hover:bg-accent hover:text-accent-foreground">
|
||||
<EyeNoneIcon className="mt-px h-5 w-5" />
|
||||
<EyeOff className="mt-px h-5 w-5" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Ignoring</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -41,7 +41,7 @@ export function DemoShareDocument() {
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-sm font-medium">People with access</h4>
|
||||
<div className="text-sm font-medium">People with access</div>
|
||||
<div className="grid gap-6">
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
@@ -51,8 +51,7 @@ export function DemoTeamMembers() {
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Owner{" "}
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4 text-muted-foreground" />
|
||||
Owner <ChevronDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="end">
|
||||
@@ -105,8 +104,7 @@ export function DemoTeamMembers() {
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Member{" "}
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4 text-muted-foreground" />
|
||||
Member <ChevronDown className="text-muted-foreground" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" align="end">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CalendarIcon } from "@radix-ui/react-icons"
|
||||
import { addDays, format } from "date-fns"
|
||||
import { CalendarIcon } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
CaretSortIcon,
|
||||
CheckIcon,
|
||||
PlusCircledIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import { Check, ChevronsUpDown, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
@@ -105,7 +101,7 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) {
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
{selectedTeam.label}
|
||||
<CaretSortIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" />
|
||||
<ChevronsUpDown className="ml-auto opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
@@ -133,9 +129,9 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) {
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
{team.label}
|
||||
<CheckIcon
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
"ml-auto",
|
||||
selectedTeam.value === team.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
@@ -156,7 +152,7 @@ export default function TeamSwitcher({ className }: TeamSwitcherProps) {
|
||||
setShowNewTeamDialog(true)
|
||||
}}
|
||||
>
|
||||
<PlusCircledIcon className="mr-2 h-5 w-5" />
|
||||
<PlusCircle className="h-5 w-5" />
|
||||
Create Team
|
||||
</CommandItem>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { CalendarIcon, CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
|
||||
import { format } from "date-fns"
|
||||
import { CalendarIcon, Check, ChevronsUpDown } from "lucide-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
|
||||
@@ -174,7 +174,7 @@ export function AccountForm() {
|
||||
(language) => language.value === field.value
|
||||
)?.label
|
||||
: "Select language"}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
@@ -192,9 +192,9 @@ export function AccountForm() {
|
||||
form.setValue("language", language.value)
|
||||
}}
|
||||
>
|
||||
<CheckIcon
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2",
|
||||
language.value === field.value
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
|
||||
@@ -76,7 +76,7 @@ export function AppearanceForm() {
|
||||
<option value="system">System</option>
|
||||
</select>
|
||||
</FormControl>
|
||||
<ChevronDownIcon className="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
|
||||
<ChevronDown className="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
|
||||
</div>
|
||||
<FormDescription>
|
||||
Set the font you want to use in the dashboard.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import { ExamplesNav } from "@/components/examples-nav"
|
||||
import {
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { Button, buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Examples",
|
||||
@@ -23,32 +23,37 @@ interface ExamplesLayoutProps {
|
||||
|
||||
export default function ExamplesLayout({ children }: ExamplesLayoutProps) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Check out some examples
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">Examples</PageHeaderHeading>
|
||||
<PageHeaderHeading>Build your component library</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Dashboard, cards, authentication. Some examples built using the
|
||||
components. Use this as a guide to build your own.
|
||||
Beautifully designed components that you can copy and paste into your
|
||||
apps. Made with Tailwind CSS. Open source.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/docs">Get Started</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<Link href="/components">Components</Link>
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={siteConfig.links.github}
|
||||
>
|
||||
GitHub
|
||||
</Link>
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<section>
|
||||
<ExamplesNav />
|
||||
<div className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<section>
|
||||
<ExamplesNav />
|
||||
<div className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Image from "next/image"
|
||||
import { PlusCircledIcon } from "@radix-ui/react-icons"
|
||||
import { PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
@@ -54,7 +54,7 @@ export function AlbumArtwork({
|
||||
<ContextMenuSubTrigger>Add to Playlist</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-48">
|
||||
<ContextMenuItem>
|
||||
<PlusCircledIcon className="mr-2 h-4 w-4" />
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
New Playlist
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { PlusCircledIcon } from "@radix-ui/react-icons"
|
||||
import { PlusCircle } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { ScrollArea, ScrollBar } from "@/registry/new-york/ui/scroll-area"
|
||||
@@ -64,7 +64,7 @@ export default function MusicPage() {
|
||||
</TabsList>
|
||||
<div className="ml-auto mr-4">
|
||||
<Button>
|
||||
<PlusCircledIcon className="mr-2 h-4 w-4" />
|
||||
<PlusCircle />
|
||||
Add music
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useMutationObserver } from "@/hooks/use-mutation-observer"
|
||||
@@ -64,7 +64,7 @@ export function ModelSelector({ models, types, ...props }: ModelSelectorProps) {
|
||||
className="w-full justify-between"
|
||||
>
|
||||
{selectedModel ? selectedModel.name : "Select a model..."}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-[250px] p-0">
|
||||
@@ -154,11 +154,8 @@ function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
||||
className="data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground"
|
||||
>
|
||||
{model.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
isSelected ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
<Check
|
||||
className={cn("ml-auto", isSelected ? "opacity-100" : "opacity-0")}
|
||||
/>
|
||||
</CommandItem>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { Dialog } from "@radix-ui/react-dialog"
|
||||
import { DotsHorizontalIcon } from "@radix-ui/react-icons"
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { toast } from "@/registry/new-york/hooks/use-toast"
|
||||
import {
|
||||
@@ -40,9 +40,9 @@ export function PresetActions() {
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="secondary">
|
||||
<Button variant="secondary" size="icon">
|
||||
<span className="sr-only">Actions</span>
|
||||
<DotsHorizontalIcon className="h-4 w-4" />
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
|
||||
import { PopoverProps } from "@radix-ui/react-popover"
|
||||
import { Check, ChevronsUpDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
@@ -43,7 +43,7 @@ export function PresetSelector({ presets, ...props }: PresetSelectorProps) {
|
||||
className="flex-1 justify-between md:max-w-[200px] lg:max-w-[300px]"
|
||||
>
|
||||
{selectedPreset ? selectedPreset.name : "Load a preset..."}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] p-0">
|
||||
@@ -61,9 +61,9 @@ export function PresetSelector({ presets, ...props }: PresetSelectorProps) {
|
||||
}}
|
||||
>
|
||||
{preset.name}
|
||||
<CheckIcon
|
||||
<Check
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
"ml-auto",
|
||||
selectedPreset?.id === preset.id
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CopyIcon } from "@radix-ui/react-icons"
|
||||
import { Copy } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
@@ -37,7 +37,7 @@ export function PresetShare() {
|
||||
</div>
|
||||
<Button type="submit" size="sm" className="px-3">
|
||||
<span className="sr-only">Copy</span>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
<Copy />
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Metadata } from "next"
|
||||
import Image from "next/image"
|
||||
import { CounterClockwiseClockIcon } from "@radix-ui/react-icons"
|
||||
import { RotateCcw } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -265,7 +265,7 @@ export default function PlaygroundPage() {
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<CounterClockwiseClockIcon className="h-4 w-4" />
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,7 +283,7 @@ export default function PlaygroundPage() {
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<CounterClockwiseClockIcon className="h-4 w-4" />
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -314,7 +314,7 @@ export default function PlaygroundPage() {
|
||||
<Button>Submit</Button>
|
||||
<Button variant="secondary">
|
||||
<span className="sr-only">Show history</span>
|
||||
<CounterClockwiseClockIcon className="h-4 w-4" />
|
||||
<RotateCcw />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
CaretSortIcon,
|
||||
EyeNoneIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
@@ -42,26 +37,26 @@ export function DataTableColumnHeader<TData, TValue>({
|
||||
>
|
||||
<span>{title}</span>
|
||||
{column.getIsSorted() === "desc" ? (
|
||||
<ArrowDownIcon className="ml-2 h-4 w-4" />
|
||||
<ArrowDown />
|
||||
) : column.getIsSorted() === "asc" ? (
|
||||
<ArrowUpIcon className="ml-2 h-4 w-4" />
|
||||
<ArrowUp />
|
||||
) : (
|
||||
<CaretSortIcon className="ml-2 h-4 w-4" />
|
||||
<ChevronsUpDown />
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
||||
<ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
<ArrowUp className="h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Asc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
||||
<ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
<ArrowDown className="h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Desc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
||||
<EyeNoneIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
<EyeOff className="h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Hide
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons"
|
||||
import { Column } from "@tanstack/react-table"
|
||||
import { Check, PlusCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Badge } from "@/registry/new-york/ui/badge"
|
||||
@@ -43,7 +43,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-8 border-dashed">
|
||||
<PlusCircledIcon className="mr-2 h-4 w-4" />
|
||||
<PlusCircle />
|
||||
{title}
|
||||
{selectedValues?.size > 0 && (
|
||||
<>
|
||||
@@ -111,7 +111,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<CheckIcon className={cn("h-4 w-4")} />
|
||||
<Check />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
DoubleArrowLeftIcon,
|
||||
DoubleArrowRightIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -61,7 +61,7 @@ export function DataTablePagination<TData>({
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<DoubleArrowLeftIcon className="h-4 w-4" />
|
||||
<ChevronsLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -70,7 +70,7 @@ export function DataTablePagination<TData>({
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<ChevronLeftIcon className="h-4 w-4" />
|
||||
<ChevronLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -79,7 +79,7 @@ export function DataTablePagination<TData>({
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<ChevronRightIcon className="h-4 w-4" />
|
||||
<ChevronRight />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -88,7 +88,7 @@ export function DataTablePagination<TData>({
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<DoubleArrowRightIcon className="h-4 w-4" />
|
||||
<ChevronsRight />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { DotsHorizontalIcon } from "@radix-ui/react-icons"
|
||||
import { Row } from "@tanstack/react-table"
|
||||
import { MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -37,7 +37,7 @@ export function DataTableRowActions<TData>({
|
||||
variant="ghost"
|
||||
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||
>
|
||||
<DotsHorizontalIcon className="h-4 w-4" />
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Input } from "@/registry/new-york/ui/input"
|
||||
@@ -51,7 +51,7 @@ export function DataTableToolbar<TData>({
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Reset
|
||||
<Cross2Icon className="ml-2 h-4 w-4" />
|
||||
<X />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
|
||||
import { MixerHorizontalIcon } from "@radix-ui/react-icons"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { Settings2 } from "lucide-react"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -28,7 +28,7 @@ export function DataTableViewOptions<TData>({
|
||||
size="sm"
|
||||
className="ml-auto hidden h-8 lg:flex"
|
||||
>
|
||||
<MixerHorizontalIcon className="mr-2 h-4 w-4" />
|
||||
<Settings2 />
|
||||
View
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowRightIcon,
|
||||
ArrowUpIcon,
|
||||
CheckCircledIcon,
|
||||
CircleIcon,
|
||||
CrossCircledIcon,
|
||||
QuestionMarkCircledIcon,
|
||||
StopwatchIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
ArrowUp,
|
||||
CheckCircle,
|
||||
Circle,
|
||||
CircleOff,
|
||||
HelpCircle,
|
||||
Timer,
|
||||
} from "lucide-react"
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
@@ -28,27 +28,27 @@ export const statuses = [
|
||||
{
|
||||
value: "backlog",
|
||||
label: "Backlog",
|
||||
icon: QuestionMarkCircledIcon,
|
||||
icon: HelpCircle,
|
||||
},
|
||||
{
|
||||
value: "todo",
|
||||
label: "Todo",
|
||||
icon: CircleIcon,
|
||||
icon: Circle,
|
||||
},
|
||||
{
|
||||
value: "in progress",
|
||||
label: "In Progress",
|
||||
icon: StopwatchIcon,
|
||||
icon: Timer,
|
||||
},
|
||||
{
|
||||
value: "done",
|
||||
label: "Done",
|
||||
icon: CheckCircledIcon,
|
||||
icon: CheckCircle,
|
||||
},
|
||||
{
|
||||
value: "canceled",
|
||||
label: "Canceled",
|
||||
icon: CrossCircledIcon,
|
||||
icon: CircleOff,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -56,16 +56,16 @@ export const priorities = [
|
||||
{
|
||||
label: "Low",
|
||||
value: "low",
|
||||
icon: ArrowDownIcon,
|
||||
icon: ArrowDown,
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium",
|
||||
icon: ArrowRightIcon,
|
||||
icon: ArrowRight,
|
||||
},
|
||||
{
|
||||
label: "High",
|
||||
value: "high",
|
||||
icon: ArrowUpIcon,
|
||||
icon: ArrowUp,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -7,10 +7,12 @@ interface AppLayoutProps {
|
||||
|
||||
export default function AppLayout({ children }: AppLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<SiteHeader />
|
||||
<main className="flex-1">{children}</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
<div data-wrapper="" className="border-border/40 dark:border-border">
|
||||
<div className="mx-auto w-full border-border/40 dark:border-border min-[1800px]:max-w-[1536px] min-[1800px]:border-x">
|
||||
<SiteHeader />
|
||||
<main className="flex-1">{children}</main>
|
||||
<SiteFooter />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,18 +10,18 @@ import {
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import CardsNewYork from "@/registry/new-york/example/cards"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import MailPage from "@/app/(app)/examples/mail/page"
|
||||
|
||||
export default function IndexPage() {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<div className="relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Build your component library</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Beautifully designed components that you can copy and paste into your
|
||||
apps.
|
||||
apps. Made with Tailwind CSS. Open source.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<Button asChild size="sm">
|
||||
@@ -38,28 +38,28 @@ export default function IndexPage() {
|
||||
</Button>
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary" />
|
||||
<section className="overflow-hidden rounded-lg border bg-background shadow-md md:hidden md:shadow-xl">
|
||||
<Image
|
||||
src="/examples/mail-dark.png"
|
||||
width={1280}
|
||||
height={727}
|
||||
alt="Mail"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/mail-light.png"
|
||||
width={1280}
|
||||
height={727}
|
||||
alt="Mail"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
</section>
|
||||
<section className="hidden md:block">
|
||||
<div className="overflow-hidden rounded-lg border bg-background shadow">
|
||||
<MailPage />
|
||||
</div>
|
||||
</section>
|
||||
<div className="container py-6">
|
||||
<ExamplesNav className="[&>a:first-child]:text-primary" />
|
||||
<section className="overflow-hidden rounded-lg border bg-background shadow-md md:hidden md:shadow-xl">
|
||||
<Image
|
||||
src="/examples/cards-light.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="block dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src="/examples/cards-dark.png"
|
||||
width={1280}
|
||||
height={1214}
|
||||
alt="Cards"
|
||||
className="hidden dark:block"
|
||||
/>
|
||||
</section>
|
||||
<section className="hidden md:block [&>div]:p-0">
|
||||
<CardsNewYork />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
import { styles } from "@/registry/registry-styles"
|
||||
|
||||
interface SinkLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function SinkLayout({ children }: SinkLayoutProps) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="container">
|
||||
<div className="flex space-x-2 px-2 py-4">
|
||||
{styles.map((style) => (
|
||||
<Link href={`/sink/${style.name}`} key={style.name}>
|
||||
{style.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<ThemeWrapper>{children}</ThemeWrapper>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import AccordionDemo from "@/registry/new-york/example/accordion-demo"
|
||||
import AlertDialogDemo from "@/registry/new-york/example/alert-dialog-demo"
|
||||
import AspectRatioDemo from "@/registry/new-york/example/aspect-ratio-demo"
|
||||
import AvatarDemo from "@/registry/new-york/example/avatar-demo"
|
||||
import BadgeDemo from "@/registry/new-york/example/badge-demo"
|
||||
import BadgeDestructive from "@/registry/new-york/example/badge-destructive"
|
||||
import BadgeOutline from "@/registry/new-york/example/badge-outline"
|
||||
import BadgeSecondary from "@/registry/new-york/example/badge-secondary"
|
||||
import ButtonDemo from "@/registry/new-york/example/button-demo"
|
||||
import ButtonDestructive from "@/registry/new-york/example/button-destructive"
|
||||
import ButtonGhost from "@/registry/new-york/example/button-ghost"
|
||||
import ButtonLink from "@/registry/new-york/example/button-link"
|
||||
import ButtonLoading from "@/registry/new-york/example/button-loading"
|
||||
import ButtonOutline from "@/registry/new-york/example/button-outline"
|
||||
import ButtonSecondary from "@/registry/new-york/example/button-secondary"
|
||||
import ButtonWithIcon from "@/registry/new-york/example/button-with-icon"
|
||||
import CardDemo from "@/registry/new-york/example/card-demo"
|
||||
import CheckboxDemo from "@/registry/new-york/example/checkbox-demo"
|
||||
import CollapsibleDemo from "@/registry/new-york/example/collapsible-demo"
|
||||
import CommandDemo from "@/registry/new-york/example/command-demo"
|
||||
import ContextMenuDemo from "@/registry/new-york/example/context-menu-demo"
|
||||
import DatePickerDemo from "@/registry/new-york/example/date-picker-demo"
|
||||
import DialogDemo from "@/registry/new-york/example/dialog-demo"
|
||||
import DropdownMenuDemo from "@/registry/new-york/example/dropdown-menu-demo"
|
||||
import HoverCardDemo from "@/registry/new-york/example/hover-card-demo"
|
||||
import MenubarDemo from "@/registry/new-york/example/menubar-demo"
|
||||
import NavigationMenuDemo from "@/registry/new-york/example/navigation-menu-demo"
|
||||
import PopoverDemo from "@/registry/new-york/example/popover-demo"
|
||||
import ProgressDemo from "@/registry/new-york/example/progress-demo"
|
||||
import RadioGroupDemo from "@/registry/new-york/example/radio-group-demo"
|
||||
import ScrollAreaDemo from "@/registry/new-york/example/scroll-area-demo"
|
||||
import SelectDemo from "@/registry/new-york/example/select-demo"
|
||||
import SeparatorDemo from "@/registry/new-york/example/separator-demo"
|
||||
import SheetDemo from "@/registry/new-york/example/sheet-demo"
|
||||
import SkeletonDemo from "@/registry/new-york/example/skeleton-demo"
|
||||
import SliderDemo from "@/registry/new-york/example/slider-demo"
|
||||
import SwitchDemo from "@/registry/new-york/example/switch-demo"
|
||||
import TabsDemo from "@/registry/new-york/example/tabs-demo"
|
||||
import ToastDemo from "@/registry/new-york/example/toast-demo"
|
||||
import ToggleDemo from "@/registry/new-york/example/toggle-demo"
|
||||
import ToggleDisabled from "@/registry/new-york/example/toggle-disabled"
|
||||
import ToggleOutline from "@/registry/new-york/example/toggle-outline"
|
||||
import ToggleWithText from "@/registry/new-york/example/toggle-with-text"
|
||||
import TooltipDemo from "@/registry/new-york/example/tooltip-demo"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export default function KitchenSinkPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid grid-cols-3 items-start gap-4">
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<CardDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SliderDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
className="spa flex-col items-start space-x-0
|
||||
space-y-2"
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">Documentation</p>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
You can customize the theme using{" "}
|
||||
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-foreground">
|
||||
CSS variables
|
||||
</code>
|
||||
.{" "}
|
||||
<Link
|
||||
href="#"
|
||||
className="font-medium text-primary underline underline-offset-4"
|
||||
>
|
||||
Click here
|
||||
</Link>{" "}
|
||||
to learn more.
|
||||
</p>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CheckboxDemo />
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<MenubarDemo />
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="flex-col items-start space-x-0 space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<ButtonDemo />
|
||||
<ButtonSecondary />
|
||||
<ButtonDestructive />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonOutline />
|
||||
<ButtonLink />
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonWithIcon />
|
||||
<ButtonLoading />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&_ul>li:last-child]:hidden">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="justify-between">
|
||||
<SwitchDemo />
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<PopoverDemo />
|
||||
<ToastDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<TooltipDemo />
|
||||
<SheetDemo />
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>span]:h-[80px] [&>span]:w-[200px]">
|
||||
<RadioGroupDemo />
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<DropdownMenuDemo />
|
||||
<AlertDialogDemo />
|
||||
<DialogDemo />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<BadgeDemo />
|
||||
<BadgeSecondary />
|
||||
<BadgeDestructive />
|
||||
<BadgeOutline />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<ToggleDemo />
|
||||
<ToggleOutline />
|
||||
<ToggleDisabled />
|
||||
<ToggleWithText />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ComponentWrapper({
|
||||
className,
|
||||
children,
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between space-x-4 rounded-md p-4",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import AccordionDemo from "@/registry/default/example/accordion-demo"
|
||||
import AlertDialogDemo from "@/registry/default/example/alert-dialog-demo"
|
||||
import AspectRatioDemo from "@/registry/default/example/aspect-ratio-demo"
|
||||
import AvatarDemo from "@/registry/default/example/avatar-demo"
|
||||
import BadgeDemo from "@/registry/default/example/badge-demo"
|
||||
import BadgeDestructive from "@/registry/default/example/badge-destructive"
|
||||
import BadgeOutline from "@/registry/default/example/badge-outline"
|
||||
import BadgeSecondary from "@/registry/default/example/badge-secondary"
|
||||
import ButtonDemo from "@/registry/default/example/button-demo"
|
||||
import ButtonDestructive from "@/registry/default/example/button-destructive"
|
||||
import ButtonGhost from "@/registry/default/example/button-ghost"
|
||||
import ButtonLink from "@/registry/default/example/button-link"
|
||||
import ButtonLoading from "@/registry/default/example/button-loading"
|
||||
import ButtonOutline from "@/registry/default/example/button-outline"
|
||||
import ButtonSecondary from "@/registry/default/example/button-secondary"
|
||||
import ButtonWithIcon from "@/registry/default/example/button-with-icon"
|
||||
import CardDemo from "@/registry/default/example/card-demo"
|
||||
import CheckboxDemo from "@/registry/default/example/checkbox-demo"
|
||||
import CollapsibleDemo from "@/registry/default/example/collapsible-demo"
|
||||
import CommandDemo from "@/registry/default/example/command-demo"
|
||||
import ContextMenuDemo from "@/registry/default/example/context-menu-demo"
|
||||
import DatePickerDemo from "@/registry/default/example/date-picker-demo"
|
||||
import DialogDemo from "@/registry/default/example/dialog-demo"
|
||||
import DropdownMenuDemo from "@/registry/default/example/dropdown-menu-demo"
|
||||
import HoverCardDemo from "@/registry/default/example/hover-card-demo"
|
||||
import MenubarDemo from "@/registry/default/example/menubar-demo"
|
||||
import NavigationMenuDemo from "@/registry/default/example/navigation-menu-demo"
|
||||
import PopoverDemo from "@/registry/default/example/popover-demo"
|
||||
import ProgressDemo from "@/registry/default/example/progress-demo"
|
||||
import RadioGroupDemo from "@/registry/default/example/radio-group-demo"
|
||||
import ScrollAreaDemo from "@/registry/default/example/scroll-area-demo"
|
||||
import SelectDemo from "@/registry/default/example/select-demo"
|
||||
import SeparatorDemo from "@/registry/default/example/separator-demo"
|
||||
import SheetDemo from "@/registry/default/example/sheet-demo"
|
||||
import SkeletonDemo from "@/registry/default/example/skeleton-demo"
|
||||
import SliderDemo from "@/registry/default/example/slider-demo"
|
||||
import SwitchDemo from "@/registry/default/example/switch-demo"
|
||||
import TabsDemo from "@/registry/default/example/tabs-demo"
|
||||
import ToastDemo from "@/registry/default/example/toast-demo"
|
||||
import ToggleDemo from "@/registry/default/example/toggle-demo"
|
||||
import ToggleDisabled from "@/registry/default/example/toggle-disabled"
|
||||
import ToggleGroupDemo from "@/registry/default/example/toggle-group-demo"
|
||||
import ToggleOutline from "@/registry/default/example/toggle-outline"
|
||||
import ToggleWithText from "@/registry/default/example/toggle-with-text"
|
||||
import TooltipDemo from "@/registry/default/example/tooltip-demo"
|
||||
import { Button } from "@/registry/default/ui/button"
|
||||
|
||||
export default function KitchenSinkPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="grid gap-4">
|
||||
<div className="grid grid-cols-3 items-start gap-4">
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<CardDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SliderDemo className="w-full" />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper
|
||||
className="spa flex-col items-start space-x-0
|
||||
space-y-2"
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">Documentation</p>
|
||||
<p className="text-sm font-medium leading-none">
|
||||
You can customize the theme using{" "}
|
||||
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-foreground">
|
||||
CSS variables
|
||||
</code>
|
||||
.{" "}
|
||||
<Link
|
||||
href="#"
|
||||
className="font-medium text-primary underline underline-offset-4"
|
||||
>
|
||||
Click here
|
||||
</Link>{" "}
|
||||
to learn more.
|
||||
</p>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CheckboxDemo />
|
||||
<HoverCardDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<TabsDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<MenubarDemo />
|
||||
<AvatarDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="flex-col items-start space-x-0 space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<ButtonDemo />
|
||||
<ButtonSecondary />
|
||||
<ButtonDestructive />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonOutline />
|
||||
<ButtonLink />
|
||||
<ButtonGhost />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonWithIcon />
|
||||
<ButtonLoading />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<DatePickerDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AccordionDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&_ul>li:last-child]:hidden">
|
||||
<NavigationMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="justify-between">
|
||||
<SwitchDemo />
|
||||
<SelectDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<ToggleGroupDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SeparatorDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<AspectRatioDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<PopoverDemo />
|
||||
<ToastDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
<div className="grid gap-4">
|
||||
<ComponentWrapper>
|
||||
<TooltipDemo />
|
||||
<SheetDemo />
|
||||
<ProgressDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<CommandDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>span]:h-[80px] [&>span]:w-[200px]">
|
||||
<RadioGroupDemo />
|
||||
<ContextMenuDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<DropdownMenuDemo />
|
||||
<AlertDialogDemo />
|
||||
<DialogDemo />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<BadgeDemo />
|
||||
<BadgeSecondary />
|
||||
<BadgeDestructive />
|
||||
<BadgeOutline />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<SkeletonDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper className="[&>div]:w-full">
|
||||
<CollapsibleDemo />
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<div className="flex space-x-2">
|
||||
<ToggleDemo />
|
||||
<ToggleOutline />
|
||||
<ToggleDisabled />
|
||||
<ToggleWithText />
|
||||
</div>
|
||||
</ComponentWrapper>
|
||||
<ComponentWrapper>
|
||||
<ScrollAreaDemo />
|
||||
</ComponentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ComponentWrapper({
|
||||
className,
|
||||
children,
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between space-x-4 rounded-md p-4",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
52
apps/www/app/(app)/themes/layout.tsx
Normal file
52
apps/www/app/(app)/themes/layout.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { ThemeCustomizer } from "@/components/theme-customizer"
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Themes",
|
||||
description: "Hand-picked themes that you can copy and paste into your apps.",
|
||||
}
|
||||
|
||||
export default function ThemesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<ThemeWrapper
|
||||
defaultTheme="zinc"
|
||||
className="relative flex w-full flex-col items-start md:flex-row"
|
||||
>
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<ThemeCustomizer />
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
</ThemeWrapper>
|
||||
<div className="container py-6">
|
||||
<section id="themes" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +1,7 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import "public/registry/themes.css"
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import {
|
||||
PageActions,
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
import { ThemeCustomizer } from "@/components/theme-customizer"
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
import { ThemesTabs } from "@/app/(app)/themes/tabs"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Themes",
|
||||
description: "Hand-picked themes that you can copy and paste into your apps.",
|
||||
}
|
||||
import "public/registry/themes.css"
|
||||
|
||||
export default function ThemesPage() {
|
||||
return (
|
||||
<div className="container">
|
||||
<ThemeWrapper
|
||||
defaultTheme="zinc"
|
||||
className="relative flex w-full flex-col items-start md:flex-row"
|
||||
>
|
||||
<PageHeader className="w-full">
|
||||
<Announcement />
|
||||
<PageHeaderHeading className="hidden md:block">
|
||||
Add colors. Make it yours.
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderHeading className="md:hidden">
|
||||
Make it yours
|
||||
</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
Hand-picked themes that you can copy and paste into your apps.
|
||||
</PageHeaderDescription>
|
||||
<PageActions>
|
||||
<ThemeCustomizer />
|
||||
</PageActions>
|
||||
</PageHeader>
|
||||
</ThemeWrapper>
|
||||
<ThemesTabs />
|
||||
</div>
|
||||
)
|
||||
return <ThemesTabs />
|
||||
}
|
||||
|
||||
@@ -1,73 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper"
|
||||
import CardsDefault from "@/registry/default/example/cards"
|
||||
import { Skeleton } from "@/registry/default/ui/skeleton"
|
||||
import CardsNewYork from "@/registry/new-york/example/cards"
|
||||
|
||||
export function ThemesTabs() {
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
const [config] = useConfig()
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{!mounted ? (
|
||||
<div className="md:grids-col-2 grid md:gap-4 lg:grid-cols-10 xl:gap-6">
|
||||
<div className="space-y-4 lg:col-span-4 xl:col-span-6 xl:space-y-6">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="grid gap-1 sm:grid-cols-[260px_1fr] md:hidden">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="pt-3 sm:pl-2 sm:pt-0 xl:pl-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<div className="pt-3 sm:col-span-2 xl:pt-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
|
||||
<div className="space-y-4 xl:space-y-6">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<div className="space-y-4 xl:space-y-6">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="hidden xl:block">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 lg:col-span-6 xl:col-span-4 xl:space-y-6">
|
||||
<div className="hidden gap-1 sm:grid-cols-[260px_1fr] md:grid">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
<div className="pt-3 sm:pl-2 sm:pt-0 xl:pl-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<div className="pt-3 sm:col-span-2 xl:pt-4">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden md:block">
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
<Skeleton className="h-[218px] w-full" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ThemeWrapper>
|
||||
{config.style === "new-york" && <CardsNewYork />}
|
||||
{config.style === "default" && <CardsDefault />}
|
||||
</ThemeWrapper>
|
||||
)}
|
||||
<ThemeWrapper>
|
||||
<CardsNewYork />
|
||||
</ThemeWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import * as React from "react"
|
||||
import { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { getAllBlockIds, getBlock } from "@/lib/blocks"
|
||||
import { getAllBlockIds } from "@/lib/blocks"
|
||||
import { absoluteUrl, cn } from "@/lib/utils"
|
||||
import { BlockChunk } from "@/components/block-chunk"
|
||||
import { BlockWrapper } from "@/components/block-wrapper"
|
||||
import { Style, styles } from "@/registry/registry-styles"
|
||||
|
||||
import "@/styles/mdx.css"
|
||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
||||
|
||||
const getCachedRegistryItem = React.cache(
|
||||
async (name: string, style: Style["name"]) => {
|
||||
return await getRegistryItem(name, style)
|
||||
}
|
||||
)
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -19,23 +25,23 @@ export async function generateMetadata({
|
||||
}
|
||||
}): Promise<Metadata> {
|
||||
const { name, style } = params
|
||||
const block = await getBlock(name, style)
|
||||
const item = await getCachedRegistryItem(name, style)
|
||||
|
||||
if (!block) {
|
||||
if (!item) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const title = block.name
|
||||
const description = block.description
|
||||
const title = item.name
|
||||
const description = item.description
|
||||
|
||||
return {
|
||||
title: `${block.description} - ${block.name}`,
|
||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: "article",
|
||||
url: absoluteUrl(`/blocks/${block.name}`),
|
||||
url: absoluteUrl(`/blocks/${style}/${item.name}`),
|
||||
images: [
|
||||
{
|
||||
url: siteConfig.ogImage,
|
||||
@@ -76,39 +82,22 @@ export default async function BlockPage({
|
||||
}
|
||||
}) {
|
||||
const { name, style } = params
|
||||
const block = await getBlock(name, style)
|
||||
const item = await getCachedRegistryItem(name, style)
|
||||
const Component = getRegistryComponent(name, style)
|
||||
|
||||
if (!block) {
|
||||
if (!item || !Component) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
const Component = block.component
|
||||
|
||||
const chunks = block.chunks?.map((chunk) => ({ ...chunk }))
|
||||
delete block.component
|
||||
block.chunks?.map((chunk) => delete chunk.component)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ThemesStyle /> */}
|
||||
<div
|
||||
className={cn(
|
||||
"themes-wrapper bg-background",
|
||||
block.container?.className
|
||||
item.meta?.containerClassName
|
||||
)}
|
||||
>
|
||||
<BlockWrapper block={block}>
|
||||
<Component />
|
||||
{chunks?.map((chunk, index) => (
|
||||
<BlockChunk
|
||||
key={chunk.name}
|
||||
block={block}
|
||||
chunk={block.chunks?.[index]}
|
||||
>
|
||||
<chunk.component />
|
||||
</BlockChunk>
|
||||
))}
|
||||
</BlockWrapper>
|
||||
<Component />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
34
apps/www/app/(internal)/icons/layout.tsx
Normal file
34
apps/www/app/(internal)/icons/layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Metadata } from "next"
|
||||
|
||||
import { Announcement } from "@/components/announcement"
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderDescription,
|
||||
PageHeaderHeading,
|
||||
} from "@/components/page-header"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Icons",
|
||||
description: "All icons in all libraries.",
|
||||
}
|
||||
|
||||
export default function IconsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="container relative">
|
||||
<PageHeader>
|
||||
<Announcement />
|
||||
<PageHeaderHeading>Icons</PageHeaderHeading>
|
||||
<PageHeaderDescription>
|
||||
All icons in all libraries.
|
||||
</PageHeaderDescription>
|
||||
</PageHeader>
|
||||
<section id="icons" className="scroll-mt-20">
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
52
apps/www/app/(internal)/icons/page.tsx
Normal file
52
apps/www/app/(internal)/icons/page.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Icons } from "@/__registry__/icons"
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/registry/new-york/ui/table"
|
||||
import { iconLibraries } from "@/registry/registry-icons"
|
||||
|
||||
export default function IconsPage() {
|
||||
return (
|
||||
<div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[240px]" align="left">
|
||||
name
|
||||
</TableHead>
|
||||
{Object.keys(iconLibraries).map((library) => (
|
||||
<TableHead key={library}>{library}</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{Object.entries(Icons).map(([name, icon]) => (
|
||||
<TableRow key={name}>
|
||||
<TableCell align="left">
|
||||
<code>{name}</code>
|
||||
</TableCell>
|
||||
{Object.entries(iconLibraries).map(([library, name]) => {
|
||||
const IconComponent = icon[library as keyof typeof icon]
|
||||
return (
|
||||
<TableCell key={library} className="[&_svg]:h-4 [&_svg]:w-4">
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
{IconComponent && <IconComponent />}
|
||||
</React.Suspense>
|
||||
</TableCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
3
apps/www/app/(internal)/sink/page.tsx
Normal file
3
apps/www/app/(internal)/sink/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import SinkPage from "@/registry/new-york/block/_sink/page"
|
||||
|
||||
export default SinkPage
|
||||
@@ -1,8 +1,8 @@
|
||||
import "@/styles/globals.css"
|
||||
import { Metadata, Viewport } from "next"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { fontSans } from "@/lib/fonts"
|
||||
import { META_THEME_COLORS, siteConfig } from "@/config/site"
|
||||
import { fontMono, fontSans } from "@/lib/fonts"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Analytics } from "@/components/analytics"
|
||||
import { ThemeProvider } from "@/components/providers"
|
||||
@@ -65,10 +65,7 @@ export const metadata: Metadata = {
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{ media: "(prefers-color-scheme: light)", color: "white" },
|
||||
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
||||
],
|
||||
themeColor: META_THEME_COLORS.light,
|
||||
}
|
||||
|
||||
interface RootLayoutProps {
|
||||
@@ -79,11 +76,24 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head />
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
try {
|
||||
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
||||
}
|
||||
} catch (_) {}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={cn(
|
||||
"min-h-screen bg-background font-sans antialiased",
|
||||
fontSans.variable
|
||||
fontSans.variable,
|
||||
fontMono.variable
|
||||
)}
|
||||
>
|
||||
<ThemeProvider
|
||||
@@ -91,6 +101,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableColorScheme
|
||||
>
|
||||
<div vaul-drawer-wrapper="">
|
||||
<div className="relative flex min-h-screen flex-col bg-background">
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import Link from "next/link"
|
||||
import { ArrowRightIcon } from "@radix-ui/react-icons"
|
||||
import { Blocks, PieChart } from "lucide-react"
|
||||
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { ArrowRight } from "lucide-react"
|
||||
|
||||
export function Announcement() {
|
||||
return (
|
||||
<Link
|
||||
href="/docs/components/sidebar"
|
||||
className="group inline-flex items-center px-0.5 text-sm font-medium"
|
||||
className="group mb-2 inline-flex items-center px-0.5 text-sm font-medium"
|
||||
>
|
||||
<PieChart className="h-4 w-4" />{" "}
|
||||
<Separator className="mx-2 h-4" orientation="vertical" />{" "}
|
||||
<span className="underline-offset-4 group-hover:underline">
|
||||
New sidebar component
|
||||
</span>
|
||||
<ArrowRightIcon className="ml-1 h-4 w-4" />
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AnimatePresence, motion } from "framer-motion"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useLiftMode } from "@/hooks/use-lift-mode"
|
||||
import { BlockCopyButton } from "@/components/block-copy-button"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Block, type BlockChunk } from "@/registry/schema"
|
||||
|
||||
export function BlockChunk({
|
||||
block,
|
||||
chunk,
|
||||
children,
|
||||
...props
|
||||
}: React.PropsWithChildren<{ block: Block; chunk?: BlockChunk }>) {
|
||||
const { isLiftMode } = useLiftMode(block.name)
|
||||
|
||||
if (!chunk) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isLiftMode && (
|
||||
<motion.div
|
||||
key={chunk.name}
|
||||
x-chunk-container={chunk.name}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0, transition: { ease: "easeOut", duration: 0.2 } }}
|
||||
transition={{ ease: "easeIn", duration: 0.2 }}
|
||||
className={cn(
|
||||
"group rounded-xl bg-background shadow-xl transition",
|
||||
chunk.container?.className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative z-30">{children}</div>
|
||||
{chunk.code && (
|
||||
<div className="absolute inset-x-0 top-0 z-30 flex px-4 py-3 opacity-0 transition-all duration-200 ease-in group-hover:-translate-y-12 group-hover:opacity-100">
|
||||
<div className="flex w-full items-center justify-end gap-2">
|
||||
<BlockCopyButton
|
||||
event="copy_chunk_code"
|
||||
name={chunk.name}
|
||||
code={chunk.code}
|
||||
/>
|
||||
<V0Button size="icon" name={chunk.name} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +1,52 @@
|
||||
import { unstable_cache } from "next/cache"
|
||||
import * as React from "react"
|
||||
import { z } from "zod"
|
||||
|
||||
import { getBlock } from "@/lib/blocks"
|
||||
import { BlockPreview } from "@/components/block-preview"
|
||||
|
||||
const getBlockByName = unstable_cache(
|
||||
async (name: string) => {
|
||||
const block = await getBlock(name)
|
||||
|
||||
if (!block) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
name: block.name,
|
||||
style: block.style,
|
||||
description: block.description,
|
||||
container: block.container,
|
||||
}
|
||||
},
|
||||
["block"]
|
||||
)
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import {
|
||||
createFileTreeForRegistryItemFiles,
|
||||
getRegistryItem,
|
||||
} from "@/lib/registry"
|
||||
import { BlockViewer } from "@/components/block-viewer"
|
||||
import { registryItemFileSchema } from "@/registry/schema"
|
||||
|
||||
export async function BlockDisplay({ name }: { name: string }) {
|
||||
const block = await getBlockByName(name)
|
||||
const item = await getCachedRegistryItem(name)
|
||||
|
||||
if (!block) {
|
||||
if (!item?.files) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <BlockPreview key={block.name} block={block} />
|
||||
const [tree, highlightedFiles] = await Promise.all([
|
||||
getCachedFileTree(item.files),
|
||||
getCachedHighlightedFiles(item.files),
|
||||
])
|
||||
|
||||
return (
|
||||
<BlockViewer item={item} tree={tree} highlightedFiles={highlightedFiles} />
|
||||
)
|
||||
}
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
})
|
||||
|
||||
const getCachedFileTree = React.cache(
|
||||
async (files: Array<{ path: string; target?: string }>) => {
|
||||
if (!files) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await createFileTreeForRegistryItemFiles(files)
|
||||
}
|
||||
)
|
||||
|
||||
const getCachedHighlightedFiles = React.cache(
|
||||
async (files: z.infer<typeof registryItemFileSchema>[]) => {
|
||||
return await Promise.all(
|
||||
files.map(async (file) => ({
|
||||
...file,
|
||||
highlightedContent: await highlightCode(file.content ?? ""),
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
|
||||
import { BlockToolbar } from "@/components/block-toolbar"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/registry/new-york/ui/resizable"
|
||||
import { type Block } from "@/registry/schema"
|
||||
|
||||
export function BlockPreview({
|
||||
block,
|
||||
}: {
|
||||
block: Pick<Block, "name" | "style" | "description" | "container">
|
||||
}) {
|
||||
const ref = React.useRef<ImperativePanelHandle>(null)
|
||||
|
||||
return (
|
||||
<div
|
||||
id={block.name}
|
||||
className="relative grid w-full scroll-m-20 gap-4"
|
||||
style={
|
||||
{
|
||||
"--container-height": block.container?.height,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<BlockToolbar block={block} resizablePanelRef={ref} />
|
||||
<ResizablePanelGroup direction="horizontal" className="relative z-10">
|
||||
<ResizablePanel
|
||||
ref={ref}
|
||||
className="relative aspect-[4/2.5] rounded-lg border bg-background md:aspect-auto"
|
||||
defaultSize={100}
|
||||
minSize={30}
|
||||
>
|
||||
<Image
|
||||
src={`/images/blocks/${block.name}.png`}
|
||||
alt={block.name}
|
||||
data-block={block.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:hidden sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={`/images/blocks/${block.name}-dark.png`}
|
||||
alt={block.name}
|
||||
data-block={block.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<iframe
|
||||
src={`/blocks/${block.style}/${block.name}`}
|
||||
height={block.container?.height ?? 450}
|
||||
className="chunk-mode relative z-20 hidden w-full bg-background md:block"
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="relative hidden w-3 bg-transparent p-0 after:absolute after:right-0 after:top-1/2 after:h-8 after:w-[6px] after:-translate-y-1/2 after:translate-x-[-1px] after:rounded-full after:bg-border after:transition-all after:hover:h-10 sm:block" />
|
||||
<ResizablePanel defaultSize={0} minSize={0} />
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
Check,
|
||||
Fullscreen,
|
||||
Monitor,
|
||||
Smartphone,
|
||||
Tablet,
|
||||
Terminal,
|
||||
} from "lucide-react"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york/ui/toggle-group"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
export function BlockToolbar({
|
||||
block,
|
||||
resizablePanelRef,
|
||||
}: {
|
||||
block: Pick<Block, "name" | "style" | "description" | "container">
|
||||
resizablePanelRef: React.RefObject<ImperativePanelHandle>
|
||||
}) {
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
<Button asChild variant="link" className="whitespace-normal px-1 md:px-2">
|
||||
<a href={`#${block.name}`}>{block.description}</a>
|
||||
</Button>
|
||||
<div className="ml-auto hidden items-center gap-2 md:flex md:pr-[14px]">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-7 rounded-md border bg-muted shadow-none"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
copyToClipboard(`npx shadcn@latest add ${block.name}`)
|
||||
}}
|
||||
>
|
||||
{isCopied ? <Check /> : <Terminal />}
|
||||
npx shadcn add {block.name}
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 md:flex" />
|
||||
<div className="hidden h-7 items-center gap-1.5 rounded-md border p-[2px] shadow-none md:flex">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="100"
|
||||
onValueChange={(value) => {
|
||||
if (resizablePanelRef.current) {
|
||||
resizablePanelRef.current.resize(parseInt(value))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value="100"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Desktop"
|
||||
>
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="60"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Tablet"
|
||||
>
|
||||
<Tablet className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="30"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Mobile"
|
||||
>
|
||||
<Smartphone className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
asChild
|
||||
title="Open in New Tab"
|
||||
>
|
||||
<Link
|
||||
href={`/blocks/${block.style}/${block.name}`}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="sr-only">Open in New Tab</span>
|
||||
<Fullscreen className="h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</Button>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 md:flex" />
|
||||
<V0Button
|
||||
className="hidden shadow-none md:flex"
|
||||
id={`v0-button-${block.name}`}
|
||||
name={`v0-${block.name}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
452
apps/www/components/block-viewer.tsx
Normal file
452
apps/www/components/block-viewer.tsx
Normal file
@@ -0,0 +1,452 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import {
|
||||
Check,
|
||||
ChevronRight,
|
||||
Clipboard,
|
||||
File,
|
||||
Folder,
|
||||
Fullscreen,
|
||||
Monitor,
|
||||
Smartphone,
|
||||
Tablet,
|
||||
Terminal,
|
||||
} from "lucide-react"
|
||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||
import { z } from "zod"
|
||||
|
||||
import { trackEvent } from "@/lib/events"
|
||||
import { FileTree, createFileTreeForRegistryItemFiles } from "@/lib/registry"
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/registry/new-york/ui/collapsible"
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/registry/new-york/ui/resizable"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarProvider,
|
||||
} from "@/registry/new-york/ui/sidebar"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york/ui/tabs"
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/registry/new-york/ui/toggle-group"
|
||||
import { Style } from "@/registry/registry-styles"
|
||||
import { registryEntrySchema, registryItemFileSchema } from "@/registry/schema"
|
||||
|
||||
type BlockViewerContext = {
|
||||
item: z.infer<typeof registryEntrySchema>
|
||||
view: "code" | "preview"
|
||||
setView: (view: "code" | "preview") => void
|
||||
style?: Style["name"]
|
||||
setStyle: (style: Style["name"]) => void
|
||||
activeFile: string | null
|
||||
setActiveFile: (file: string) => void
|
||||
resizablePanelRef: React.RefObject<ImperativePanelHandle> | null
|
||||
tree: ReturnType<typeof createFileTreeForRegistryItemFiles> | null
|
||||
highlightedFiles:
|
||||
| (z.infer<typeof registryItemFileSchema> & {
|
||||
highlightedContent: string
|
||||
})[]
|
||||
| null
|
||||
}
|
||||
|
||||
const BlockViewerContext = React.createContext<BlockViewerContext | null>(null)
|
||||
|
||||
function useBlockViewer() {
|
||||
const context = React.useContext(BlockViewerContext)
|
||||
if (!context) {
|
||||
throw new Error("useBlockViewer must be used within a BlockViewerProvider.")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
function BlockViewerProvider({
|
||||
item,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
children,
|
||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles"> & {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [view, setView] = React.useState<BlockViewerContext["view"]>("preview")
|
||||
const [style, setStyle] =
|
||||
React.useState<BlockViewerContext["style"]>("new-york")
|
||||
const [activeFile, setActiveFile] = React.useState<
|
||||
BlockViewerContext["activeFile"]
|
||||
>(highlightedFiles?.[0].target ?? null)
|
||||
const resizablePanelRef = React.useRef<ImperativePanelHandle>(null)
|
||||
|
||||
return (
|
||||
<BlockViewerContext.Provider
|
||||
value={{
|
||||
item,
|
||||
view,
|
||||
setView,
|
||||
style,
|
||||
setStyle,
|
||||
resizablePanelRef,
|
||||
activeFile,
|
||||
setActiveFile,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
id={item.name}
|
||||
data-view={view}
|
||||
className="group/block-view-wrapper flex min-w-0 flex-col items-stretch gap-4"
|
||||
style={
|
||||
{
|
||||
"--height": item.meta?.iframeHeight ?? 450,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</BlockViewerContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerToolbar() {
|
||||
const { setView, item, resizablePanelRef, style } = useBlockViewer()
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center gap-2 md:pr-[14px]">
|
||||
<Tabs
|
||||
defaultValue="preview"
|
||||
onValueChange={(value) => setView(value as "preview" | "code")}
|
||||
className="hidden lg:flex"
|
||||
>
|
||||
<TabsList className="h-7 items-center rounded-md p-0 px-[calc(theme(spacing.1)_-_2px)] py-[theme(spacing.1)]">
|
||||
<TabsTrigger
|
||||
value="preview"
|
||||
className="h-[1.45rem] rounded-sm px-2 text-xs"
|
||||
>
|
||||
Preview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="code"
|
||||
className="h-[1.45rem] rounded-sm px-2 text-xs"
|
||||
>
|
||||
Code
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 lg:flex" />
|
||||
<a
|
||||
href={`#${item.name}`}
|
||||
className="text-sm font-medium underline-offset-2 hover:underline"
|
||||
>
|
||||
{item.description}
|
||||
</a>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden h-7 w-7 rounded-md border bg-transparent shadow-none md:flex lg:w-auto"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
copyToClipboard(`npx shadcn@latest add ${item.name}`)
|
||||
}}
|
||||
>
|
||||
{isCopied ? <Check /> : <Terminal />}
|
||||
<span className="hidden lg:inline">npx shadcn add {item.name}</span>
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 md:flex" />
|
||||
<div className="hidden h-7 items-center gap-1.5 rounded-md border p-[2px] shadow-none lg:flex">
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
defaultValue="100"
|
||||
onValueChange={(value) => {
|
||||
if (resizablePanelRef?.current) {
|
||||
resizablePanelRef.current.resize(parseInt(value))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value="100"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Desktop"
|
||||
>
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="60"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Tablet"
|
||||
>
|
||||
<Tablet className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value="30"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
title="Mobile"
|
||||
>
|
||||
<Smartphone className="h-3.5 w-3.5" />
|
||||
</ToggleGroupItem>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-[22px] w-[22px] rounded-sm p-0"
|
||||
asChild
|
||||
title="Open in New Tab"
|
||||
>
|
||||
<Link href={`/blocks/${style}/${item.name}`} target="_blank">
|
||||
<span className="sr-only">Open in New Tab</span>
|
||||
<Fullscreen className="h-3.5 w-3.5" />
|
||||
</Link>
|
||||
</Button>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<Separator orientation="vertical" className="mx-2 hidden h-4 xl:flex" />
|
||||
<V0Button
|
||||
className="hidden shadow-none sm:flex"
|
||||
id={`v0-button-${item.name}`}
|
||||
name={`v0-${item.name}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerView() {
|
||||
const { item, style, resizablePanelRef } = useBlockViewer()
|
||||
|
||||
return (
|
||||
<div className="h-[--height] group-data-[view=code]/block-view-wrapper:hidden">
|
||||
<div className="grid w-full gap-4">
|
||||
<ResizablePanelGroup direction="horizontal" className="relative z-10">
|
||||
<ResizablePanel
|
||||
ref={resizablePanelRef}
|
||||
className="relative aspect-[4/2.5] rounded-xl border bg-background md:aspect-auto"
|
||||
defaultSize={100}
|
||||
minSize={30}
|
||||
>
|
||||
<Image
|
||||
src={`/images/blocks/${item.name}.png`}
|
||||
alt={item.name}
|
||||
data-block={item.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:hidden sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={`/images/blocks/${item.name}-dark.png`}
|
||||
alt={item.name}
|
||||
data-block={item.name}
|
||||
width={1440}
|
||||
height={900}
|
||||
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background data-[block=sidebar-10]:left-auto data-[block=sidebar-10]:right-0 data-[block=sidebar-11]:-top-1/3 data-[block=sidebar-14]:left-auto data-[block=sidebar-14]:right-0 data-[block=login-01]:max-w-full data-[block=sidebar-13]:max-w-full data-[block=sidebar-15]:max-w-full dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<iframe
|
||||
src={`/blocks/${style}/${item.name}`}
|
||||
height={item.meta?.iframeHeight ?? 450}
|
||||
className="chunk-mode relative z-20 hidden w-full bg-background md:block"
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="relative hidden w-3 bg-transparent p-0 after:absolute after:right-0 after:top-1/2 after:h-8 after:w-[6px] after:-translate-y-1/2 after:translate-x-[-1px] after:rounded-full after:bg-border after:transition-all after:hover:h-10 md:block" />
|
||||
<ResizablePanel defaultSize={0} minSize={0} />
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewerCode() {
|
||||
const { activeFile, highlightedFiles } = useBlockViewer()
|
||||
|
||||
const file = React.useMemo(() => {
|
||||
return highlightedFiles?.find((file) => file.target === activeFile)
|
||||
}, [highlightedFiles, activeFile])
|
||||
|
||||
if (!file) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mr-[14px] flex h-[--height] overflow-hidden rounded-xl bg-zinc-950 text-white group-data-[view=preview]/block-view-wrapper:hidden">
|
||||
<div className="w-[280px]">
|
||||
<BlockViewerFileTree />
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="flex h-12 items-center gap-2 border-b border-zinc-700 bg-zinc-900 px-4 text-sm font-medium">
|
||||
<File className="size-4" />
|
||||
{file.target}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<BlockCopyCodeButton />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
key={file?.path}
|
||||
data-rehype-pretty-code-fragment
|
||||
dangerouslySetInnerHTML={{ __html: file?.highlightedContent ?? "" }}
|
||||
className="relative flex-1 overflow-hidden after:absolute after:inset-y-0 after:left-0 after:w-10 after:bg-zinc-950 [&_.line:before]:sticky [&_.line:before]:left-2 [&_.line:before]:z-10 [&_.line:before]:translate-y-[-1px] [&_.line:before]:pr-1 [&_pre]:h-[--height] [&_pre]:overflow-auto [&_pre]:!bg-transparent [&_pre]:pb-20 [&_pre]:pt-4 [&_pre]:font-mono [&_pre]:text-sm [&_pre]:leading-relaxed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function BlockViewerFileTree() {
|
||||
const { tree } = useBlockViewer()
|
||||
|
||||
if (!tree) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider className="flex !min-h-full flex-col">
|
||||
<Sidebar
|
||||
collapsible="none"
|
||||
className="w-full flex-1 border-r border-zinc-700 bg-zinc-900 text-white"
|
||||
>
|
||||
<SidebarGroupLabel className="h-12 rounded-none border-b border-zinc-700 px-4 text-sm text-white">
|
||||
Files
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroup className="p-0">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu className="gap-1.5">
|
||||
{tree.map((file, index) => (
|
||||
<Tree key={index} item={file} index={1} />
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</Sidebar>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function Tree({ item, index }: { item: FileTree; index: number }) {
|
||||
const { activeFile, setActiveFile } = useBlockViewer()
|
||||
|
||||
if (!item.children) {
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive={item.path === activeFile}
|
||||
onClick={() => item.path && setActiveFile(item.path)}
|
||||
className="whitespace-nowrap rounded-none pl-[--index] hover:bg-zinc-700 hover:text-white focus:bg-zinc-700 focus:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white"
|
||||
data-index={index}
|
||||
style={
|
||||
{
|
||||
"--index": `${index * (index === 2 ? 1.2 : 1.3)}rem`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<ChevronRight className="invisible" />
|
||||
<File className="h-4 w-4" />
|
||||
{item.name}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<Collapsible
|
||||
className="group/collapsible [&[data-state=open]>button>svg:first-child]:rotate-90"
|
||||
defaultOpen
|
||||
>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
className="whitespace-nowrap rounded-none pl-[--index] hover:bg-zinc-700 hover:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white data-[state=open]:hover:bg-zinc-700 data-[state=open]:hover:text-white"
|
||||
style={
|
||||
{
|
||||
"--index": `${index * (index === 1 ? 1 : 1.2)}rem`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4 transition-transform" />
|
||||
<Folder className="h-4 w-4" />
|
||||
{item.name}
|
||||
</SidebarMenuButton>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub className="m-0 w-full border-none p-0">
|
||||
{item.children.map((subItem, key) => (
|
||||
<Tree key={key} item={subItem} index={index + 1} />
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockCopyCodeButton() {
|
||||
const { activeFile, item } = useBlockViewer()
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
const file = React.useMemo(() => {
|
||||
return item.files?.find((file) => file.target === activeFile)
|
||||
}, [activeFile, item.files])
|
||||
|
||||
const content = file?.content
|
||||
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
copyToClipboard(content)
|
||||
trackEvent({
|
||||
name: "copy_block_code",
|
||||
properties: {
|
||||
name: item.name,
|
||||
file: file.path,
|
||||
},
|
||||
})
|
||||
}}
|
||||
className="h-7 w-7 shrink-0 rounded-lg p-0 hover:bg-zinc-700 hover:text-white focus:bg-zinc-700 focus:text-white focus-visible:bg-zinc-700 focus-visible:text-white active:bg-zinc-700 active:text-white data-[active=true]:bg-zinc-700 data-[active=true]:text-white [&>svg]:size-3"
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? <Check /> : <Clipboard />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function BlockViewer({
|
||||
item,
|
||||
tree,
|
||||
highlightedFiles,
|
||||
...props
|
||||
}: Pick<BlockViewerContext, "item" | "tree" | "highlightedFiles">) {
|
||||
return (
|
||||
<BlockViewerProvider
|
||||
item={item}
|
||||
tree={tree}
|
||||
highlightedFiles={highlightedFiles}
|
||||
{...props}
|
||||
>
|
||||
<BlockViewerToolbar />
|
||||
<BlockViewerView />
|
||||
<BlockViewerCode />
|
||||
</BlockViewerProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export { BlockViewer }
|
||||
@@ -1,69 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AnimatePresence, motion } from "framer-motion"
|
||||
|
||||
import { useLiftMode } from "@/hooks/use-lift-mode"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
export function BlockWrapper({
|
||||
block,
|
||||
children,
|
||||
}: React.PropsWithChildren<{ block: Block }>) {
|
||||
const { isLiftMode } = useLiftMode(block.name)
|
||||
|
||||
React.useEffect(() => {
|
||||
const components = document.querySelectorAll("[x-chunk]")
|
||||
block.chunks?.map((chunk, index) => {
|
||||
const $chunk = document.querySelector<HTMLElement>(
|
||||
`[x-chunk="${chunk.name}"]`
|
||||
)
|
||||
const $wrapper = document.querySelector<HTMLElement>(
|
||||
`[x-chunk-container="${chunk.name}"]`
|
||||
)
|
||||
|
||||
const $component = components[index]
|
||||
|
||||
if (!$chunk || !$component) {
|
||||
return
|
||||
}
|
||||
|
||||
const position = $component.getBoundingClientRect()
|
||||
$chunk.style.zIndex = "40"
|
||||
// $chunk.style.position = "absolute"
|
||||
// $chunk.style.top = `${position.top}px`
|
||||
// $chunk.style.left = `${position.left}px`
|
||||
$chunk.style.width = `${position.width}px`
|
||||
$chunk.style.height = `${position.height}px`
|
||||
|
||||
if ($wrapper) {
|
||||
$wrapper.style.zIndex = "40"
|
||||
$wrapper.style.position = "absolute"
|
||||
$wrapper.style.top = `${position.top}px`
|
||||
$wrapper.style.left = `${position.left}px`
|
||||
$wrapper.style.width = `${position.width}px`
|
||||
$wrapper.style.height = `${position.height}px`
|
||||
}
|
||||
})
|
||||
}, [block.chunks, isLiftMode])
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<AnimatePresence>
|
||||
{isLiftMode && (
|
||||
<motion.div
|
||||
className="absolute inset-0 z-30 bg-background/90 fill-mode-backwards"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { ease: "easeOut", duration: 0.38 },
|
||||
}}
|
||||
transition={{ ease: "easeOut", duration: 0.2, delay: 0.18 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/registry/new-york/ui/alert"
|
||||
|
||||
interface CalloutProps {
|
||||
icon?: string
|
||||
title?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Callout({ title, children, icon, ...props }: CalloutProps) {
|
||||
export function Callout({
|
||||
title,
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Alert> & { icon?: string }) {
|
||||
return (
|
||||
<Alert {...props}>
|
||||
<Alert className={cn("bg-muted/50", className)} {...props}>
|
||||
{icon && <span className="mr-4 text-2xl">{icon}</span>}
|
||||
{title && <AlertTitle>{title}</AlertTitle>}
|
||||
<AlertDescription>{children}</AlertDescription>
|
||||
|
||||
@@ -3,7 +3,8 @@ import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
||||
import { useThemesConfig } from "@/hooks/use-themes-config"
|
||||
import { BlockCopyButton } from "@/components/block-copy-button"
|
||||
import { ChartCopyButton } from "@/components/chart-copy-button"
|
||||
import { Chart } from "@/components/chart-display"
|
||||
import { V0Button } from "@/components/v0-button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import {
|
||||
@@ -18,13 +19,14 @@ import {
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/registry/new-york/ui/tabs"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
export function ChartCodeViewer({
|
||||
chart,
|
||||
className,
|
||||
children,
|
||||
}: { chart: Block } & React.ComponentProps<"div">) {
|
||||
}: {
|
||||
chart: Chart
|
||||
} & React.ComponentProps<"div">) {
|
||||
const [tab, setTab] = React.useState("code")
|
||||
const { themesConfig } = useThemesConfig()
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)")
|
||||
@@ -85,10 +87,10 @@ ${Object.entries(themesConfig?.activeTheme.cssVars.dark || {})
|
||||
</TabsList>
|
||||
{tab === "code" && (
|
||||
<div className="ml-auto flex items-center justify-center gap-2">
|
||||
<BlockCopyButton
|
||||
<ChartCopyButton
|
||||
event="copy_chart_code"
|
||||
name={chart.name}
|
||||
code={chart.code}
|
||||
code={chart.files?.[0]?.content ?? ""}
|
||||
/>
|
||||
<V0Button
|
||||
id={`v0-button-${chart.name}`}
|
||||
@@ -98,7 +100,7 @@ ${Object.entries(themesConfig?.activeTheme.cssVars.dark || {})
|
||||
</div>
|
||||
)}
|
||||
{tab === "theme" && (
|
||||
<BlockCopyButton
|
||||
<ChartCopyButton
|
||||
event="copy_chart_theme"
|
||||
name={chart.name}
|
||||
code={themeCode}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/registry/new-york/ui/tooltip"
|
||||
|
||||
export function BlockCopyButton({
|
||||
export function ChartCopyButton({
|
||||
event,
|
||||
name,
|
||||
code,
|
||||
@@ -1,19 +1,27 @@
|
||||
import { getBlock } from "@/lib/blocks"
|
||||
import * as React from "react"
|
||||
import { z } from "zod"
|
||||
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import { getRegistryItem } from "@/lib/registry"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ChartToolbar } from "@/components/chart-toolbar"
|
||||
import { registryEntrySchema } from "@/registry/schema"
|
||||
|
||||
export type Chart = z.infer<typeof registryEntrySchema> & {
|
||||
highlightedCode: string
|
||||
}
|
||||
|
||||
export async function ChartDisplay({
|
||||
name,
|
||||
children,
|
||||
className,
|
||||
}: { name: string } & React.ComponentProps<"div">) {
|
||||
const chart = await getBlock(name)
|
||||
const chart = await getCachedRegistryItem(name)
|
||||
const highlightedCode = await getChartHighlightedCode(
|
||||
chart?.files?.[0]?.content ?? ""
|
||||
)
|
||||
|
||||
// Cannot (and don't need to) pass to the client.
|
||||
delete chart?.component
|
||||
delete chart?.chunks
|
||||
|
||||
if (!chart) {
|
||||
if (!chart || !highlightedCode) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -25,7 +33,7 @@ export async function ChartDisplay({
|
||||
)}
|
||||
>
|
||||
<ChartToolbar
|
||||
chart={chart}
|
||||
chart={{ ...chart, highlightedCode }}
|
||||
className="relative z-20 flex justify-end border-b bg-card px-3 py-2.5 text-card-foreground"
|
||||
>
|
||||
{children}
|
||||
@@ -36,3 +44,11 @@ export async function ChartDisplay({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
||||
return await getRegistryItem(name)
|
||||
})
|
||||
|
||||
const getChartHighlightedCode = React.cache(async (content: string) => {
|
||||
return await highlightCode(content)
|
||||
})
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { BlockCopyButton } from "@/components/block-copy-button"
|
||||
import { ChartCodeViewer } from "@/components/chart-code-viewer"
|
||||
import { Separator } from "@/registry/new-york/ui/separator"
|
||||
import { Block } from "@/registry/schema"
|
||||
|
||||
import "@/styles/mdx.css"
|
||||
import {
|
||||
@@ -17,21 +15,26 @@ import {
|
||||
Radar,
|
||||
} from "lucide-react"
|
||||
|
||||
import { ChartCopyButton } from "@/components/chart-copy-button"
|
||||
import { Chart } from "@/components/chart-display"
|
||||
|
||||
export function ChartToolbar({
|
||||
chart,
|
||||
className,
|
||||
children,
|
||||
}: { chart: Block } & React.ComponentProps<"div">) {
|
||||
}: {
|
||||
chart: Chart
|
||||
} & React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<div className="flex items-center gap-1.5 pl-1 text-[13px] text-muted-foreground [&>svg]:h-[0.9rem] [&>svg]:w-[0.9rem]">
|
||||
<ChartTitle chart={chart} />
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2 [&>form]:flex">
|
||||
<BlockCopyButton
|
||||
<ChartCopyButton
|
||||
event="copy_chart_code"
|
||||
name={chart.name}
|
||||
code={chart.code}
|
||||
code={chart.files?.[0]?.content ?? ""}
|
||||
className="[&_svg]-h-3 h-6 w-6 rounded-[6px] bg-transparent text-foreground shadow-none hover:bg-muted dark:text-foreground [&_svg]:w-3"
|
||||
/>
|
||||
<Separator orientation="vertical" className="mx-0 hidden h-4 md:flex" />
|
||||
@@ -41,7 +44,7 @@ export function ChartToolbar({
|
||||
)
|
||||
}
|
||||
|
||||
function ChartTitle({ chart }: { chart: Block }) {
|
||||
function ChartTitle({ chart }: { chart: Chart }) {
|
||||
const { subcategory } = chart
|
||||
|
||||
if (!subcategory) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { type Color } from "@/lib/colors"
|
||||
import { trackEvent } from "@/lib/events"
|
||||
import { useColors } from "@/hooks/use-colors"
|
||||
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||
|
||||
export function Color({ color }: { color: Color }) {
|
||||
const { format } = useColors()
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
import * as React from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||
import {
|
||||
CircleIcon,
|
||||
FileIcon,
|
||||
LaptopIcon,
|
||||
MoonIcon,
|
||||
SunIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import { Circle, File, Laptop, Moon, Sun } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { docsConfig } from "@/config/docs"
|
||||
@@ -61,7 +55,7 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"relative h-8 w-full justify-start rounded-[0.5rem] bg-muted/50 text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64"
|
||||
"relative h-8 w-full justify-start rounded-[0.5rem] bg-muted/50 text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-56 xl:w-64"
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
{...props}
|
||||
@@ -87,7 +81,7 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
runCommand(() => router.push(navItem.href as string))
|
||||
}}
|
||||
>
|
||||
<FileIcon className="mr-2 h-4 w-4" />
|
||||
<File />
|
||||
{navItem.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -103,7 +97,7 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
}}
|
||||
>
|
||||
<div className="mr-2 flex h-4 w-4 items-center justify-center">
|
||||
<CircleIcon className="h-3 w-3" />
|
||||
<Circle className="h-3 w-3" />
|
||||
</div>
|
||||
{navItem.title}
|
||||
</CommandItem>
|
||||
@@ -113,15 +107,15 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Theme">
|
||||
<CommandItem onSelect={() => runCommand(() => setTheme("light"))}>
|
||||
<SunIcon className="mr-2 h-4 w-4" />
|
||||
<Sun />
|
||||
Light
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={() => runCommand(() => setTheme("dark"))}>
|
||||
<MoonIcon className="mr-2 h-4 w-4" />
|
||||
<Moon />
|
||||
Dark
|
||||
</CommandItem>
|
||||
<CommandItem onSelect={() => runCommand(() => setTheme("system"))}>
|
||||
<LaptopIcon className="mr-2 h-4 w-4" />
|
||||
<Laptop />
|
||||
System
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
@@ -94,10 +94,7 @@ export function ComponentPreview({
|
||||
className="absolute left-0 top-0 z-20 hidden w-[970px] max-w-none bg-background dark:block sm:w-[1280px] md:hidden md:dark:hidden"
|
||||
/>
|
||||
<div className="absolute inset-0 hidden w-[1600px] bg-background md:block">
|
||||
<iframe
|
||||
src={`/blocks/${config.style}/${name}`}
|
||||
className="size-full"
|
||||
/>
|
||||
<iframe src={`/blocks/new-york/${name}`} className="size-full" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ArrowRightIcon } from "@radix-ui/react-icons"
|
||||
import { ArrowRight } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ScrollArea, ScrollBar } from "@/registry/new-york/ui/scroll-area"
|
||||
|
||||
const examples = [
|
||||
{
|
||||
name: "Examples",
|
||||
href: "/examples/cards",
|
||||
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/mail",
|
||||
},
|
||||
{
|
||||
name: "Mail",
|
||||
href: "/examples/mail",
|
||||
@@ -18,11 +23,6 @@ const examples = [
|
||||
href: "/examples/dashboard",
|
||||
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/dashboard",
|
||||
},
|
||||
{
|
||||
name: "Cards",
|
||||
href: "/examples/cards",
|
||||
code: "https://github.com/shadcn/ui/tree/main/apps/www/app/(app)/examples/cards",
|
||||
},
|
||||
{
|
||||
name: "Tasks",
|
||||
href: "/examples/tasks",
|
||||
@@ -59,14 +59,13 @@ export function ExamplesNav({ className, ...props }: ExamplesNavProps) {
|
||||
<div className="relative">
|
||||
<ScrollArea className="max-w-[600px] lg:max-w-none">
|
||||
<div className={cn("mb-4 flex items-center", className)} {...props}>
|
||||
{examples.map((example, index) => (
|
||||
{examples.map((example) => (
|
||||
<Link
|
||||
href={example.href}
|
||||
key={example.href}
|
||||
className={cn(
|
||||
"flex h-7 items-center justify-center rounded-full px-4 text-center text-sm transition-colors hover:text-primary",
|
||||
pathname?.startsWith(example.href) ||
|
||||
(index === 0 && pathname === "/")
|
||||
pathname?.startsWith(example.href)
|
||||
? "bg-muted font-medium text-primary"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
@@ -100,7 +99,7 @@ export function ExampleCodeLink({ pathname }: ExampleCodeLinkProps) {
|
||||
className="absolute right-0 top-0 hidden items-center rounded-[0.5rem] text-sm font-medium md:flex"
|
||||
>
|
||||
View code
|
||||
<ArrowRightIcon className="ml-1 h-4 w-4" />
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,18 +12,18 @@ export function MainNav() {
|
||||
|
||||
return (
|
||||
<div className="mr-4 hidden md:flex">
|
||||
<Link href="/" className="mr-4 flex items-center space-x-2 lg:mr-6">
|
||||
<Link href="/" className="mr-4 flex items-center gap-2 lg:mr-6">
|
||||
<Icons.logo className="h-6 w-6" />
|
||||
<span className="hidden font-bold lg:inline-block">
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</Link>
|
||||
<nav className="flex items-center gap-4 text-sm lg:gap-6">
|
||||
<nav className="flex items-center gap-4 text-sm xl:gap-6">
|
||||
<Link
|
||||
href="/docs"
|
||||
className={cn(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname === "/docs" ? "text-foreground" : "text-foreground/60"
|
||||
pathname === "/docs" ? "text-foreground" : "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Docs
|
||||
@@ -35,7 +35,7 @@ export function MainNav() {
|
||||
pathname?.startsWith("/docs/components") &&
|
||||
!pathname?.startsWith("/docs/component/chart")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Components
|
||||
@@ -46,7 +46,7 @@ export function MainNav() {
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/blocks")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Blocks
|
||||
@@ -58,7 +58,7 @@ export function MainNav() {
|
||||
pathname?.startsWith("/docs/component/chart") ||
|
||||
pathname?.startsWith("/charts")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Charts
|
||||
@@ -69,29 +69,18 @@ export function MainNav() {
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/themes")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Themes
|
||||
</Link>
|
||||
<Link
|
||||
href="/examples"
|
||||
className={cn(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/examples")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
)}
|
||||
>
|
||||
Examples
|
||||
</Link>
|
||||
<Link
|
||||
href="/colors"
|
||||
className={cn(
|
||||
"transition-colors hover:text-foreground/80",
|
||||
pathname?.startsWith("/colors")
|
||||
? "text-foreground"
|
||||
: "text-foreground/60"
|
||||
: "text-foreground/80"
|
||||
)}
|
||||
>
|
||||
Colors
|
||||
|
||||
@@ -139,10 +139,10 @@ const components = {
|
||||
<hr className="my-4 md:my-8" {...props} />
|
||||
),
|
||||
table: ({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) => (
|
||||
<div className="my-6 w-full overflow-y-auto rounded-lg">
|
||||
<div className="my-6 w-full overflow-y-auto">
|
||||
<table
|
||||
className={cn(
|
||||
"relative w-full overflow-hidden text-sm after:absolute after:inset-0 after:rounded-lg after:ring-1 after:ring-border",
|
||||
"relative w-full overflow-hidden border-none text-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -150,12 +150,15 @@ const components = {
|
||||
</div>
|
||||
),
|
||||
tr: ({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) => (
|
||||
<tr className={cn("m-0 border-t", className)} {...props} />
|
||||
<tr
|
||||
className={cn("last:border-b-none m-0 border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
th: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
|
||||
<th
|
||||
className={cn(
|
||||
"border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
|
||||
"px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -164,7 +167,7 @@ const components = {
|
||||
td: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
|
||||
<td
|
||||
className={cn(
|
||||
"border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
|
||||
"px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -3,68 +3,55 @@
|
||||
import * as React from "react"
|
||||
import Link, { LinkProps } from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ViewVerticalIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import { docsConfig } from "@/config/docs"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { useMetaColor } from "@/hooks/use-meta-color"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
import { ScrollArea } from "@/registry/new-york/ui/scroll-area"
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/registry/new-york/ui/sheet"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerTrigger,
|
||||
} from "@/registry/new-york/ui/drawer"
|
||||
|
||||
export function MobileNav() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const { setMetaColor, metaColor } = useMetaColor()
|
||||
|
||||
const onOpenChange = React.useCallback(
|
||||
(open: boolean) => {
|
||||
setOpen(open)
|
||||
setMetaColor(open ? "#09090b" : metaColor)
|
||||
},
|
||||
[setMetaColor, metaColor]
|
||||
)
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="mr-2 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
|
||||
className="-ml-2 mr-2 h-8 w-8 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
|
||||
>
|
||||
<svg
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="!size-6"
|
||||
>
|
||||
<path
|
||||
d="M3 5H11"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="M3 12H16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="M3 19H21"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
d="M3.75 9h16.5m-16.5 6.75h16.5"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Toggle Menu</span>
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="pr-0">
|
||||
<MobileLink
|
||||
href="/"
|
||||
className="flex items-center"
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<Icons.logo className="mr-2 h-4 w-4" />
|
||||
<span className="font-bold">{siteConfig.name}</span>
|
||||
</MobileLink>
|
||||
<ScrollArea className="my-4 h-[calc(100vh-8rem)] pb-10 pl-6">
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[60svh] p-0">
|
||||
<div className="overflow-auto p-6">
|
||||
<div className="flex flex-col space-y-3">
|
||||
{docsConfig.mainNav?.map(
|
||||
(item) =>
|
||||
@@ -108,9 +95,9 @@ export function MobileNav() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,7 +122,7 @@ function MobileLink({
|
||||
router.push(href.toString())
|
||||
onOpenChange?.(false)
|
||||
}}
|
||||
className={cn(className)}
|
||||
className={cn("text-base", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
35
apps/www/components/mode-switcher.tsx
Normal file
35
apps/www/components/mode-switcher.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { META_THEME_COLORS } from "@/config/site"
|
||||
import { useMetaColor } from "@/hooks/use-meta-color"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export function ModeSwitcher() {
|
||||
const { setTheme, resolvedTheme } = useTheme()
|
||||
const { setMetaColor } = useMetaColor()
|
||||
|
||||
const toggleTheme = React.useCallback(() => {
|
||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
||||
setMetaColor(
|
||||
resolvedTheme === "dark"
|
||||
? META_THEME_COLORS.light
|
||||
: META_THEME_COLORS.dark
|
||||
)
|
||||
}, [resolvedTheme, setTheme, setMetaColor])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="group/toggle h-8 w-8 px-0"
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
<SunIcon className="hidden [html.dark_&]:block" />
|
||||
<MoonIcon className="hidden [html.light_&]:block" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { MoonIcon, SunIcon } from "@radix-ui/react-icons"
|
||||
import { MoonIcon, SunIcon } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
@@ -8,12 +8,12 @@ function PageHeader({
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"mx-auto flex flex-col items-start gap-2 px-4 py-8 md:py-12 md:pb-8 lg:py-12 lg:pb-10",
|
||||
"flex flex-col items-start gap-2 border-b border-border/40 py-8 dark:border-border md:py-10 lg:py-12",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<div className="container">{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import Link from "next/link"
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"
|
||||
import { Doc } from "contentlayer/generated"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { NavItem, NavItemWithChildren } from "types/nav"
|
||||
|
||||
import { docsConfig } from "@/config/docs"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
interface DocsPagerProps {
|
||||
doc: Doc
|
||||
@@ -21,22 +20,20 @@ export function DocsPager({ doc }: DocsPagerProps) {
|
||||
return (
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
{pager?.prev?.href && (
|
||||
<Link
|
||||
href={pager.prev.href}
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
>
|
||||
<ChevronLeftIcon className="mr-2 h-4 w-4" />
|
||||
{pager.prev.title}
|
||||
</Link>
|
||||
<Button variant="ghost" asChild>
|
||||
<Link href={pager.prev.href}>
|
||||
<ChevronLeft />
|
||||
{pager.prev.title}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{pager?.next?.href && (
|
||||
<Link
|
||||
href={pager.next.href}
|
||||
className={cn(buttonVariants({ variant: "outline" }), "ml-auto")}
|
||||
>
|
||||
{pager.next.title}
|
||||
<ChevronRightIcon className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
<Button variant="ghost" className="ml-auto" asChild>
|
||||
<Link href={pager.next.href}>
|
||||
{pager.next.title}
|
||||
<ChevronRight />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
import * as React from "react"
|
||||
import { Provider as JotaiProvider } from "jotai"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
import { TooltipProvider } from "@/registry/new-york/ui/tooltip"
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||
return (
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
|
||||
@@ -44,18 +44,16 @@ export function DocsSidebarNavItems({
|
||||
pathname,
|
||||
}: DocsSidebarNavItemsProps) {
|
||||
return items?.length ? (
|
||||
<div className="grid grid-flow-row auto-rows-max text-sm">
|
||||
<div className="grid grid-flow-row auto-rows-max gap-0.5 text-sm">
|
||||
{items.map((item, index) =>
|
||||
item.href && !item.disabled ? (
|
||||
<Link
|
||||
key={index}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"group flex w-full items-center rounded-md border border-transparent px-2 py-1 hover:underline",
|
||||
"group flex w-full items-center px-2 py-1 font-normal text-foreground underline-offset-2 hover:underline",
|
||||
item.disabled && "cursor-not-allowed opacity-60",
|
||||
pathname === item.href
|
||||
? "font-medium text-foreground"
|
||||
: "text-muted-foreground"
|
||||
pathname === item.href && "underline"
|
||||
)}
|
||||
target={item.external ? "_blank" : ""}
|
||||
rel={item.external ? "noreferrer" : ""}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { siteConfig } from "@/config/site"
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="py-6 md:px-8 md:py-0">
|
||||
<footer className="border-t border-border/40 py-6 dark:border-border md:px-8 md:py-0">
|
||||
<div className="container flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
||||
<p className="text-balance text-center text-sm leading-loose text-muted-foreground md:text-left">
|
||||
Built by{" "}
|
||||
|
||||
@@ -1,60 +1,35 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CommandMenu } from "@/components/command-menu"
|
||||
import { Icons } from "@/components/icons"
|
||||
import { MainNav } from "@/components/main-nav"
|
||||
import { MobileNav } from "@/components/mobile-nav"
|
||||
import { ModeToggle } from "@/components/mode-toggle"
|
||||
import { buttonVariants } from "@/registry/new-york/ui/button"
|
||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
||||
import { Button } from "@/registry/new-york/ui/button"
|
||||
|
||||
export function SiteHeader() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container flex h-14 max-w-screen-2xl items-center">
|
||||
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 dark:border-border">
|
||||
<div className="flex h-14 items-center px-4">
|
||||
<MainNav />
|
||||
<MobileNav />
|
||||
<div className="flex flex-1 items-center justify-between space-x-2 md:justify-end">
|
||||
<div className="flex flex-1 items-center justify-between gap-2 md:justify-end">
|
||||
<div className="w-full flex-1 md:w-auto md:flex-none">
|
||||
<CommandMenu />
|
||||
</div>
|
||||
<nav className="flex items-center">
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: "ghost",
|
||||
}),
|
||||
"h-8 w-8 px-0"
|
||||
)}
|
||||
<nav className="flex items-center gap-0.5">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 px-0">
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Icons.gitHub className="h-4 w-4" />
|
||||
<span className="sr-only">GitHub</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href={siteConfig.links.twitter}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: "ghost",
|
||||
}),
|
||||
"h-8 w-8 px-0"
|
||||
)}
|
||||
>
|
||||
<Icons.twitter className="h-3 w-3 fill-current" />
|
||||
<span className="sr-only">Twitter</span>
|
||||
</div>
|
||||
</Link>
|
||||
<ModeToggle />
|
||||
</Link>
|
||||
</Button>
|
||||
<ModeSwitcher />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
CheckIcon,
|
||||
CopyIcon,
|
||||
InfoCircledIcon,
|
||||
MoonIcon,
|
||||
ResetIcon,
|
||||
SunIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import template from "lodash.template"
|
||||
import template from "lodash/template"
|
||||
import { Check, Copy, Moon, Repeat, Sun } from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -79,77 +72,6 @@ export function ThemeCustomizer() {
|
||||
<Customizer />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="ml-2 hidden items-center gap-0.5">
|
||||
{mounted ? (
|
||||
<>
|
||||
{["zinc", "rose", "blue", "green", "orange"].map((color) => {
|
||||
const baseColor = baseColors.find(
|
||||
(baseColor) => baseColor.name === color
|
||||
)
|
||||
const isActive = config.theme === color
|
||||
|
||||
if (!baseColor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip key={baseColor.name}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
theme: baseColor.name,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"flex h-8 w-8 items-center justify-center rounded-full border-2 text-xs",
|
||||
isActive
|
||||
? "border-[--theme-primary]"
|
||||
: "border-transparent"
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--theme-primary": `hsl(${
|
||||
baseColor?.activeColor[
|
||||
mode === "dark" ? "dark" : "light"
|
||||
]
|
||||
})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-5 w-5 items-center justify-center rounded-full bg-[--theme-primary]"
|
||||
)}
|
||||
>
|
||||
{isActive && (
|
||||
<CheckIcon className="h-4 w-4 text-white" />
|
||||
)}
|
||||
</span>
|
||||
<span className="sr-only">{baseColor.label}</span>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
align="center"
|
||||
className="rounded-[0.5rem] bg-zinc-900 text-zinc-50"
|
||||
>
|
||||
{baseColor.label}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<div className="mr-1 flex items-center gap-4">
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CopyCodeButton variant="ghost" size="sm" className="[&_svg]:hidden" />
|
||||
</div>
|
||||
@@ -173,10 +95,10 @@ function Customizer() {
|
||||
<div className="flex items-start pt-4 md:pt-0">
|
||||
<div className="space-y-1 pr-2">
|
||||
<div className="font-semibold leading-none tracking-tight">
|
||||
Customize
|
||||
Theme Customizer
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Pick a style and color for your components.
|
||||
Customize your components colors.
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
@@ -191,110 +113,58 @@ function Customizer() {
|
||||
})
|
||||
}}
|
||||
>
|
||||
<ResetIcon />
|
||||
<Repeat />
|
||||
<span className="sr-only">Reset</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col space-y-4 md:space-y-6">
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex w-full items-center">
|
||||
<Label className="text-xs">Style</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<InfoCircledIcon className="ml-1 h-3 w-3" />
|
||||
<span className="sr-only">About styles</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="space-y-3 rounded-[0.5rem] text-sm"
|
||||
side="right"
|
||||
align="start"
|
||||
alignOffset={-20}
|
||||
>
|
||||
<p className="font-medium">
|
||||
What is the difference between the New York and Default style?
|
||||
</p>
|
||||
<p>
|
||||
A style comes with its own set of components, animations,
|
||||
icons and more.
|
||||
</p>
|
||||
<p>
|
||||
The <span className="font-medium">Default</span> style has
|
||||
larger inputs, uses lucide-react for icons and
|
||||
tailwindcss-animate for animations.
|
||||
</p>
|
||||
<p>
|
||||
The <span className="font-medium">New York</span> style ships
|
||||
with smaller buttons and cards with shadows. It uses icons
|
||||
from Radix Icons.
|
||||
</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
onClick={() => setConfig({ ...config, style: "default" })}
|
||||
className={cn(
|
||||
config.style === "default" && "border-2 border-primary"
|
||||
)}
|
||||
>
|
||||
Default
|
||||
</Button>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
onClick={() => setConfig({ ...config, style: "new-york" })}
|
||||
className={cn(
|
||||
config.style === "new-york" && "border-2 border-primary"
|
||||
)}
|
||||
>
|
||||
New York
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Color</Label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{baseColors.map((theme) => {
|
||||
const isActive = config.theme === theme.name
|
||||
|
||||
return mounted ? (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
key={theme.name}
|
||||
onClick={() => {
|
||||
setConfig({
|
||||
...config,
|
||||
theme: theme.name,
|
||||
})
|
||||
}}
|
||||
className={cn(
|
||||
"justify-start",
|
||||
isActive && "border-2 border-primary"
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--theme-primary": `hsl(${
|
||||
theme?.activeColor[mode === "dark" ? "dark" : "light"]
|
||||
})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]"
|
||||
)}
|
||||
>
|
||||
{isActive && <CheckIcon className="h-4 w-4 text-white" />}
|
||||
</span>
|
||||
{theme.label}
|
||||
</Button>
|
||||
) : (
|
||||
<Skeleton className="h-8 w-full" key={theme.name} />
|
||||
{baseColors
|
||||
.filter(
|
||||
(theme) =>
|
||||
!["slate", "stone", "gray", "neutral"].includes(theme.name)
|
||||
)
|
||||
})}
|
||||
.map((theme) => {
|
||||
const isActive = config.theme === theme.name
|
||||
|
||||
return mounted ? (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
size="sm"
|
||||
key={theme.name}
|
||||
onClick={() => {
|
||||
setConfig({
|
||||
...config,
|
||||
theme: theme.name,
|
||||
})
|
||||
}}
|
||||
className={cn(
|
||||
"justify-start",
|
||||
isActive && "border-2 border-primary"
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--theme-primary": `hsl(${
|
||||
theme?.activeColor[mode === "dark" ? "dark" : "light"]
|
||||
})`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]"
|
||||
)}
|
||||
>
|
||||
{isActive && <Check className="h-4 w-4 text-white" />}
|
||||
</span>
|
||||
{theme.label}
|
||||
</Button>
|
||||
) : (
|
||||
<Skeleton className="h-8 w-full" key={theme.name} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
@@ -334,7 +204,7 @@ function Customizer() {
|
||||
onClick={() => setMode("light")}
|
||||
className={cn(mode === "light" && "border-2 border-primary")}
|
||||
>
|
||||
<SunIcon className="mr-1 -translate-x-1" />
|
||||
<Sun className="mr-1 -translate-x-1" />
|
||||
Light
|
||||
</Button>
|
||||
<Button
|
||||
@@ -343,7 +213,7 @@ function Customizer() {
|
||||
onClick={() => setMode("dark")}
|
||||
className={cn(mode === "dark" && "border-2 border-primary")}
|
||||
>
|
||||
<MoonIcon className="mr-1 -translate-x-1" />
|
||||
<Moon className="mr-1 -translate-x-1" />
|
||||
Dark
|
||||
</Button>
|
||||
</>
|
||||
@@ -391,11 +261,7 @@ function CopyCodeButton({
|
||||
className={cn("md:hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
{hasCopied ? (
|
||||
<CheckIcon className="mr-2 h-4 w-4" />
|
||||
) : (
|
||||
<CopyIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{hasCopied ? <Check /> : <Copy />}
|
||||
Copy code
|
||||
</Button>
|
||||
)}
|
||||
@@ -432,11 +298,7 @@ function CopyCodeButton({
|
||||
}}
|
||||
className="absolute right-4 top-4 bg-muted text-muted-foreground hover:bg-muted hover:text-muted-foreground"
|
||||
>
|
||||
{hasCopied ? (
|
||||
<CheckIcon className="mr-2 h-4 w-4" />
|
||||
) : (
|
||||
<CopyIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{hasCopied ? <Check /> : <Copy />}
|
||||
Copy
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,10 @@ export interface DocsConfig {
|
||||
|
||||
export const docsConfig: DocsConfig = {
|
||||
mainNav: [
|
||||
{
|
||||
title: "Home",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
href: "/docs",
|
||||
@@ -28,10 +32,6 @@ export const docsConfig: DocsConfig = {
|
||||
title: "Themes",
|
||||
href: "/themes",
|
||||
},
|
||||
{
|
||||
title: "Examples",
|
||||
href: "/examples",
|
||||
},
|
||||
{
|
||||
title: "Colors",
|
||||
href: "/colors",
|
||||
@@ -71,6 +71,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/cli",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Next.js 15 + React 19",
|
||||
href: "/docs/react-19",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Typography",
|
||||
href: "/docs/components/typography",
|
||||
|
||||
@@ -11,3 +11,8 @@ export const siteConfig = {
|
||||
}
|
||||
|
||||
export type SiteConfig = typeof siteConfig
|
||||
|
||||
export const META_THEME_COLORS = {
|
||||
light: "#ffffff",
|
||||
dark: "#09090b",
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ Options:
|
||||
-y, --yes skip confirmation prompt. (default: false)
|
||||
-o, --overwrite overwrite existing files. (default: false)
|
||||
-c, --cwd <cwd> the working directory. defaults to the current directory.
|
||||
-a, --all add all available components. (default: false)
|
||||
-p, --path <path> the path to add the component to.
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
@@ -88,3 +88,14 @@ You can use the `<Calendar>` component to build a date picker. See the [Date Pic
|
||||
### Form
|
||||
|
||||
<ComponentPreview name="calendar-form" />
|
||||
|
||||
## Changelog
|
||||
|
||||
### 11-03-2024 day_outside color
|
||||
|
||||
- Changed the color of the `day_outside` class to the following to improve contrast:
|
||||
|
||||
```tsx showLineNumbers title="calendar.tsx"
|
||||
"day_outside:
|
||||
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
||||
```
|
||||
|
||||
@@ -72,3 +72,35 @@ import {
|
||||
name="card-demo"
|
||||
description="A card showing notifications settings."
|
||||
/>
|
||||
|
||||
## Changelog
|
||||
|
||||
### 11-03-2024 a11y for title and description
|
||||
|
||||
- Changed the `CardTitle` and `CardDescription` components to use `div` instead of `h3` and `p` to improve accessibility.
|
||||
|
||||
```tsx showLineNumbers title="card.tsx"
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
```
|
||||
|
||||
@@ -4,6 +4,12 @@ description: Beautiful charts. Built using Recharts. Copy and paste into your ap
|
||||
component: true
|
||||
---
|
||||
|
||||
<Callout className="mb-6">
|
||||
|
||||
**Note:** If you are using charts with **React 19** or the **Next.js 15**, see the note [here](/docs/react-19#recharts).
|
||||
|
||||
</Callout>
|
||||
|
||||
<ComponentPreview
|
||||
name="chart-bar-interactive"
|
||||
className="-mt-4 [&_.preview]:p-0 [&_.preview]:border-t [&_.preview>div]:shadow-none [&_.preview>div]:border-none [&_.preview>div]:w-full [&_.preview]:lg:min-h-[404px]"
|
||||
@@ -47,7 +53,7 @@ We do not wrap Recharts. This means you're not locked into an abstraction. When
|
||||
|
||||
<Callout className="mt-4">
|
||||
|
||||
**Note:** If you are trying to use charts with **React 19** or the **Next.js 15**, you will need the [recharts@alpha](https://github.com/recharts/recharts/releases/tag/v2.13.0-alpha.4) release currently.
|
||||
**Note:** If you are using charts with **React 19** or the **Next.js 15**, see the note [here](/docs/react-19#recharts).
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
@@ -136,3 +136,27 @@ export function CommandMenu() {
|
||||
### Combobox
|
||||
|
||||
You can use the `<Command />` component as a combobox. See the [Combobox](/docs/components/combobox) page for more information.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2024-10-25 Classes for icons
|
||||
|
||||
Added `gap-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0` to the `<CommandItem />` to automatically style icon inside.
|
||||
|
||||
Add the following classes to the `cva` call in your `command.tsx` file.
|
||||
|
||||
```tsx showLineNumbers title="command.tsx"
|
||||
const CommandItem = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"... gap-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
```
|
||||
|
||||
@@ -120,3 +120,23 @@ const DropdownMenuItem = React.forwardRef<
|
||||
/>
|
||||
))
|
||||
```
|
||||
|
||||
### 2024-10-25 Classes for `<DropdownMenuSubTrigger />`
|
||||
|
||||
Added `gap-2 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0` to the `<DropdownMenuSubTrigger />` to automatically style icon inside.
|
||||
|
||||
Add the following classes to the `cva` call in your `dropdown-menu.tsx` file.
|
||||
|
||||
```tsx title="dropdown-menu.tsx"
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{/* ... */}
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
```
|
||||
|
||||
@@ -406,6 +406,40 @@ You can change the keyboard shortcut by updating the `SIDEBAR_KEYBOARD_SHORTCUT`
|
||||
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
||||
```
|
||||
|
||||
### Persisted State
|
||||
|
||||
The `SidebarProvider` supports persisting the sidebar state across page reloads and server-side rendering. It uses cookies to store the current state of the sidebar. When the sidebar state changes, a default cookie named `sidebar:state` is set with the current open/closed state. This cookie is then read on subsequent page loads to restore the sidebar state.
|
||||
|
||||
To persist sidebar state in Next.js, set up your `SidebarProvider` in `app/layout.tsx` like this:
|
||||
|
||||
```tsx showLineNumbers title="app/layout.tsx"
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
|
||||
import { AppSidebar } from "@/components/app-sidebar"
|
||||
|
||||
export async function Layout({ children }: { children: React.ReactNode }) {
|
||||
const cookieStore = await cookies()
|
||||
const defaultOpen = cookieStore.get("sidebar:state")?.value === "true"
|
||||
|
||||
return (
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<AppSidebar />
|
||||
<main>
|
||||
<SidebarTrigger />
|
||||
{children}
|
||||
</main>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can change the name of the cookie by updating the `SIDEBAR_COOKIE_NAME` variable in `sidebar.tsx`.
|
||||
|
||||
```tsx showLineNumbers title="components/ui/sidebar.tsx"
|
||||
const SIDEBAR_COOKIE_NAME = "sidebar:state"
|
||||
```
|
||||
|
||||
## Sidebar
|
||||
|
||||
The main `Sidebar` component used to render a collapsible sidebar.
|
||||
@@ -1323,7 +1357,30 @@ You can find more tips on using states for styling in this [Twitter thread](http
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2024-10-21
|
||||
### 2024-10-30 Cookie handling in setOpen
|
||||
|
||||
- [#5593](https://github.com/shadcn-ui/ui/pull/5593) - Improved setOpen callback logic in `<SidebarProvider>`.
|
||||
|
||||
Update the `setOpen` callback in `<SidebarProvider>` as follows:
|
||||
|
||||
```tsx showLineNumbers
|
||||
const setOpen = React.useCallback(
|
||||
(value: boolean | ((value: boolean) => boolean)) => {
|
||||
const openState = typeof value === "function" ? value(open) : value
|
||||
if (setOpenProp) {
|
||||
setOpenProp(openState)
|
||||
} else {
|
||||
_setOpen(openState)
|
||||
}
|
||||
|
||||
// This sets the cookie to keep the sidebar state.
|
||||
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
|
||||
},
|
||||
[setOpenProp, open]
|
||||
)
|
||||
```
|
||||
|
||||
### 2024-10-21 Fixed `text-sidebar-foreground`
|
||||
|
||||
- [#5491](https://github.com/shadcn-ui/ui/pull/5491) - Moved `text-sidebar-foreground` from `<SidebarProvider>` to `<Sidebar>` component.
|
||||
|
||||
|
||||
@@ -22,9 +22,11 @@ npm install next-themes
|
||||
|
||||
import * as React from "react"
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Every component recreated in Figma. With customizable props, typogr
|
||||
|
||||
## Paid
|
||||
|
||||
- [shadcn/ui kit](http://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
|
||||
- [shadcn/ui kit](https://shadcndesign.com) by [ Matt Wierzbicki](https://x.com/matsugfx) - A premium, always up-to-date UI kit for Figma - shadcn/ui compatible and optimized for smooth design-to-dev handoff.
|
||||
|
||||
## Free
|
||||
|
||||
|
||||
@@ -16,28 +16,14 @@ Components are styled using Tailwind CSS. You need to install Tailwind CSS in yo
|
||||
Add the following dependencies to your project:
|
||||
|
||||
```bash
|
||||
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge
|
||||
```
|
||||
|
||||
### Add icon library
|
||||
|
||||
If you're using the `default` style, install `lucide-react`:
|
||||
|
||||
```bash
|
||||
npm install lucide-react
|
||||
```
|
||||
|
||||
If you're using the `new-york` style, install `@radix-ui/react-icons`:
|
||||
|
||||
```bash
|
||||
npm install @radix-ui/react-icons
|
||||
npm install tailwindcss-animate class-variance-authority clsx tailwind-merge lucide-react
|
||||
```
|
||||
|
||||
### Configure path aliases
|
||||
|
||||
I use the `@` alias. This is how I configure it in tsconfig.json:
|
||||
Configure the path aliases in your `tsconfig.json` file.
|
||||
|
||||
```json {3-6} title="tsconfig.json"
|
||||
```json {3-6} title="tsconfig.json" showLineNumbers
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
@@ -50,27 +36,16 @@ I use the `@` alias. This is how I configure it in tsconfig.json:
|
||||
|
||||
The `@` alias is a preference. You can use other aliases if you want.
|
||||
|
||||
**If you use a different alias such as ~, you'll need to update import statements when adding components.**
|
||||
|
||||
### Configure tailwind.config.js
|
||||
|
||||
Here's what my `tailwind.config.js` file looks like:
|
||||
|
||||
```js title="tailwind.config.js"
|
||||
const { fontFamily } = require("tailwindcss/defaultTheme")
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
@@ -112,23 +87,6 @@ module.exports = {
|
||||
md: `calc(var(--radius) - 2px)`,
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", ...fontFamily.sans],
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
@@ -148,67 +106,46 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 1.2%;
|
||||
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,17 +154,14 @@ Add the following to your styles/globals.css file. You can learn more about usin
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
@apply font-sans antialiased bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add a cn helper
|
||||
|
||||
I use a `cn` helper to make it easier to conditionally add Tailwind CSS classes. Here's how I define it in `lib/utils.ts`:
|
||||
|
||||
```ts title="lib/utils.ts"
|
||||
```ts title="lib/utils.ts" showLineNumbers
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
@@ -236,6 +170,34 @@ export function cn(...inputs: ClassValue[]) {
|
||||
}
|
||||
```
|
||||
|
||||
### Create a `components.json` file
|
||||
|
||||
Create a `components.json` file in the root of your project.
|
||||
|
||||
```json title="components.json" showLineNumbers
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
```
|
||||
|
||||
### That's it
|
||||
|
||||
You can now start adding components to your project.
|
||||
|
||||
@@ -3,6 +3,12 @@ title: Next.js
|
||||
description: Install and configure Next.js.
|
||||
---
|
||||
|
||||
<Callout>
|
||||
|
||||
**If you're using Next.js 15, see the [Next.js 15 + React 19](/docs/react-19) guide.**
|
||||
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create project
|
||||
|
||||
@@ -23,6 +23,29 @@ npm install -D tailwindcss postcss autoprefixer
|
||||
npx tailwindcss init -p
|
||||
```
|
||||
|
||||
Add this import header in your main css file, `src/index.css` in our case:
|
||||
|
||||
```css {1-3} showLineNumbers
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* ... */
|
||||
```
|
||||
|
||||
Configure the tailwind template paths in `tailwind.config.js`:
|
||||
|
||||
```js {3}
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
```
|
||||
|
||||
### Edit tsconfig.json file
|
||||
|
||||
The current version of Vite splits TypeScript configuration into three files, two of which need to be edited.
|
||||
|
||||
168
apps/www/content/docs/react-19.mdx
Normal file
168
apps/www/content/docs/react-19.mdx
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Next.js 15 + React 19
|
||||
description: Using shadcn/ui with Next.js 15 and React 19.
|
||||
---
|
||||
|
||||
<Callout>
|
||||
**The following guide applies to any framework that supports React 19**. I
|
||||
titled this page "Next.js 15 + React 19" to help people upgrading to Next.js
|
||||
15 find it. We are working with package maintainers to help upgrade to React
|
||||
19.
|
||||
</Callout>
|
||||
|
||||
## TL;DR
|
||||
|
||||
If you're using `npm`, you can install shadcn/ui dependencies with a flag. The `shadcn` CLI will prompt you to select a flag when you run it. No flags required for pnpm, bun, or yarn.
|
||||
|
||||
See [Upgrade Status](#upgrade-status) for the status of React 19 support for each package.
|
||||
|
||||
## What's happening?
|
||||
|
||||
React 19 is now [rc](https://www.npmjs.com/package/react?activeTab=versions) and is [tested and supported in the latest Next.js 15 release](https://nextjs.org/blog/next-15#react-19).
|
||||
|
||||
To support React 19, package maintainers will need to test and update their packages to include React 19 as a peer dependency. This is [already](https://github.com/radix-ui/primitives/pull/2952) [in](https://github.com/pacocoursey/cmdk/pull/318) [progress](https://github.com/emilkowalski/vaul/pull/498).
|
||||
|
||||
```diff /^19.0/
|
||||
"peerDependencies": {
|
||||
- "react": "^16.8 || ^17.0 || ^18.0",
|
||||
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0",
|
||||
- "react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0"
|
||||
},
|
||||
```
|
||||
|
||||
<Callout>
|
||||
You can check if a package lists React 19 as a peer dependency by running
|
||||
`npm info <package> peerDependencies`.
|
||||
</Callout>
|
||||
|
||||
In the meantime, if you are installing a package that **does not** list React 19 as a peer dependency, you will see an error message like this:
|
||||
|
||||
```bash
|
||||
npm error code ERESOLVE
|
||||
npm error ERESOLVE unable to resolve dependency tree
|
||||
npm error
|
||||
npm error While resolving: my-app@0.1.0
|
||||
npm error Found: react@19.0.0-rc-69d4b800-20241021
|
||||
npm error node_modules/react
|
||||
npm error react@"19.0.0-rc-69d4b800-20241021" from the root project
|
||||
```
|
||||
|
||||
<Callout>
|
||||
**Note:** This is npm only. PNPM and Bun will only show a silent warning.
|
||||
</Callout>
|
||||
|
||||
## How to fix this
|
||||
|
||||
### Solution 1: `--force` or `--legacy-peer-deps`
|
||||
|
||||
You can force install a package with the `--force` or the `--legacy-peer-deps` flag.
|
||||
|
||||
```bash
|
||||
npm i <package> --force
|
||||
|
||||
npm i <package> --legacy-peer-deps
|
||||
```
|
||||
|
||||
This will install the package and ignore the peer dependency warnings.
|
||||
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="flags">
|
||||
<AccordionTrigger className="font-medium">
|
||||
What do the `--force` and `--legacy-peer-deps` flag do?
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="[&_ul]:mt-0">
|
||||
|
||||
- `--force`: Ignores and overrides any dependency conflicts, forcing the
|
||||
installation of packages.
|
||||
- `--legacy-peer-deps`: Skips strict peer dependency checks, allowing
|
||||
installation of packages with unmet peer dependencies to avoid errors.
|
||||
|
||||
</AccordionContent>
|
||||
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
### Solution 2: Use React 18
|
||||
|
||||
You can downgrade `react` and `react-dom` to version 18, which is compatible with the package you are installing and upgrade when the dependency is updated.
|
||||
|
||||
```bash
|
||||
npm i react@18 react-dom@18
|
||||
```
|
||||
|
||||
Whichever solution you choose, make sure you test your app thoroughly to ensure
|
||||
there are no regressions.
|
||||
|
||||
## Using shadcn/ui on Next.js 15
|
||||
|
||||
### Using pnpm, bun, or yarn
|
||||
|
||||
Follow the instructions in the [installation guide](/docs/installation/next) to install shadcn/ui. No flags are needed.
|
||||
|
||||
### Using npm
|
||||
|
||||
When you run `npx shadcn@latest init -d`, you will be prompted to select an option to resolve the peer dependency issues.
|
||||
|
||||
```bash
|
||||
It looks like you are using React 19.
|
||||
Some packages may fail to install due to peer dependency issues (see https://ui.shadcn.com/react-19).
|
||||
|
||||
? How would you like to proceed? › - Use arrow-keys. Return to submit.
|
||||
❯ Use --force
|
||||
Use --legacy-peer-deps
|
||||
```
|
||||
|
||||
You can then run the command with the flag you choose.
|
||||
|
||||
## Adding components
|
||||
|
||||
The process for adding components is the same as above. Select a flag to resolve the peer dependency issues.
|
||||
|
||||
**Remember to always test your app after installing new dependencies.**
|
||||
|
||||
## Upgrade Status
|
||||
|
||||
To make it easy for you track the progress of the upgrade, I've created a table below with React 19 support status for the shadcn/ui dependencies.
|
||||
|
||||
- ✅ - Works with React 19 using npm, pnpm, and bun.
|
||||
- 🚧 - Works with React 19 using pnpm and bun. Requires flag for npm. PR is in progress.
|
||||
|
||||
| Package | Status | Note |
|
||||
| ---------------------------------------------------------------------------------- | ------ | ----------------------------------------------------------- |
|
||||
| [radix-ui](https://www.npmjs.com/package/@radix-ui/react-icons) | ✅ | |
|
||||
| [lucide-react](https://www.npmjs.com/package/lucide-react) | ✅ | |
|
||||
| [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) | ✅ | Does not list React 19 as a peer dependency. |
|
||||
| [tailwindcss-animate](https://www.npmjs.com/package/tailwindcss-animate) | ✅ | Does not list React 19 as a peer dependency. |
|
||||
| [embla-carousel-react](https://www.npmjs.com/package/embla-carousel-react) | ✅ | |
|
||||
| [recharts](https://www.npmjs.com/package/recharts) | ✅ | See note [below](#recharts) |
|
||||
| [react-hook-form](https://www.npmjs.com/package/react-hook-form) | ✅ | |
|
||||
| [react-resizable-panels](https://www.npmjs.com/package/react-resizable-panels) | ✅ | |
|
||||
| [sonner](https://www.npmjs.com/package/sonner) | ✅ | |
|
||||
| [react-day-picker](https://www.npmjs.com/package/react-day-picker) | ✅ | Works with flag for npm. Work to upgrade to v9 in progress. |
|
||||
| [input-otp](https://www.npmjs.com/package/input-otp) | ✅ | |
|
||||
| [vaul](https://www.npmjs.com/package/vaul) | ✅ | |
|
||||
| [@radix-ui/react-icons](https://www.npmjs.com/package/@radix-ui/react-icons) | 🚧 | See [PR #194](https://github.com/radix-ui/icons/pull/194) |
|
||||
| [cmdk](https://www.npmjs.com/package/cmdk) | ✅ | |
|
||||
|
||||
If you have any questions, please [open an issue](https://github.com/shadcn/ui/issues) on GitHub.
|
||||
|
||||
## Recharts
|
||||
|
||||
To use recharts with React 19, you will need to override the `react-is` dependency.
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Add the following to your `package.json`</Step>
|
||||
|
||||
```json title="package.json"
|
||||
"overrides": {
|
||||
"react-is": "^19.0.0-rc-69d4b800-20241021"
|
||||
}
|
||||
```
|
||||
|
||||
Note: the `react-is` version needs to match the version of React 19 you are using. The above is an example.
|
||||
|
||||
<Step>Run `npm install --legacy-peer-deps`</Step>
|
||||
|
||||
</Steps>
|
||||
25
apps/www/hooks/use-meta-color.ts
Normal file
25
apps/www/hooks/use-meta-color.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
import { useTheme } from "next-themes"
|
||||
|
||||
import { META_THEME_COLORS } from "@/config/site"
|
||||
|
||||
export function useMetaColor() {
|
||||
const { resolvedTheme } = useTheme()
|
||||
|
||||
const metaColor = React.useMemo(() => {
|
||||
return resolvedTheme !== "dark"
|
||||
? META_THEME_COLORS.light
|
||||
: META_THEME_COLORS.dark
|
||||
}, [resolvedTheme])
|
||||
|
||||
const setMetaColor = React.useCallback((color: string) => {
|
||||
document
|
||||
.querySelector('meta[name="theme-color"]')
|
||||
?.setAttribute("content", color)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
metaColor,
|
||||
setMetaColor,
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,14 @@
|
||||
"use server"
|
||||
|
||||
import { promises as fs } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { Index } from "@/__registry__"
|
||||
import { Project, ScriptKind, SourceFile, SyntaxKind } from "ts-morph"
|
||||
import { z } from "zod"
|
||||
|
||||
import { highlightCode } from "@/lib/highlight-code"
|
||||
import { Style } from "@/registry/registry-styles"
|
||||
import { BlockChunk, blockSchema, registryEntrySchema } from "@/registry/schema"
|
||||
import { registryEntrySchema } from "@/registry/schema"
|
||||
|
||||
const DEFAULT_BLOCKS_STYLE = "new-york" satisfies Style["name"]
|
||||
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
const BLOCKS_WHITELIST_PREFIXES = ["sidebar", "login"]
|
||||
const REGISTRY_BLOCK_TYPES = ["registry:block"]
|
||||
|
||||
export async function getAllBlockIds(
|
||||
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
||||
@@ -24,136 +17,14 @@ export async function getAllBlockIds(
|
||||
return blocks.map((block) => block.name)
|
||||
}
|
||||
|
||||
export async function getBlock(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
||||
) {
|
||||
const entry = Index[style][name]
|
||||
|
||||
const content = await _getBlockContent(name, style)
|
||||
|
||||
const chunks = entry?.chunks
|
||||
? await Promise.all(
|
||||
entry?.chunks?.map(async (chunk: BlockChunk) => {
|
||||
const code = await readFile(chunk.file)
|
||||
|
||||
const tempFile = await createTempSourceFile(`${chunk.name}.tsx`)
|
||||
const sourceFile = project.createSourceFile(tempFile, code, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
sourceFile
|
||||
.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
|
||||
.filter((node) => {
|
||||
return node.getAttribute("x-chunk") !== undefined
|
||||
})
|
||||
?.map((component) => {
|
||||
component
|
||||
.getAttribute("x-chunk")
|
||||
?.asKind(SyntaxKind.JsxAttribute)
|
||||
?.remove()
|
||||
})
|
||||
|
||||
return {
|
||||
...chunk,
|
||||
code: sourceFile
|
||||
.getText()
|
||||
.replaceAll(`@/registry/${style}/`, "@/components/"),
|
||||
}
|
||||
})
|
||||
)
|
||||
: []
|
||||
|
||||
const block = {
|
||||
style,
|
||||
highlightedCode: content.code ? await highlightCode(content.code) : "",
|
||||
...entry,
|
||||
...content,
|
||||
chunks,
|
||||
type: "registry:block",
|
||||
}
|
||||
|
||||
const result = blockSchema.safeParse(block)
|
||||
|
||||
if (!result.success) {
|
||||
console.log(block)
|
||||
return null
|
||||
}
|
||||
|
||||
return result.data
|
||||
}
|
||||
|
||||
async function _getAllBlocks(style: Style["name"] = DEFAULT_BLOCKS_STYLE) {
|
||||
const index = z.record(registryEntrySchema).parse(Index[style])
|
||||
|
||||
return Object.values(index).filter((block) => block.type === "registry:block")
|
||||
}
|
||||
|
||||
async function _getBlockCode(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_BLOCKS_STYLE
|
||||
) {
|
||||
const entry = Index[style][name]
|
||||
if (!entry) {
|
||||
console.error(`Block ${name} not found in style ${style}`)
|
||||
return ""
|
||||
}
|
||||
const block = registryEntrySchema.parse(entry)
|
||||
|
||||
if (!block.source) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return await readFile(block.source)
|
||||
}
|
||||
|
||||
async function readFile(source: string) {
|
||||
const filepath = path.join(process.cwd(), source)
|
||||
return await fs.readFile(filepath, "utf-8")
|
||||
}
|
||||
|
||||
async function createTempSourceFile(filename: string) {
|
||||
const dir = await fs.mkdtemp(path.join(tmpdir(), "codex-"))
|
||||
return path.join(dir, filename)
|
||||
}
|
||||
|
||||
async function _getBlockContent(name: string, style: Style["name"]) {
|
||||
const raw = await _getBlockCode(name, style)
|
||||
|
||||
const tempFile = await createTempSourceFile(`${name}.tsx`)
|
||||
const sourceFile = project.createSourceFile(tempFile, raw, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
// Extract meta.
|
||||
const iframeHeight = _extractVariable(sourceFile, "iframeHeight")
|
||||
const containerClassName = _extractVariable(sourceFile, "containerClassName")
|
||||
|
||||
// Format the code.
|
||||
let code = sourceFile.getText()
|
||||
code = code.replaceAll(`@/registry/${style}/`, "@/components/")
|
||||
code = code.replaceAll("export default", "export")
|
||||
|
||||
return {
|
||||
code,
|
||||
container: {
|
||||
height: iframeHeight,
|
||||
className: containerClassName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function _extractVariable(sourceFile: SourceFile, name: string) {
|
||||
const variable = sourceFile.getVariableDeclaration(name)
|
||||
if (!variable) {
|
||||
return null
|
||||
}
|
||||
|
||||
const value = variable
|
||||
.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral)
|
||||
.getLiteralValue()
|
||||
|
||||
variable.remove()
|
||||
|
||||
return value
|
||||
return Object.values(index).filter((block) =>
|
||||
BLOCKS_WHITELIST_PREFIXES.some(
|
||||
(prefix) =>
|
||||
block.name.startsWith(prefix) &&
|
||||
REGISTRY_BLOCK_TYPES.includes(block.type)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
// import { JetBrains_Mono as FontMono, Inter as FontSans } from "next/font/google"
|
||||
import { JetBrains_Mono as FontMono } from "next/font/google"
|
||||
// import { GeistMono } from "geist/font/mono"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
import { GeistSans } from "geist/font/sans"
|
||||
|
||||
// export const fontSans = FontSans({
|
||||
// subsets: ["latin"],
|
||||
// variable: "--font-sans",
|
||||
// })
|
||||
export const fontSans = GeistSans
|
||||
|
||||
export const fontMono = FontMono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-mono",
|
||||
})
|
||||
export const fontMono = GeistMono
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"use server"
|
||||
|
||||
import { codeToHtml } from "shiki"
|
||||
|
||||
export async function highlightCode(code: string) {
|
||||
const html = codeToHtml(code, {
|
||||
lang: "typescript",
|
||||
const html = await codeToHtml(code, {
|
||||
lang: "jsx",
|
||||
theme: "github-dark-default",
|
||||
transformers: [
|
||||
{
|
||||
|
||||
37
apps/www/lib/registry.test.ts
Normal file
37
apps/www/lib/registry.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import { createFileTreeForRegistryItemFiles } from "@/lib/registry"
|
||||
|
||||
describe("createFileTreeForRegistryItemFiles", () => {
|
||||
it("should create a nested file tree structure", async () => {
|
||||
const files = [
|
||||
{ path: "page.tsx" },
|
||||
{ path: "components/foo.tsx" },
|
||||
{ path: "components/baz.tsx" },
|
||||
{ path: "components/boo/quip.tsx" },
|
||||
]
|
||||
|
||||
const expectedOutput = [
|
||||
{ name: "page.tsx", path: "page.tsx" },
|
||||
{
|
||||
name: "components",
|
||||
children: [
|
||||
{ name: "foo.tsx", path: "components/foo.tsx" },
|
||||
{ name: "baz.tsx", path: "components/baz.tsx" },
|
||||
{
|
||||
name: "boo",
|
||||
children: [{ name: "quip.tsx", path: "components/boo/quip.tsx" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = await createFileTreeForRegistryItemFiles(files)
|
||||
expect(result).toEqual(expectedOutput)
|
||||
})
|
||||
|
||||
it("should return an empty array for empty input", async () => {
|
||||
const result = await createFileTreeForRegistryItemFiles([])
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
270
apps/www/lib/registry.ts
Normal file
270
apps/www/lib/registry.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { promises as fs } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { Index } from "@/__registry__"
|
||||
import { Project, ScriptKind, SourceFile, SyntaxKind } from "ts-morph"
|
||||
import { z } from "zod"
|
||||
|
||||
import { Style } from "@/registry/registry-styles"
|
||||
import { registryEntrySchema, registryItemFileSchema } from "@/registry/schema"
|
||||
|
||||
export const DEFAULT_REGISTRY_STYLE = "new-york" satisfies Style["name"]
|
||||
|
||||
const memoizedIndex: typeof Index = Object.fromEntries(
|
||||
Object.entries(Index).map(([style, items]) => [style, { ...items }])
|
||||
)
|
||||
|
||||
export function getRegistryComponent(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_REGISTRY_STYLE
|
||||
) {
|
||||
return memoizedIndex[style][name]?.component
|
||||
}
|
||||
|
||||
export async function getRegistryItem(
|
||||
name: string,
|
||||
style: Style["name"] = DEFAULT_REGISTRY_STYLE
|
||||
) {
|
||||
const item = memoizedIndex[style][name]
|
||||
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Convert all file paths to object.
|
||||
// TODO: remove when we migrate to new registry.
|
||||
item.files = item.files.map((file: unknown) =>
|
||||
typeof file === "string" ? { path: file } : file
|
||||
)
|
||||
|
||||
// Fail early before doing expensive file operations.
|
||||
const result = registryEntrySchema.safeParse(item)
|
||||
if (!result.success) {
|
||||
return null
|
||||
}
|
||||
|
||||
let files: typeof result.data.files = []
|
||||
for (const file of item.files) {
|
||||
const content = await getFileContent(file.path)
|
||||
const relativePath = path.relative(process.cwd(), file.path)
|
||||
|
||||
files.push({
|
||||
...file,
|
||||
path: relativePath,
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
// Get meta.
|
||||
// Assume the first file is the main file.
|
||||
const meta = await getFileMeta(files[0].path)
|
||||
|
||||
// Fix file paths.
|
||||
files = fixFilePaths(files)
|
||||
|
||||
const parsed = registryEntrySchema.safeParse({
|
||||
...result.data,
|
||||
files,
|
||||
meta,
|
||||
})
|
||||
|
||||
if (!parsed.success) {
|
||||
console.error(parsed.error.message)
|
||||
return null
|
||||
}
|
||||
|
||||
return parsed.data
|
||||
}
|
||||
|
||||
async function getFileContent(filePath: string) {
|
||||
const raw = await fs.readFile(filePath, "utf-8")
|
||||
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
|
||||
const tempFile = await createTempSourceFile(filePath)
|
||||
const sourceFile = project.createSourceFile(tempFile, raw, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
// Remove meta variables.
|
||||
removeVariable(sourceFile, "iframeHeight")
|
||||
removeVariable(sourceFile, "containerClassName")
|
||||
removeVariable(sourceFile, "description")
|
||||
|
||||
let code = sourceFile.getFullText()
|
||||
|
||||
// Format the code.
|
||||
code = code.replaceAll("export default", "export")
|
||||
|
||||
// Fix imports.
|
||||
code = fixImport(code)
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
async function getFileMeta(filePath: string) {
|
||||
const raw = await fs.readFile(filePath, "utf-8")
|
||||
|
||||
const project = new Project({
|
||||
compilerOptions: {},
|
||||
})
|
||||
|
||||
const tempFile = await createTempSourceFile(filePath)
|
||||
const sourceFile = project.createSourceFile(tempFile, raw, {
|
||||
scriptKind: ScriptKind.TSX,
|
||||
})
|
||||
|
||||
const iframeHeight = extractVariable(sourceFile, "iframeHeight")
|
||||
const containerClassName = extractVariable(sourceFile, "containerClassName")
|
||||
const description = extractVariable(sourceFile, "description")
|
||||
|
||||
return {
|
||||
iframeHeight,
|
||||
containerClassName,
|
||||
description,
|
||||
}
|
||||
}
|
||||
|
||||
function getFileTarget(file: z.infer<typeof registryItemFileSchema>) {
|
||||
let target = file.target
|
||||
|
||||
if (!target || target === "") {
|
||||
const fileName = file.path.split("/").pop()
|
||||
if (
|
||||
file.type === "registry:block" ||
|
||||
file.type === "registry:component" ||
|
||||
file.type === "registry:example"
|
||||
) {
|
||||
target = `components/${fileName}`
|
||||
}
|
||||
|
||||
if (file.type === "registry:ui") {
|
||||
target = `components/ui/${fileName}`
|
||||
}
|
||||
|
||||
if (file.type === "registry:hook") {
|
||||
target = `hooks/${fileName}`
|
||||
}
|
||||
|
||||
if (file.type === "registry:lib") {
|
||||
target = `lib/${fileName}`
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
async function createTempSourceFile(filename: string) {
|
||||
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
|
||||
return path.join(dir, filename)
|
||||
}
|
||||
|
||||
function removeVariable(sourceFile: SourceFile, name: string) {
|
||||
sourceFile.getVariableDeclaration(name)?.remove()
|
||||
}
|
||||
|
||||
function extractVariable(sourceFile: SourceFile, name: string) {
|
||||
const variable = sourceFile.getVariableDeclaration(name)
|
||||
if (!variable) {
|
||||
return null
|
||||
}
|
||||
|
||||
const value = variable
|
||||
.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral)
|
||||
.getLiteralValue()
|
||||
|
||||
variable.remove()
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
function fixFilePaths(files: z.infer<typeof registryEntrySchema>["files"]) {
|
||||
if (!files) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Resolve all paths relative to the first file's directory.
|
||||
const firstFilePath = files[0].path
|
||||
const firstFilePathDir = path.dirname(firstFilePath)
|
||||
|
||||
return files.map((file) => {
|
||||
return {
|
||||
...file,
|
||||
path: path.relative(firstFilePathDir, file.path),
|
||||
target: getFileTarget(file),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function fixImport(content: string) {
|
||||
const regex = /@\/(.+?)\/((?:.*?\/)?(?:components|ui|hooks|lib))\/([\w-]+)/g
|
||||
|
||||
const replacement = (
|
||||
match: string,
|
||||
path: string,
|
||||
type: string,
|
||||
component: string
|
||||
) => {
|
||||
if (type.endsWith("components")) {
|
||||
return `@/components/${component}`
|
||||
} else if (type.endsWith("ui")) {
|
||||
return `@/components/ui/${component}`
|
||||
} else if (type.endsWith("hooks")) {
|
||||
return `@/hooks/${component}`
|
||||
} else if (type.endsWith("lib")) {
|
||||
return `@/lib/${component}`
|
||||
}
|
||||
|
||||
return match
|
||||
}
|
||||
|
||||
return content.replace(regex, replacement)
|
||||
}
|
||||
|
||||
export type FileTree = {
|
||||
name: string
|
||||
path?: string
|
||||
children?: FileTree[]
|
||||
}
|
||||
|
||||
export function createFileTreeForRegistryItemFiles(
|
||||
files: Array<{ path: string; target?: string }>
|
||||
) {
|
||||
const root: FileTree[] = []
|
||||
|
||||
for (const file of files) {
|
||||
const path = file.target ?? file.path
|
||||
const parts = path.split("/")
|
||||
let currentLevel = root
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
const isFile = i === parts.length - 1
|
||||
const existingNode = currentLevel.find((node) => node.name === part)
|
||||
|
||||
if (existingNode) {
|
||||
if (isFile) {
|
||||
// Update existing file node with full path
|
||||
existingNode.path = path
|
||||
} else {
|
||||
// Move to next level in the tree
|
||||
currentLevel = existingNode.children!
|
||||
}
|
||||
} else {
|
||||
const newNode: FileTree = isFile
|
||||
? { name: part, path }
|
||||
: { name: part, children: [] }
|
||||
|
||||
currentLevel.push(newNode)
|
||||
|
||||
if (!isFile) {
|
||||
currentLevel = newNode.children!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
@@ -42,12 +42,12 @@ export function rehypeComponent() {
|
||||
file.endsWith(`${fileName}.tsx`) ||
|
||||
file.endsWith(`${fileName}.ts`)
|
||||
)
|
||||
}) || component.files[0]
|
||||
: component.files[0]
|
||||
}) || component.files[0]?.path
|
||||
: component.files[0]?.path
|
||||
}
|
||||
|
||||
// Read the source file.
|
||||
const filePath = path.join(process.cwd(), src)
|
||||
const filePath = src
|
||||
let source = fs.readFileSync(filePath, "utf8")
|
||||
|
||||
// Replace imports.
|
||||
@@ -106,10 +106,10 @@ export function rehypeComponent() {
|
||||
try {
|
||||
for (const style of styles) {
|
||||
const component = Index[style.name][name]
|
||||
const src = component.files[0]
|
||||
const src = component.files[0]?.path
|
||||
|
||||
// Read the source file.
|
||||
const filePath = path.join(process.cwd(), src)
|
||||
const filePath = src
|
||||
let source = fs.readFileSync(filePath, "utf8")
|
||||
|
||||
// Replace imports.
|
||||
|
||||
@@ -2,6 +2,11 @@ import { createContentlayerPlugin } from "next-contentlayer2"
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
outputFileTracingIncludes: {
|
||||
"/blocks/*": ["./registry/**/*"],
|
||||
},
|
||||
},
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
images: {
|
||||
@@ -58,6 +63,11 @@ const nextConfig = {
|
||||
destination: "/docs/components/sidebar",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/react-19",
|
||||
destination: "/docs/react-19",
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
@@ -67,12 +67,12 @@
|
||||
"geist": "^1.2.2",
|
||||
"input-otp": "^1.2.2",
|
||||
"jotai": "^2.1.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "0.359.0",
|
||||
"markdown-wasm": "^1.2.0",
|
||||
"next": "14.3.0-canary.43",
|
||||
"next-contentlayer2": "^0.4.6",
|
||||
"next-themes": "^0.2.1",
|
||||
"next-themes": "^0.4.3",
|
||||
"react": "^18.2.0",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -85,12 +85,12 @@
|
||||
"swr": "2.2.6-beta.3",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"ts-morph": "^22.0.0",
|
||||
"vaul": "0.9.0",
|
||||
"vaul": "1.1.1",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shikijs/compat": "^1.1.7",
|
||||
"@types/lodash.template": "^4.5.1",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/react": "^18.2.65",
|
||||
"@types/react-color": "^3.0.6",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user