snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 22:44:43 +05:00
commit 1416db152c
5727 changed files with 630238 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,336 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/bases/base/ui/accordion"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function AccordionExample() {
return (
<ExampleWrapper className="w-full max-w-4xl lg:grid-cols-1 2xl:max-w-4xl 2xl:grid-cols-1">
<AccordionBasic />
<AccordionMultiple />
<AccordionWithBorders />
<AccordionInCard />
<AccordionWithDisabled />
</ExampleWrapper>
)
}
function AccordionBasic() {
const items = [
{
value: "item-1",
trigger: "Is it accessible?",
content: "Yes. It adheres to the WAI-ARIA design pattern.",
},
{
value: "item-2",
trigger: "Is it styled?",
content:
"Yes. It comes with default styles that matches the other components' aesthetic.",
},
{
value: "item-3",
trigger: "Is it animated?",
content:
"Yes. It's animated by default, but you can disable it if you prefer.",
},
]
return (
<Example title="Basic">
<Accordion className="mx-auto max-w-lg">
{items.map((item) => (
<AccordionItem key={item.value} value={item.value}>
<AccordionTrigger>{item.trigger}</AccordionTrigger>
<AccordionContent>{item.content}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</Example>
)
}
function AccordionMultiple() {
const items = [
{
value: "item-1",
trigger:
"What are the key considerations when implementing a comprehensive enterprise-level authentication system?",
content:
"Implementing a robust enterprise authentication system requires careful consideration of multiple factors. This includes secure password hashing and storage, multi-factor authentication (MFA) implementation, session management, OAuth2 and SSO integration, regular security audits, rate limiting to prevent brute force attacks, and maintaining detailed audit logs. Additionally, you'll need to consider scalability, performance impact, and compliance with relevant data protection regulations such as GDPR or HIPAA.",
},
{
value: "item-2",
trigger:
"How does modern distributed system architecture handle eventual consistency and data synchronization across multiple regions?",
content:
"Modern distributed systems employ various strategies to maintain data consistency across regions. This often involves using techniques like CRDT (Conflict-Free Replicated Data Types), vector clocks, and gossip protocols. Systems might implement event sourcing patterns, utilize message queues for asynchronous updates, and employ sophisticated conflict resolution strategies. Popular solutions like Amazon's DynamoDB and Google's Spanner demonstrate different approaches to solving these challenges, balancing between consistency, availability, and partition tolerance as described in the CAP theorem.",
},
]
return (
<Example title="Multiple">
<Accordion multiple className="mx-auto max-w-lg">
{items.map((item) => (
<AccordionItem key={item.value} value={item.value}>
<AccordionTrigger>{item.trigger}</AccordionTrigger>
<AccordionContent>{item.content}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</Example>
)
}
function AccordionWithBorders() {
const items = [
{
value: "billing",
trigger: "How does billing work?",
content:
"We offer monthly and annual subscription plans. Billing is charged at the beginning of each cycle, and you can cancel anytime. All plans include automatic backups, 24/7 support, and unlimited team members. There are no hidden fees or setup costs.",
},
{
value: "security",
trigger: "Is my data secure?",
content:
"Yes. We use end-to-end encryption, SOC 2 Type II compliance, and regular third-party security audits. All data is encrypted at rest and in transit using industry-standard protocols. We also offer optional two-factor authentication and single sign-on for enterprise customers.",
},
{
value: "integration",
trigger: "What integrations do you support?",
content: (
<>
<p>
We integrate with 500+ popular tools including Slack, Zapier,
Salesforce, HubSpot, and more. You can also build custom
integrations using our REST API and webhooks.{" "}
</p>
<p>
Our API documentation includes code examples in 10+ programming
languages.
</p>
</>
),
},
]
return (
<Example title="With Borders">
<Accordion className="mx-auto max-w-lg style-vega:gap-2 style-nova:gap-2 style-lyra:gap-2">
{items.map((item) => (
<AccordionItem
key={item.value}
value={item.value}
className="style-vega:rounded-lg style-vega:border style-nova:rounded-lg style-nova:border style-lyra:border"
>
<AccordionTrigger className="font-medium style-vega:px-4 style-vega:text-sm style-nova:px-2.5 style-nova:text-sm style-lyra:px-2 style-lyra:text-xs style-maia:text-sm style-mira:text-xs style-luma:text-sm">
{item.trigger}
</AccordionTrigger>
<AccordionContent className="text-muted-foreground style-vega:px-4 style-nova:px-2.5 style-nova:text-sm style-lyra:px-2 style-lyra:text-xs style-maia:px-0 style-mira:px-0 style-luma:px-0">
{item.content}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</Example>
)
}
function AccordionInCard() {
const items = [
{
value: "plans",
trigger: "What subscription plans do you offer?",
content: (
<>
<p>
We offer three subscription tiers: Starter ($9/month), Professional
($29/month), and Enterprise ($99/month). Each plan includes
increasing storage limits, API access, priority support, and team
collaboration features.
</p>
<p>
<a href="#">Annual billing is available</a> with a 20% discount. All
plans include a 14-day free trial with no credit card required.
</p>
<Button size="sm">
View plans
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</Button>
</>
),
},
{
value: "billing",
trigger: "How does billing work?",
content: (
<>
<p>
Billing occurs automatically at the start of each billing cycle. We
accept all major credit cards, PayPal, and ACH transfers for
enterprise customers.
</p>
<p>
You&apos;ll receive an invoice via email after each payment. You can
update your payment method or billing information anytime in your
account settings. Failed payments will trigger automated retry
attempts and email notifications.
</p>
</>
),
},
{
value: "upgrade",
trigger: "Can I upgrade or downgrade my plan?",
content: (
<>
<p>
Yes, you can change your plan at any time. When upgrading,
you&apos;ll be charged a prorated amount for the remainder of your
billing cycle and immediately gain access to new features.
</p>
<p>
When downgrading, the change takes effect at the end of your current
billing period, and you&apos;ll retain access to premium features
until then. No refunds are provided for downgrades.
</p>
</>
),
},
{
value: "cancel",
trigger: "How do I cancel my subscription?",
content: (
<>
<p>
You can cancel your subscription anytime from your account settings.
There are no cancellation fees or penalties. Your access will
continue until the end of your current billing period.
</p>
<p>
After cancellation, your data is retained for 30 days in case you
want to reactivate. You can export all your data before or after
canceling. We&apos;d love to hear your feedback about why
you&apos;re leaving.
</p>
</>
),
},
{
value: "refund",
trigger: "What is your refund policy?",
content: (
<>
<p>
We offer a 30-day money-back guarantee for new subscriptions. If
you&apos;re not satisfied within the first 30 days, contact our
support team for a full refund.
</p>
<p>
After 30 days, we don&apos;t provide refunds for partial billing
periods, but you can cancel anytime to avoid future charges.
Enterprise customers have custom refund terms outlined in their
contracts.
</p>
</>
),
},
]
return (
<Example title="In Card">
<Card className="mx-auto w-full max-w-lg gap-4">
<CardHeader>
<CardTitle>Subscription & Billing</CardTitle>
<CardDescription>
Common questions about your account, plans, and payments
</CardDescription>
</CardHeader>
<CardContent>
<Accordion
multiple
defaultValue={["plans"]}
className="style-maia:rounded-md style-mira:rounded-md"
>
{items.map((item) => (
<AccordionItem key={item.value} value={item.value}>
<AccordionTrigger>{item.trigger}</AccordionTrigger>
<AccordionContent>{item.content}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</CardContent>
</Card>
</Example>
)
}
function AccordionWithDisabled() {
const items = [
{
value: "item-1",
trigger: "Can I access my account history?",
content:
"Yes, you can view your complete account history including all transactions, plan changes, and support tickets in the Account History section of your dashboard.",
disabled: false,
},
{
value: "item-2",
trigger: "Premium feature information",
content:
"This section contains information about premium features. Upgrade your plan to access this content.",
disabled: true,
},
{
value: "item-3",
trigger: "How do I update my email address?",
content:
"You can update your email address in your account settings. You'll receive a verification email at your new address to confirm the change.",
disabled: false,
},
]
return (
<Example title="With Disabled">
<Accordion className="mx-auto max-w-lg overflow-hidden border style-vega:rounded-lg style-nova:rounded-lg style-lyra:rounded-none style-maia:rounded-lg style-mira:rounded-lg style-luma:rounded-xl">
{items.map((item) => (
<AccordionItem
key={item.value}
value={item.value}
disabled={item.disabled}
className="p-1 data-open:bg-muted/50"
>
<AccordionTrigger className="style-vega:px-4 style-nova:px-2.5 style-lyra:px-2">
{item.trigger}
</AccordionTrigger>
<AccordionContent className="style-vega:px-4 style-nova:px-2.5 style-lyra:px-2">
{item.content}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</Example>
)
}

View File

@@ -0,0 +1,230 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/base/ui/alert-dialog"
import { Button } from "@/registry/bases/base/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function AlertDialogExample() {
return (
<ExampleWrapper>
<AlertDialogBasic />
<AlertDialogSmall />
<AlertDialogWithMedia />
<AlertDialogSmallWithMedia />
<AlertDialogDestructive />
<AlertDialogInDialog />
</ExampleWrapper>
)
}
function AlertDialogBasic() {
return (
<Example title="Basic" className="items-center">
<AlertDialog>
<AlertDialogTrigger
render={<Button variant="outline">Default</Button>}
/>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Example>
)
}
function AlertDialogSmall() {
return (
<Example title="Small" className="items-center">
<AlertDialog>
<AlertDialogTrigger render={<Button variant="outline">Small</Button>} />
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to this device?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Example>
)
}
function AlertDialogWithMedia() {
return (
<Example title="With Media" className="items-center">
<AlertDialog>
<AlertDialogTrigger
render={<Button variant="outline">Default (Media)</Button>}
/>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogMedia>
<IconPlaceholder
lucide="BluetoothIcon"
tabler="IconBluetooth"
hugeicons="BluetoothIcon"
phosphor="BluetoothIcon"
remixicon="RiBluetoothLine"
/>
</AlertDialogMedia>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete your account and remove your data
from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Example>
)
}
function AlertDialogSmallWithMedia() {
return (
<Example title="Small With Media" className="items-center">
<AlertDialog>
<AlertDialogTrigger
render={<Button variant="outline">Small (Media)</Button>}
/>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia>
<IconPlaceholder
lucide="BluetoothIcon"
tabler="IconBluetooth"
hugeicons="BluetoothIcon"
phosphor="BluetoothIcon"
remixicon="RiBluetoothLine"
/>
</AlertDialogMedia>
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to this device?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Example>
)
}
function AlertDialogDestructive() {
return (
<Example title="Destructive" className="items-center">
<AlertDialog>
<AlertDialogTrigger
render={<Button variant="destructive">Delete Chat</Button>}
/>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
<IconPlaceholder
lucide="Trash2Icon"
tabler="IconTrash"
hugeicons="Delete02Icon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
</AlertDialogMedia>
<AlertDialogTitle>Delete chat?</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete this chat conversation. View{" "}
<a href="#">Settings</a> delete any memories saved during this
chat.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="ghost">Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Example>
)
}
function AlertDialogInDialog() {
return (
<Example title="In Dialog" className="items-center">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Alert Dialog Example</DialogTitle>
<DialogDescription>
Click the button below to open an alert dialog.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<AlertDialog>
<AlertDialogTrigger render={<Button />}>
Open Alert Dialog
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete
your account and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DialogFooter>
</DialogContent>
</Dialog>
</Example>
)
}

View File

@@ -0,0 +1,227 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Alert,
AlertAction,
AlertDescription,
AlertTitle,
} from "@/registry/bases/base/ui/alert"
import { Badge } from "@/registry/bases/base/ui/badge"
import { Button } from "@/registry/bases/base/ui/button"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function AlertExample() {
return (
<ExampleWrapper className="lg:grid-cols-1">
<AlertExample1 />
<AlertExample2 />
<AlertExample3 />
<AlertExample4 />
</ExampleWrapper>
)
}
function AlertExample1() {
return (
<Example title="Basic">
<div className="mx-auto flex w-full max-w-lg flex-col gap-4">
<Alert>
<AlertTitle>Success! Your changes have been saved.</AlertTitle>
</Alert>
<Alert>
<AlertTitle>Success! Your changes have been saved.</AlertTitle>
<AlertDescription>
This is an alert with title and description.
</AlertDescription>
</Alert>
<Alert>
<AlertDescription>
This one has a description only. No title. No icon.
</AlertDescription>
</Alert>
</div>
</Example>
)
}
function AlertExample2() {
return (
<Example title="With Icons">
<div className="mx-auto flex w-full max-w-lg flex-col gap-4">
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>
Let&apos;s try one with icon, title and a <a href="#">link</a>.
</AlertTitle>
</Alert>
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertDescription>
This one has an icon and a description only. No title.{" "}
<a href="#">But it has a link</a> and a <a href="#">second link</a>.
</AlertDescription>
</Alert>
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>Success! Your changes have been saved</AlertTitle>
<AlertDescription>
This is an alert with icon, title and description.
</AlertDescription>
</Alert>
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>
This is a very long alert title that demonstrates how the component
handles extended text content and potentially wraps across multiple
lines
</AlertTitle>
</Alert>
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertDescription>
This is a very long alert description that demonstrates how the
component handles extended text content and potentially wraps across
multiple lines
</AlertDescription>
</Alert>
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>
This is an extremely long alert title that spans multiple lines to
demonstrate how the component handles very lengthy headings while
maintaining readability and proper text wrapping behavior
</AlertTitle>
<AlertDescription>
This is an equally long description that contains detailed
information about the alert. It shows how the component can
accommodate extensive content while preserving proper spacing,
alignment, and readability across different screen sizes and
viewport widths. This helps ensure the user experience remains
consistent regardless of the content length.
</AlertDescription>
</Alert>
</div>
</Example>
)
}
function AlertExample3() {
return (
<Example title="Destructive">
<div className="mx-auto flex w-full max-w-lg flex-col gap-4">
<Alert variant="destructive">
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>Something went wrong!</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
<Alert variant="destructive">
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>Unable to process your payment.</AlertTitle>
<AlertDescription>
<p>
Please verify your <a href="#">billing information</a> and try
again.
</p>
<ul className="list-inside list-disc">
<li>Check your card details</li>
<li>Ensure sufficient funds</li>
<li>Verify billing address</li>
</ul>
</AlertDescription>
</Alert>
</div>
</Example>
)
}
function AlertExample4() {
return (
<Example title="With Actions">
<div className="mx-auto flex w-full max-w-lg flex-col gap-4">
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>The selected emails have been marked as spam.</AlertTitle>
<AlertAction>
<Button size="xs">Undo</Button>
</AlertAction>
</Alert>
<Alert>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
<AlertTitle>The selected emails have been marked as spam.</AlertTitle>
<AlertDescription>
This is a very long alert title that demonstrates how the component
handles extended text content.
</AlertDescription>
<AlertAction>
<Badge variant="secondary">Badge</Badge>
</AlertAction>
</Alert>
</div>
</Example>
)
}

View File

@@ -0,0 +1,90 @@
import Image from "next/image"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { AspectRatio } from "@/registry/bases/base/ui/aspect-ratio"
export default function AspectRatioExample() {
return (
<ExampleWrapper className="max-w-4xl 2xl:max-w-4xl">
<AspectRatio16x9 />
<AspectRatio21x9 />
<AspectRatio1x1 />
<AspectRatio9x16 />
</ExampleWrapper>
)
}
function AspectRatio16x9() {
return (
<Example title="16:9" className="items-center justify-center">
<AspectRatio
ratio={16 / 9}
className="rounded-lg bg-muted style-luma:rounded-3xl"
>
<Image
src="https://avatar.vercel.sh/shadcn1"
alt="Photo"
fill
className="h-full w-full rounded-lg object-cover grayscale dark:brightness-20 style-luma:rounded-3xl"
/>
</AspectRatio>
</Example>
)
}
function AspectRatio1x1() {
return (
<Example title="1:1" className="items-start">
<AspectRatio
ratio={1 / 1}
className="rounded-lg bg-muted style-luma:rounded-3xl"
>
<Image
src="https://avatar.vercel.sh/shadcn1"
alt="Photo"
fill
className="h-full w-full rounded-lg object-cover grayscale dark:brightness-20 style-luma:rounded-3xl"
/>
</AspectRatio>
</Example>
)
}
function AspectRatio9x16() {
return (
<Example title="9:16" className="items-center justify-center">
<AspectRatio
ratio={9 / 16}
className="rounded-lg bg-muted style-luma:rounded-3xl"
>
<Image
src="https://avatar.vercel.sh/shadcn1"
alt="Photo"
fill
className="h-full w-full rounded-lg object-cover grayscale dark:brightness-20 style-luma:rounded-3xl"
/>
</AspectRatio>
</Example>
)
}
function AspectRatio21x9() {
return (
<Example title="21:9" className="items-center justify-center">
<AspectRatio
ratio={21 / 9}
className="rounded-lg bg-muted style-luma:rounded-3xl"
>
<Image
src="https://avatar.vercel.sh/shadcn1"
alt="Photo"
fill
className="h-full w-full rounded-lg object-cover grayscale dark:brightness-20 style-luma:rounded-3xl"
/>
</AspectRatio>
</Example>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,511 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/registry/bases/base/ui/avatar"
import { Button } from "@/registry/bases/base/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/base/ui/empty"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function AvatarExample() {
return (
<ExampleWrapper>
<AvatarSizes />
<AvatarWithBadge />
<AvatarWithBadgeIcon />
<AvatarGroupExample />
<AvatarGroupWithCount />
<AvatarGroupWithIconCount />
<AvatarInEmpty />
</ExampleWrapper>
)
}
function AvatarSizes() {
return (
<Example title="Sizes">
<div className="flex flex-wrap items-center gap-2">
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
<div className="flex flex-wrap items-center gap-2">
<Avatar size="sm">
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
</Example>
)
}
function AvatarWithBadge() {
return (
<Example title="Badge">
<div className="flex flex-wrap items-center gap-2">
<Avatar size="sm">
<AvatarImage
src="https://github.com/jorgezreik.png"
alt="@jorgezreik"
/>
<AvatarFallback>JZ</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/jorgezreik.png"
alt="@jorgezreik"
/>
<AvatarFallback>JZ</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/jorgezreik.png"
alt="@jorgezreik"
/>
<AvatarFallback>JZ</AvatarFallback>
<AvatarBadge />
</Avatar>
</div>
<div className="flex flex-wrap items-center gap-2">
<Avatar size="sm">
<AvatarFallback>JZ</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar>
<AvatarFallback>JZ</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar size="lg">
<AvatarFallback>JZ</AvatarFallback>
<AvatarBadge />
</Avatar>
</div>
</Example>
)
}
function AvatarWithBadgeIcon() {
return (
<Example title="Badge with Icon">
<div className="flex flex-wrap items-center gap-2">
<Avatar size="sm">
<AvatarImage
src="https://github.com/pranathip.png"
alt="@pranathip"
/>
<AvatarFallback>PP</AvatarFallback>
<AvatarBadge>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarBadge>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/pranathip.png"
alt="@pranathip"
/>
<AvatarFallback>PP</AvatarFallback>
<AvatarBadge>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarBadge>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/pranathip.png"
alt="@pranathip"
/>
<AvatarFallback>PP</AvatarFallback>
<AvatarBadge>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarBadge>
</Avatar>
</div>
<div className="flex flex-wrap items-center gap-2">
<Avatar size="sm">
<AvatarFallback>PP</AvatarFallback>
<AvatarBadge>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
</AvatarBadge>
</Avatar>
<Avatar>
<AvatarFallback>PP</AvatarFallback>
<AvatarBadge>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
</AvatarBadge>
</Avatar>
<Avatar size="lg">
<AvatarFallback>PP</AvatarFallback>
<AvatarBadge>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
</AvatarBadge>
</Avatar>
</div>
</Example>
)
}
function AvatarGroupExample() {
return (
<Example title="Group">
<AvatarGroup>
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</AvatarGroup>
</Example>
)
}
function AvatarGroupWithCount() {
return (
<Example title="Group with Count">
<AvatarGroup>
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
</Example>
)
}
function AvatarGroupWithIconCount() {
return (
<Example title="Group with Icon Count">
<AvatarGroup>
<Avatar size="sm">
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="sm">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarGroupCount>
</AvatarGroup>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
className="grayscale"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
className="grayscale"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
className="grayscale"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarGroupCount>
</AvatarGroup>
</Example>
)
}
function AvatarInEmpty() {
return (
<Example title="In Empty">
<Empty className="w-full flex-none border">
<EmptyHeader>
<EmptyMedia>
<AvatarGroup>
<Avatar size="lg">
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
className="grayscale"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
className="grayscale"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
className="grayscale"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</AvatarGroupCount>
</AvatarGroup>
</EmptyMedia>
<EmptyTitle>No Team Members</EmptyTitle>
<EmptyDescription>
Invite your team to collaborate on this project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
Invite Members
</Button>
</EmptyContent>
</Empty>
</Example>
)
}

View File

@@ -0,0 +1,339 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Badge } from "@/registry/bases/base/ui/badge"
import { Spinner } from "@/registry/bases/base/ui/spinner"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function BadgeExample() {
return (
<ExampleWrapper className="lg:grid-cols-1">
<BadgeVariants />
<BadgeWithIconLeft />
<BadgeWithIconRight />
<BadgeWithSpinner />
<BadgeAsLink />
<BadgeLongText />
<BadgeCustomColors />
</ExampleWrapper>
)
}
function BadgeVariants() {
return (
<Example title="Variants">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="ghost">Ghost</Badge>
<Badge variant="link">Link</Badge>
</div>
</Example>
)
}
function BadgeWithIconLeft() {
return (
<Example title="Icon Left" className="max-w-fit">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge>
<IconPlaceholder
lucide="BadgeCheck"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadge02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
data-icon="inline-start"
/>
Default
</Badge>
<Badge variant="secondary">
<IconPlaceholder
lucide="BadgeCheck"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadge02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
data-icon="inline-start"
/>
Secondary
</Badge>
<Badge variant="destructive">
<IconPlaceholder
lucide="BadgeCheck"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadge02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
data-icon="inline-start"
/>
Destructive
</Badge>
<Badge variant="outline">
<IconPlaceholder
lucide="BadgeCheck"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadge02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
data-icon="inline-start"
/>
Outline
</Badge>
<Badge variant="ghost">
<IconPlaceholder
lucide="BadgeCheck"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadge02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
data-icon="inline-start"
/>
Ghost
</Badge>
<Badge variant="link">
<IconPlaceholder
lucide="BadgeCheck"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadge02Icon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
data-icon="inline-start"
/>
Link
</Badge>
</div>
</Example>
)
}
function BadgeWithIconRight() {
return (
<Example title="Icon Right" className="max-w-fit">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge>
Default
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Badge>
<Badge variant="secondary">
Secondary
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Badge>
<Badge variant="destructive">
Destructive
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Badge>
<Badge variant="outline">
Outline
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Badge>
<Badge variant="ghost">
Ghost
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Badge>
<Badge variant="link">
Link
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Badge>
</div>
</Example>
)
}
function BadgeWithSpinner() {
return (
<Example title="With Spinner" className="max-w-fit">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge>
<Spinner data-icon="inline-start" />
Default
</Badge>
<Badge variant="secondary">
<Spinner data-icon="inline-start" />
Secondary
</Badge>
<Badge variant="destructive">
<Spinner data-icon="inline-start" />
Destructive
</Badge>
<Badge variant="outline">
<Spinner data-icon="inline-start" />
Outline
</Badge>
<Badge variant="ghost">
<Spinner data-icon="inline-start" />
Ghost
</Badge>
<Badge variant="link">
<Spinner data-icon="inline-start" />
Link
</Badge>
</div>
</Example>
)
}
function BadgeAsLink() {
return (
<Example title="asChild">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge
render={
<a href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRightIcon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
}
/>
<Badge
variant="secondary"
render={
<a href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRightIcon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
}
/>
<Badge
variant="destructive"
render={
<a href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRightIcon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
}
/>
<Badge
variant="ghost"
render={
<a href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
}
/>
</div>
</Example>
)
}
function BadgeLongText() {
return (
<Example title="Long Text">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge variant="secondary">
A badge with a lot of text to see how it wraps
</Badge>
</div>
</Example>
)
}
function BadgeCustomColors() {
return (
<Example title="Custom Colors" className="max-w-fit">
<div className="flex flex-wrap gap-2 style-sera:gap-6">
<Badge className="bg-blue-600 text-blue-50 dark:bg-blue-600 dark:text-blue-50">
Blue
</Badge>
<Badge className="bg-green-600 text-green-50 dark:bg-green-600 dark:text-green-50">
Green
</Badge>
<Badge className="bg-sky-600 text-sky-50 dark:bg-sky-600 dark:text-sky-50">
Sky
</Badge>
<Badge className="bg-purple-600 text-purple-50 dark:bg-purple-600 dark:text-purple-50">
Purple
</Badge>
<Badge className="bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
Blue
</Badge>
<Badge className="bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
Green
</Badge>
<Badge className="bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300">
Sky
</Badge>
<Badge className="bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300">
Purple
</Badge>
<Badge className="bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300">
Red
</Badge>
</div>
</Example>
)
}

View File

@@ -0,0 +1,118 @@
import Link from "next/link"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/registry/bases/base/ui/breadcrumb"
import { Button } from "@/registry/bases/base/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
export default function BreadcrumbExample() {
return (
<ExampleWrapper>
<BreadcrumbBasic />
<BreadcrumbWithDropdown />
<BreadcrumbWithLink />
</ExampleWrapper>
)
}
function BreadcrumbBasic() {
return (
<Example title="Basic" className="items-center justify-center">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="#">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</Example>
)
}
function BreadcrumbWithDropdown() {
return (
<Example title="With Dropdown" className="items-center justify-center">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button size="icon-sm" variant="ghost" />}
>
<BreadcrumbEllipsis />
<span className="sr-only">Toggle menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Themes</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="#">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</Example>
)
}
function BreadcrumbWithLink() {
return (
<Example title="With Link" className="items-center justify-center">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="#">Home</Link>} />
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbEllipsis />
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink render={<Link href="#">Components</Link>} />
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</Example>
)
}

View File

@@ -0,0 +1,520 @@
"use client"
import * as React from "react"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/registry/bases/base/ui/bubble"
import { Button } from "@/registry/bases/base/ui/button"
import {
Collapsible,
CollapsibleTrigger,
} from "@/registry/bases/base/ui/collapsible"
import { Marker, MarkerContent } from "@/registry/bases/base/ui/marker"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function BubbleExample() {
return (
<ExampleWrapper>
<BubbleSizes />
<BubbleVariants />
<BubbleAlignment />
<BubbleGrouped />
<BubbleCollapsible />
<BubbleButtonLinks />
<BubbleWithReactions />
<BubbleReactionsButtons />
</ExampleWrapper>
)
}
function BubbleVariants() {
return (
<Example title="Variants">
<div className="flex w-full max-w-md flex-col gap-8">
<Bubble>
<BubbleContent>
Default bubbles use the primary color for the active user side of a
chat.
</BubbleContent>
</Bubble>
<Bubble variant="secondary">
<BubbleContent>
Secondary bubbles are the standard neutral surface for assistant and
conversation content.
</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
Muted bubbles lower the emphasis for quiet system notes or for
displaying supporting content.
</BubbleContent>
</Bubble>
<Bubble variant="tinted" align="end">
<BubbleContent>
Tinted bubbles use a softer primary tint when primary fill is too
strong.
</BubbleContent>
</Bubble>
<Bubble variant="outline">
<BubbleContent>
Outline bubbles can be used to frame message content and give it a
border.
</BubbleContent>
</Bubble>
<Bubble variant="destructive">
<BubbleContent>
Destructive bubbles flag errors or failed actions in a conversation.
</BubbleContent>
</Bubble>
<Bubble variant="ghost">
<BubbleContent>
<span className="whitespace-pre-wrap">
{`Ghost bubbles work for assistant text and other content that should not be framed.
This is perfect for assistant messages that should not have a frame and can take the full width of the container.
Ghost bubbles are full width and can take the full width of the container.
`}
</span>
</BubbleContent>
</Bubble>
</div>
</Example>
)
}
function BubbleSizes() {
return (
<Example title="Sizes">
<div className="flex w-full max-w-md flex-col gap-8">
<Bubble>
<BubbleContent>This is a one line bubble.</BubbleContent>
</Bubble>
<Bubble>
<BubbleContent>
This bubble has multiple lines. It should wrap to the next line and
you should see a different radius on the corners.
</BubbleContent>
</Bubble>
<Bubble>
<BubbleContent>
<p>This bubble has multiple lines.</p>
<p>
It should wrap to the next line and you should see a different
radius on the corners.
</p>
<p>Here is some more text to see how it wraps.</p>
</BubbleContent>
</Bubble>
</div>
</Example>
)
}
function BubbleGrouped() {
return (
<Example title="Grouped">
<div className="flex w-full max-w-md flex-col gap-8">
<BubbleGroup>
<Bubble variant="secondary">
<BubbleContent>I finished the audit pass.</BubbleContent>
</Bubble>
<Bubble variant="secondary">
<BubbleContent>
The registry output looks clean, but I found one stale route.
</BubbleContent>
</Bubble>
<Bubble variant="secondary">
<BubbleContent>Want me to remove it now?</BubbleContent>
</Bubble>
</BubbleGroup>
<BubbleGroup>
<Bubble variant="tinted" align="end">
<BubbleContent>Yes, clean that up.</BubbleContent>
</Bubble>
<Bubble variant="tinted" align="end">
<BubbleContent>Then rerun the registry build.</BubbleContent>
</Bubble>
</BubbleGroup>
</div>
</Example>
)
}
const text = `The accessibility review found two focus states that were visually too subtle in dark mode.
I checked the dialog, menu, and drawer paths because each one renders focusable controls inside a layered surface.
The dialog and drawer are fine. The menu needs the hover and focus tokens split so keyboard focus stays visible when the pointer is not involved.
I also recommend keeping the change in the style file instead of the primitive so the other themes can choose their own focus treatment later.`
const previewLength = 180
function BubbleCollapsible() {
const [open, setOpen] = React.useState(false)
const isLong = text.length > previewLength
const preview = `${text.slice(0, previewLength)}...`
return (
<Example title="Collapsible">
<div className="flex w-full max-w-md flex-col gap-8">
<Collapsible open={open} onOpenChange={setOpen}>
<Bubble variant="muted" align="end">
<BubbleContent className="whitespace-pre-line">
<div>{open || !isLong ? text : preview}</div>
{isLong ? (
<CollapsibleTrigger
render={
<Button
variant="link"
className="gap-1 p-0 text-muted-foreground"
/>
}
>
{open ? "Show less" : "Show more"}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
data-icon="inline-end"
className="group-data-panel-open/button:rotate-180"
/>
</CollapsibleTrigger>
) : null}
</BubbleContent>
</Bubble>
</Collapsible>
<Bubble variant="ghost">
<BubbleContent>
<span className="whitespace-pre-wrap">
{`Ghost bubbles work for assistant text and other content that should not be framed.
This is perfect for assistant messages that should not have a frame and can take the full width of the container.
Use this for content that needs the whole row.`}
</span>
</BubbleContent>
</Bubble>
</div>
</Example>
)
}
function BubbleWithReactions() {
return (
<Example title="Reaction Placement">
<div className="flex w-full max-w-md flex-col gap-12">
<Marker variant="separator">
<MarkerContent>side=bottom align=end</MarkerContent>
</Marker>
<Bubble>
<BubbleContent>This is a one line message.</BubbleContent>
<BubbleReactions
side="bottom"
align="end"
role="img"
aria-label="Reaction: thumbs up"
>
<span>👍</span>
</BubbleReactions>
</Bubble>
<Bubble variant="secondary" align="end">
<BubbleContent>
A longer message that wraps across lines so the reaction offset is
easier to inspect.
</BubbleContent>
<BubbleReactions
side="bottom"
align="start"
role="img"
aria-label="Reactions: thumbs up, surprised"
>
<span>👍</span>
<span>😮</span>
</BubbleReactions>
</Bubble>
<Bubble variant="tinted">
<BubbleContent>
A longer message that wraps across lines so the reaction offset is
easier to inspect.
</BubbleContent>
<BubbleReactions
side="bottom"
align="end"
role="img"
aria-label="Reactions: thumbs up, surprised, fire, eyes, and 8 more"
>
<span>👍</span>
<span>😮</span>
<span>🔥</span>
<span>👀</span>
<span>+8</span>
</BubbleReactions>
</Bubble>
<Marker variant="separator">
<MarkerContent>side=bottom align=start</MarkerContent>
</Marker>
<Bubble variant="secondary">
<BubbleContent>This is a one line message.</BubbleContent>
<BubbleReactions
side="bottom"
align="start"
role="img"
aria-label="Reaction: fire"
>
<span>🔥</span>
</BubbleReactions>
</Bubble>
<Bubble variant="secondary">
<BubbleContent>
A longer message that wraps across lines so the reaction offset is
easier to inspect.
</BubbleContent>
<BubbleReactions
side="bottom"
align="start"
role="img"
aria-label="Reactions: thumbs up, surprised, fire, eyes"
>
<span>👍</span>
<span>😮</span>
<span>🔥</span>
<span>👀</span>
</BubbleReactions>
</Bubble>
<Marker variant="separator">
<MarkerContent>side=top align=start</MarkerContent>
</Marker>
<Bubble variant="secondary">
<BubbleContent>This is a one line message.</BubbleContent>
<BubbleReactions
side="top"
align="start"
role="img"
aria-label="Reaction: fire"
>
<span>🔥</span>
</BubbleReactions>
</Bubble>
<Bubble variant="secondary">
<BubbleContent>
A longer message that wraps across lines so the reaction offset is
easier to inspect.
</BubbleContent>
<BubbleReactions
side="top"
align="start"
role="img"
aria-label="Reactions: thumbs up, surprised, fire, eyes"
>
<span>👍</span>
<span>😮</span>
<span>🔥</span>
<span>👀</span>
</BubbleReactions>
</Bubble>
<Marker variant="separator">
<MarkerContent>side=bottom align=end</MarkerContent>
</Marker>
<Bubble variant="muted">
<BubbleContent>This is a one line message.</BubbleContent>
<BubbleReactions
side="top"
align="end"
role="img"
aria-label="Reaction: thumbs up"
>
<span>👍</span>
</BubbleReactions>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
A longer message that wraps across lines so the reaction offset.
</BubbleContent>
<BubbleReactions
side="top"
align="end"
role="img"
aria-label="Reactions: thumbs up, surprised, fire, eyes"
className="px-1.5 py-0.5"
>
<span>👍</span>
<span>😮</span>
<span>🔥</span>
<span>👀</span>
</BubbleReactions>
</Bubble>
</div>
</Example>
)
}
function BubbleReactionsButtons() {
return (
<Example title="Reactions Buttons">
<div className="flex w-full max-w-md flex-col gap-8">
<Bubble>
<BubbleContent>This is a one line message.</BubbleContent>
<BubbleReactions>
<Button
variant="outline"
size="xs"
onClick={() =>
toast("You clicked the button in the bubble reaction")
}
>
Button
</Button>
</BubbleReactions>
</Bubble>
<Bubble align="end">
<BubbleContent>This is a one line message.</BubbleContent>
<BubbleReactions align="start">
<Button
variant="ghost"
size="icon-xs"
onClick={() => toast("Confetti!")}
>
🎉
</Button>
</BubbleReactions>
</Bubble>
<Bubble variant="tinted">
<BubbleContent>
We are going to the movies first then dinner. Are you in?
</BubbleContent>
<BubbleReactions className="gap-1 bg-background">
<Button
variant="secondary"
size="icon-xs"
aria-label="Thumbs up"
onClick={() => toast("You agree!")}
>
<IconPlaceholder
lucide="ThumbsUpIcon"
tabler="IconThumbUp"
hugeicons="ThumbsUpIcon"
phosphor="ThumbsUpIcon"
remixicon="RiThumbUpLine"
/>
</Button>
<Button
variant="secondary"
size="icon-xs"
aria-label="Thumbs down"
onClick={() => toast("You disagree!")}
>
<IconPlaceholder
lucide="ThumbsDownIcon"
tabler="IconThumbDown"
hugeicons="ThumbsDownIcon"
phosphor="ThumbsDownIcon"
remixicon="RiThumbDownLine"
/>
</Button>
</BubbleReactions>
</Bubble>
</div>
</Example>
)
}
function BubbleAlignment() {
return (
<Example title="Alignment">
<div className="flex w-full max-w-md flex-col gap-8">
<Bubble variant="muted">
<BubbleContent>This bubble is aligned to the start.</BubbleContent>
</Bubble>
<Bubble align="end">
<BubbleContent>This bubble is aligned to the end.</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>
This multiline bubble is aligned to the start. The corners should
adjust when the text wraps to show the grouped side of the
conversation.
</BubbleContent>
</Bubble>
<Bubble align="end">
<BubbleContent>
This multiline bubble is aligned to the end. It should sit on the
opposite side with the matching corner radius for wrapped text.
</BubbleContent>
</Bubble>
</div>
</Example>
)
}
const quickReplies = [
{
label: "I need help with my account.",
message: "I need help with my account.",
},
{
label: "I forgot my password.",
message: "I forgot my password.",
},
{
label:
"I have another question. I'd like to talk to a human. Can you help me?",
message: "I have another question.",
},
]
function BubbleButtonLinks() {
return (
<Example title="Button & Links">
<div className="flex w-full max-w-md flex-col gap-8">
<Bubble>
<BubbleContent render={<a href="#" />}>
This bubble is a link.
</BubbleContent>
</Bubble>
<Bubble variant="secondary">
<BubbleContent render={<button type="button" />}>
This one is a button you can click.
</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent render={<button type="button" />}>
You can also do tinted buttons. Even ones that are multilines.
</BubbleContent>
</Bubble>
<Marker variant="separator">
<MarkerContent>Chat Suggestions</MarkerContent>
</Marker>
<Bubble>
<BubbleContent>How can I help you today?</BubbleContent>
</Bubble>
<BubbleGroup>
{quickReplies.map((reply) => (
<Bubble key={reply.label} variant="outline" align="end">
<BubbleContent
className="border-dashed border-primary"
render={
<button type="button" onClick={() => toast(reply.message)} />
}
>
{reply.label}
</BubbleContent>
</Bubble>
))}
</BubbleGroup>
</div>
</Example>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,753 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
ButtonGroup,
ButtonGroupText,
} from "@/registry/bases/base/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/base/ui/input-group"
import { Label } from "@/registry/bases/base/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/base/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function ButtonGroupExample() {
return (
<ExampleWrapper>
<ButtonGroupBasic />
<ButtonGroupWithInput />
<ButtonGroupWithText />
<ButtonGroupWithDropdown />
<ButtonGroupWithSelect />
<ButtonGroupWithIcons />
<ButtonGroupWithInputGroup />
<ButtonGroupWithFields />
<ButtonGroupWithLike />
<ButtonGroupWithSelectAndInput />
<ButtonGroupNested />
<ButtonGroupPagination />
<ButtonGroupPaginationSplit />
<ButtonGroupNavigation />
<ButtonGroupTextAlignment />
<ButtonGroupVertical />
<ButtonGroupVerticalNested />
</ExampleWrapper>
)
}
function ButtonGroupBasic() {
return (
<Example title="Basic">
<div className="flex flex-col gap-4">
<ButtonGroup>
<Button variant="outline">Button</Button>
<Button variant="outline">Another Button</Button>
</ButtonGroup>
</div>
</Example>
)
}
function ButtonGroupWithInput() {
return (
<Example title="With Input">
<div className="flex flex-col gap-4">
<ButtonGroup>
<Button variant="outline">Button</Button>
<Input placeholder="Type something here..." />
</ButtonGroup>
<ButtonGroup>
<Input placeholder="Type something here..." />
<Button variant="outline">Button</Button>
</ButtonGroup>
</div>
</Example>
)
}
function ButtonGroupWithText() {
return (
<Example title="With Text">
<div className="flex flex-col gap-4">
<ButtonGroup>
<ButtonGroupText>Text</ButtonGroupText>
<Button variant="outline">Another Button</Button>
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText render={<Label htmlFor="input-text" />}>
GPU Size
</ButtonGroupText>
<Input
id="input-text"
placeholder="Type something here..."
className="style-luma:border-border"
/>
</ButtonGroup>
</div>
</Example>
)
}
function ButtonGroupWithDropdown() {
return (
<Example title="With Dropdown">
<div className="flex flex-col gap-4">
<ButtonGroup>
<Button variant="outline">Update</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" size="icon" />}
>
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Disable</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
Uninstall
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Follow</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" size="icon" />}
>
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-50">
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="VolumeX"
tabler="IconVolume"
hugeicons="VolumeOffIcon"
phosphor="SpeakerSlashIcon"
remixicon="RiVolumeMuteLine"
/>
Mute Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="AlertTriangleIcon"
tabler="IconAlertTriangle"
hugeicons="AlertCircleIcon"
phosphor="WarningIcon"
remixicon="RiErrorWarningLine"
/>
Report Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="UserRoundXIcon"
tabler="IconUserX"
hugeicons="UserRemove01Icon"
phosphor="UserMinusIcon"
remixicon="RiUserUnfollowLine"
/>
Block User
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare"
hugeicons="Share03Icon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
Share Conversation
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="Delete02Icon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</div>
</Example>
)
}
const currencyItems = [
{ label: "$", value: "$" },
{ label: "€", value: "€" },
{ label: "£", value: "£" },
]
function ButtonGroupWithSelect() {
return (
<Example title="With Select">
<Field>
<Label htmlFor="amount">Amount</Label>
<ButtonGroup>
<Select items={currencyItems} defaultValue={currencyItems[0]}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{currencyItems.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Input placeholder="Enter amount to send" />
<Button variant="outline">
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</Button>
</ButtonGroup>
</Field>
</Example>
)
}
function ButtonGroupWithIcons() {
return (
<Example title="With Icons">
<div className="flex flex-col gap-4">
<ButtonGroup>
<Button variant="outline">
<IconPlaceholder
lucide="FlipHorizontalIcon"
tabler="IconFlipHorizontal"
hugeicons="FlipHorizontalIcon"
phosphor="ArrowsHorizontalIcon"
remixicon="RiArrowLeftRightLine"
/>
</Button>
<Button variant="outline">
<IconPlaceholder
lucide="FlipVerticalIcon"
tabler="IconFlipVertical"
hugeicons="FlipVerticalIcon"
phosphor="ArrowsVerticalIcon"
remixicon="RiArrowUpDownLine"
/>
</Button>
<Button variant="outline">
<IconPlaceholder
lucide="RotateCwIcon"
tabler="IconRotateClockwise2"
hugeicons="Rotate01Icon"
phosphor="ArrowClockwiseIcon"
remixicon="RiRefreshLine"
/>
</Button>
</ButtonGroup>
</div>
</Example>
)
}
function ButtonGroupWithInputGroup() {
return (
<Example title="With Input Group">
<div className="flex flex-col gap-4">
<InputGroup>
<InputGroupInput placeholder="Type to search..." />
<InputGroupAddon
align="inline-start"
className="text-muted-foreground"
>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
</InputGroup>
</div>
</Example>
)
}
function ButtonGroupWithFields() {
return (
<Example title="With Fields">
<FieldGroup className="grid grid-cols-3 gap-4">
<Field className="col-span-2">
<Label htmlFor="width">Width</Label>
<ButtonGroup>
<InputGroup>
<InputGroupInput id="width" />
<InputGroupAddon className="text-muted-foreground">
W
</InputGroupAddon>
<InputGroupAddon
align="inline-end"
className="text-muted-foreground"
>
px
</InputGroupAddon>
</InputGroup>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="MinusIcon"
tabler="IconMinus"
hugeicons="MinusSignIcon"
phosphor="MinusIcon"
remixicon="RiSubtractLine"
/>
</Button>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
</ButtonGroup>
</Field>
</FieldGroup>
</Example>
)
}
function ButtonGroupWithLike() {
return (
<Example title="With Like">
<ButtonGroup>
<Button variant="outline">
<IconPlaceholder
lucide="HeartIcon"
tabler="IconBell"
hugeicons="Notification02Icon"
phosphor="HeartIcon"
remixicon="RiHeartLine"
data-icon="inline-start"
/>{" "}
Like
</Button>
<Button
variant="outline"
size="icon"
className="w-12"
render={<span />}
nativeButton={false}
>
1.2K
</Button>
</ButtonGroup>
</Example>
)
}
const durationItems = [
{ label: "Hours", value: "hours" },
{ label: "Days", value: "days" },
{ label: "Weeks", value: "weeks" },
]
function ButtonGroupWithSelectAndInput() {
return (
<Example title="With Select and Input">
<ButtonGroup>
<Select items={durationItems} defaultValue={durationItems[0]}>
<SelectTrigger id="duration">
<SelectValue />
</SelectTrigger>
<SelectContent align="start">
<SelectGroup>
{durationItems.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Input />
</ButtonGroup>
</Example>
)
}
function ButtonGroupNested() {
return (
<Example title="Nested">
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
</ButtonGroup>
<ButtonGroup>
<InputGroup>
<InputGroupInput placeholder="Send a message..." />
<Tooltip>
<TooltipTrigger render={<InputGroupAddon align="inline-end" />}>
<IconPlaceholder
lucide="AudioLinesIcon"
tabler="IconHeadphones"
hugeicons="AudioWave01Icon"
phosphor="MicrophoneIcon"
remixicon="RiMicLine"
/>
</TooltipTrigger>
<TooltipContent>Voice Mode</TooltipContent>
</Tooltip>
</InputGroup>
</ButtonGroup>
</ButtonGroup>
</Example>
)
}
function ButtonGroupPagination() {
return (
<Example title="Pagination">
<ButtonGroup>
<Button variant="outline" size="sm">
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft02Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
data-icon="inline-start"
/>
Previous
</Button>
<Button variant="outline" size="sm">
1
</Button>
<Button variant="outline" size="sm">
2
</Button>
<Button variant="outline" size="sm">
3
</Button>
<Button variant="outline" size="sm">
4
</Button>
<Button variant="outline" size="sm">
5
</Button>
<Button variant="outline" size="sm">
Next
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
data-icon="inline-end"
/>
</Button>
</ButtonGroup>
</Example>
)
}
function ButtonGroupPaginationSplit() {
return (
<Example title="Pagination Split">
<ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="sm">
1
</Button>
<Button variant="outline" size="sm">
2
</Button>
<Button variant="outline" size="sm">
3
</Button>
<Button variant="outline" size="sm">
4
</Button>
<Button variant="outline" size="sm">
5
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon-xs">
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft01Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
/>
</Button>
<Button variant="outline" size="icon-xs">
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</Button>
</ButtonGroup>
</ButtonGroup>
</Example>
)
}
function ButtonGroupNavigation() {
return (
<Example title="Navigation">
<ButtonGroup>
<ButtonGroup>
<Button variant="outline">
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft01Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
/>
</Button>
<Button variant="outline">
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</Button>
</ButtonGroup>
<ButtonGroup aria-label="Single navigation button">
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft01Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
/>
</Button>
</ButtonGroup>
</ButtonGroup>
</Example>
)
}
function ButtonGroupTextAlignment() {
return (
<Example title="Text Alignment">
<Field>
<Label id="alignment-label">Text Alignment</Label>
<ButtonGroup aria-labelledby="alignment-label">
<Button variant="outline" size="sm">
Left
</Button>
<Button variant="outline" size="sm">
Center
</Button>
<Button variant="outline" size="sm">
Right
</Button>
<Button variant="outline" size="sm">
Justify
</Button>
</ButtonGroup>
</Field>
</Example>
)
}
function ButtonGroupVertical() {
return (
<Example title="Vertical">
<div className="flex gap-6">
<ButtonGroup
orientation="vertical"
aria-label="Media controls"
className="h-fit"
>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Button>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="MinusIcon"
tabler="IconMinus"
hugeicons="MinusSignIcon"
phosphor="MinusIcon"
remixicon="RiSubtractLine"
/>
</Button>
</ButtonGroup>
</div>
</Example>
)
}
function ButtonGroupVerticalNested() {
return (
<Example title="Vertical Nested">
<ButtonGroup orientation="vertical" aria-label="Design tools palette">
<ButtonGroup orientation="vertical">
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</Button>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</Button>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare"
hugeicons="Share03Icon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
</Button>
</ButtonGroup>
<ButtonGroup orientation="vertical">
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="FlipHorizontalIcon"
tabler="IconFlipHorizontal"
hugeicons="FlipHorizontalIcon"
phosphor="ArrowsHorizontalIcon"
remixicon="RiArrowLeftRightLine"
/>
</Button>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="FlipVerticalIcon"
tabler="IconFlipVertical"
hugeicons="FlipVerticalIcon"
phosphor="ArrowsVerticalIcon"
remixicon="RiArrowUpDownLine"
/>
</Button>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="RotateCwIcon"
tabler="IconRotateClockwise2"
hugeicons="Rotate01Icon"
phosphor="ArrowClockwiseIcon"
remixicon="RiRefreshLine"
/>
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="Delete02Icon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
</Button>
</ButtonGroup>
</ButtonGroup>
</Example>
)
}

View File

@@ -0,0 +1,542 @@
"use client"
import * as React from "react"
import { addDays, format } from "date-fns"
import { type DateRange } from "react-day-picker"
import { es } from "react-day-picker/locale"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { Calendar, CalendarDayButton } from "@/registry/bases/base/ui/calendar"
import { Card, CardContent, CardFooter } from "@/registry/bases/base/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/base/ui/input-group"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/registry/bases/base/ui/popover"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function CalendarExample() {
return (
<ExampleWrapper>
<CalendarSingle />
<CalendarMultiple />
<CalendarWeekNumbers />
<CalendarBookedDates />
<CalendarRange />
<CalendarRangeMultipleMonths />
<CalendarWithTime />
<CalendarWithPresets />
<CalendarCustomDays />
<DatePickerSimple />
<DataPickerWithDropdowns />
<DatePickerWithRange />
<CalendarInCard />
<CalendarInPopover />
</ExampleWrapper>
)
}
function CalendarInCard() {
return (
<Example title="In Card">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar mode="single" />
</CardContent>
</Card>
</Example>
)
}
function CalendarInPopover() {
return (
<Example title="In Popover">
<Popover>
<PopoverTrigger
render={<Button variant="outline" className="px-2.5 font-normal" />}
>
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
data-icon="inline-start"
/>
Open Calendar
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar mode="single" />
</PopoverContent>
</Popover>
</Example>
)
}
function CalendarSingle() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
)
return (
<Example title="Single">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
captionLayout="dropdown"
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarMultiple() {
return (
<Example title="Multiple">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar mode="multiple" />
</CardContent>
</Card>
</Example>
)
}
function CalendarRange() {
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 12),
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
})
return (
<Example
title="Range"
containerClassName="lg:col-span-full 2xl:col-span-full"
className="p-12"
>
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
mode="range"
defaultMonth={dateRange?.from}
selected={dateRange}
onSelect={setDateRange}
numberOfMonths={2}
disabled={(date) =>
date > new Date() || date < new Date("1900-01-01")
}
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarRangeMultipleMonths() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 3, 12),
to: addDays(new Date(new Date().getFullYear(), 3, 12), 60),
})
return (
<Example
title="Range Multiple Months"
containerClassName="lg:col-span-full 2xl:col-span-full"
className="p-12"
>
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={3}
locale={es}
fixedWeeks
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarBookedDates() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 3)
)
const bookedDates = Array.from(
{ length: 15 },
(_, i) => new Date(new Date().getFullYear(), 1, 12 + i)
)
return (
<Example title="Booked Dates">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
mode="single"
defaultMonth={date}
selected={date}
onSelect={setDate}
disabled={bookedDates}
modifiers={{
booked: bookedDates,
}}
modifiersClassNames={{
booked: "[&>button]:line-through opacity-100",
}}
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarWithTime() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
)
return (
<Example title="With Time">
<Card size="sm" className="mx-auto w-fit">
<CardContent>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="p-0"
/>
</CardContent>
<CardFooter className="border-t bg-card">
<FieldGroup>
<Field>
<FieldLabel htmlFor="time-from">Start Time</FieldLabel>
<InputGroup>
<InputGroupInput
id="time-from"
type="time"
step="1"
defaultValue="10:30:00"
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
<InputGroupAddon>
<IconPlaceholder
lucide="Clock2Icon"
tabler="IconClockHour2"
hugeicons="Clock03Icon"
phosphor="ClockIcon"
remixicon="RiTimeLine"
className="text-muted-foreground"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="time-to">End Time</FieldLabel>
<InputGroup>
<InputGroupInput
id="time-to"
type="time"
step="1"
defaultValue="12:30:00"
className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
<InputGroupAddon>
<IconPlaceholder
lucide="Clock2Icon"
tabler="IconClockHour2"
hugeicons="Clock03Icon"
phosphor="ClockIcon"
remixicon="RiTimeLine"
className="text-muted-foreground"
/>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</CardFooter>
</Card>
</Example>
)
}
function CalendarCustomDays() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 11, 8),
to: addDays(new Date(new Date().getFullYear(), 11, 8), 10),
})
return (
<Example title="Custom Days">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={1}
captionLayout="dropdown"
className="[--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)]"
formatters={{
formatMonthDropdown: (date) => {
return date.toLocaleString("default", { month: "long" })
},
}}
components={{
DayButton: ({ children, modifiers, day, ...props }) => {
const isWeekend =
day.date.getDay() === 0 || day.date.getDay() === 6
return (
<CalendarDayButton day={day} modifiers={modifiers} {...props}>
{children}
{!modifiers.outside && (
<span>{isWeekend ? "$120" : "$100"}</span>
)}
</CalendarDayButton>
)
},
}}
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarWithPresets() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 12)
)
const [currentMonth, setCurrentMonth] = React.useState<Date>(
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
)
return (
<Example title="With Presets">
<Card className="mx-auto w-fit max-w-[300px]" size="sm">
<CardContent>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
month={currentMonth}
onMonthChange={setCurrentMonth}
fixedWeeks
className="p-0 [--cell-size:--spacing(9.5)]"
/>
</CardContent>
<CardFooter className="flex flex-wrap gap-2 border-t">
{[
{ label: "Today", value: 0 },
{ label: "Tomorrow", value: 1 },
{ label: "In 3 days", value: 3 },
{ label: "In a week", value: 7 },
{ label: "In 2 weeks", value: 14 },
].map((preset) => (
<Button
key={preset.value}
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
const newDate = addDays(new Date(), preset.value)
setDate(newDate)
setCurrentMonth(
new Date(newDate.getFullYear(), newDate.getMonth(), 1)
)
}}
>
{preset.label}
</Button>
))}
</CardFooter>
</Card>
</Example>
)
}
function DatePickerSimple() {
const [date, setDate] = React.useState<Date>()
return (
<Example title="Date Picker Simple">
<Field className="mx-auto w-72">
<FieldLabel htmlFor="date-picker-simple">Date</FieldLabel>
<Popover>
<PopoverTrigger
render={
<Button
variant="outline"
id="date-picker-simple"
className="justify-start px-2.5 font-normal"
/>
}
>
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
data-icon="inline-start"
/>
{date ? format(date, "PPP") : <span>Pick a date</span>}
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar mode="single" selected={date} onSelect={setDate} />
</PopoverContent>
</Popover>
</Field>
</Example>
)
}
function DatePickerWithRange() {
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 20),
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
})
return (
<Example title="Date Picker Range">
<Field className="mx-auto w-72">
<FieldLabel htmlFor="date-picker-range">Date Picker Range</FieldLabel>
<Popover>
<PopoverTrigger
render={
<Button
variant="outline"
id="date-picker-range"
className="justify-start px-2.5 font-normal"
/>
}
>
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
data-icon="inline-start"
/>
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={setDate}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</Field>
</Example>
)
}
function DataPickerWithDropdowns() {
const [date, setDate] = React.useState<Date>()
const [open, setOpen] = React.useState(false)
return (
<Example title="Date Picker with Dropdowns">
<Field className="mx-auto w-72">
<Popover open={open} onOpenChange={setOpen}>
<FieldLabel htmlFor="date-picker-with-dropdowns-desktop">
Date
</FieldLabel>
<PopoverTrigger
render={
<Button
variant="outline"
id="date-picker-with-dropdowns-desktop"
className="justify-start px-2.5 font-normal"
/>
}
>
{date ? format(date, "PPP") : <span>Pick a date</span>}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDownIcon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
data-icon="inline-start"
className="ml-auto"
/>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
captionLayout="dropdown"
/>
<div className="flex gap-2 border-t p-2">
<Button
variant="outline"
size="sm"
className="w-full"
onClick={() => setOpen(false)}
>
Done
</Button>
</div>
</PopoverContent>
</Popover>
</Field>
</Example>
)
}
function CalendarWeekNumbers() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 3)
)
return (
<Example title="Week Numbers" className="justify-center">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
mode="single"
defaultMonth={date}
selected={date}
onSelect={setDate}
showWeekNumber
/>
</CardContent>
</Card>
</Example>
)
}

View File

@@ -0,0 +1,489 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/registry/bases/base/ui/avatar"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/base/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const spacingOptions = [
{
className: "[--card-spacing:--spacing(4)]",
label: "16px",
value: "4",
},
{
className: "[--card-spacing:--spacing(5)]",
label: "20px",
value: "5",
},
{
className: "[--card-spacing:--spacing(6)]",
label: "24px",
value: "6",
},
{
className: "[--card-spacing:--spacing(8)]",
label: "32px",
value: "8",
},
]
export default function CardExample() {
return (
<ExampleWrapper>
<CardDefault />
<CardSmall />
<CardContentEdgeToEdge />
<CardCustomSpacing />
<CardHeaderWithBorder />
<CardFooterWithBorder />
<CardHeaderWithBorderSmall />
<CardFooterWithBorderSmall />
<CardWithImage />
<CardWithImageSmall />
<CardLogin />
<CardMeetingNotes />
</ExampleWrapper>
)
}
function CardContentEdgeToEdge() {
return (
<Example title="Content Edge to Edge">
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Terms of Service</CardTitle>
<CardDescription>
Review the terms before accepting the agreement.
</CardDescription>
</CardHeader>
<CardContent className="-mb-(--card-spacing) px-0">
<div className="max-h-48 space-y-4 overflow-y-scroll border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed">
<p>
These terms govern your use of the workspace, including access to
shared documents, project files, and collaboration tools.
</p>
<p>
You are responsible for the content you upload and for ensuring
that your team has the appropriate permissions to view or edit it.
</p>
<p>
We may update features or limits as the service evolves. When
those changes materially affect your workflow, we will notify your
workspace administrators.
</p>
<p>
By continuing, you agree to keep your account credentials secure
and to follow your organization&apos;s acceptable use policies.
</p>
</div>
</CardContent>
<CardFooter className="justify-end gap-2">
<Button variant="outline">Decline</Button>
<Button>Accept</Button>
</CardFooter>
</Card>
</Example>
)
}
function CardCustomSpacing() {
const [spacing, setSpacing] = React.useState("4")
const selectedSpacing = spacingOptions.find(
(option) => option.value === spacing
)
return (
<Example title="Custom Spacing">
<div className="mx-auto grid w-full max-w-sm gap-4">
<ToggleGroup
value={[spacing]}
onValueChange={(value) => {
if (value[0]) {
setSpacing(value[0])
}
}}
variant="outline"
size="sm"
className="justify-center"
>
{spacingOptions.map((option) => (
<ToggleGroupItem key={option.value} value={option.value}>
{option.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<Card className={selectedSpacing?.className}>
<CardHeader>
<CardTitle>Release Health</CardTitle>
<CardDescription>
Track readiness across launch signals.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-2 rounded-lg bg-muted/50 p-3 text-sm style-lyra:rounded-none style-sera:rounded-none">
<div className="flex items-center justify-between gap-2">
<span className="text-muted-foreground">Checks passed</span>
<span className="font-medium">24 / 26</span>
</div>
<div className="flex items-center justify-between gap-2">
<span className="text-muted-foreground">Open blockers</span>
<span className="font-medium">2</span>
</div>
</div>
</CardContent>
</Card>
</div>
</Example>
)
}
function CardLogin() {
return (
<Example title="Login">
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<form>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</Field>
<Field>
<div className="flex items-center">
<FieldLabel htmlFor="password">Password</FieldLabel>
<a
href="#"
className="ml-auto inline-block underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</Field>
</FieldGroup>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Login with Google
</Button>
<div className="mt-4 text-center">
Don&apos;t have an account?{" "}
<a href="#" className="underline underline-offset-4">
Sign up
</a>
</div>
</CardFooter>
</Card>
</Example>
)
}
function CardMeetingNotes() {
return (
<Example title="Meeting Notes">
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Meeting Notes</CardTitle>
<CardDescription>
Transcript from the meeting with the client.
</CardDescription>
<CardAction>
<Button variant="outline" size="sm">
<IconPlaceholder
lucide="CaptionsIcon"
tabler="IconTextCaption"
hugeicons="TextCheckIcon"
phosphor="TextTIcon"
remixicon="RiTextWrap"
data-icon="inline-start"
/>
Transcribe
</Button>
</CardAction>
</CardHeader>
<CardContent>
<p>
Client requested dashboard redesign with focus on mobile
responsiveness.
</p>
<ol className="mt-4 flex list-decimal flex-col gap-2 pl-6">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</CardContent>
<CardFooter>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/maxleiter.png"
alt="@maxleiter"
/>
<AvatarFallback>LR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<AvatarGroupCount>+8</AvatarGroupCount>
</AvatarGroup>
</CardFooter>
</Card>
</Example>
)
}
function CardWithImage() {
return (
<Example title="With Image">
<Card size="default" className="relative mx-auto w-full max-w-sm pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" />
<img
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Photo by mymind on Unsplash"
title="Photo by mymind on Unsplash"
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
/>
<CardHeader>
<CardTitle>Beautiful Landscape</CardTitle>
<CardDescription>
A stunning view that captures the essence of natural beauty.
</CardDescription>
</CardHeader>
<CardFooter>
<Button className="w-full">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="Add01Icon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
Button
</Button>
</CardFooter>
</Card>
</Example>
)
}
function CardWithImageSmall() {
return (
<Example title="With Image (Small)">
<Card size="sm" className="relative mx-auto w-full max-w-sm pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" />
<img
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Photo by mymind on Unsplash"
title="Photo by mymind on Unsplash"
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
/>
<CardHeader>
<CardTitle>Beautiful Landscape</CardTitle>
<CardDescription>
A stunning view that captures the essence of natural beauty.
</CardDescription>
</CardHeader>
<CardFooter>
<Button size="sm" className="w-full">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="Add01Icon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
Button
</Button>
</CardFooter>
</Card>
</Example>
)
}
function CardHeaderWithBorder() {
return (
<Example title="Header with Border">
<Card className="mx-auto w-full max-w-sm">
<CardHeader className="border-b">
<CardTitle>Header with Border</CardTitle>
<CardDescription>
This is a card with a header that has a bottom border.
</CardDescription>
</CardHeader>
<CardContent>
<p>
The header has a border-b class applied, creating a visual
separation between the header and content sections.
</p>
</CardContent>
</Card>
</Example>
)
}
function CardFooterWithBorder() {
return (
<Example title="Footer with Border">
<Card className="mx-auto w-full max-w-sm">
<CardContent>
<p>
The footer has a border-t class applied, creating a visual
separation between the content and footer sections.
</p>
</CardContent>
<CardFooter className="border-t">
<Button variant="outline" className="w-full">
Footer with Border
</Button>
</CardFooter>
</Card>
</Example>
)
}
function CardDefault() {
return (
<Example title="Default Size">
<Card size="default" className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Default Card</CardTitle>
<CardDescription>
This card uses the default size variant.
</CardDescription>
</CardHeader>
<CardContent>
<p>
The card component supports a size prop that defaults to
&quot;default&quot; for standard spacing and sizing.
</p>
</CardContent>
<CardFooter>
<Button variant="outline" className="w-full">
Action
</Button>
</CardFooter>
</Card>
</Example>
)
}
function CardSmall() {
return (
<Example title="Small Size">
<Card size="sm" className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Small Card</CardTitle>
<CardDescription>
This card uses the small size variant.
</CardDescription>
</CardHeader>
<CardContent>
<p>
The card component supports a size prop that can be set to
&quot;sm&quot; for a more compact appearance.
</p>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm" className="w-full">
Action
</Button>
</CardFooter>
</Card>
</Example>
)
}
function CardHeaderWithBorderSmall() {
return (
<Example title="Header with Border (Small)">
<Card size="sm" className="mx-auto w-full max-w-sm">
<CardHeader className="border-b">
<CardTitle>Header with Border</CardTitle>
<CardDescription>
This is a small card with a header that has a bottom border.
</CardDescription>
</CardHeader>
<CardContent>
<p>
The header has a border-b class applied, creating a visual
separation between the header and content sections.
</p>
</CardContent>
</Card>
</Example>
)
}
function CardFooterWithBorderSmall() {
return (
<Example title="Footer with Border (Small)">
<Card size="sm" className="mx-auto w-full max-w-sm">
<CardContent>
<p>
The footer has a border-t class applied, creating a visual
separation between the content and footer sections.
</p>
</CardContent>
<CardFooter className="border-t">
<Button variant="outline" size="sm" className="w-full">
Footer with Border
</Button>
</CardFooter>
</Card>
</Example>
)
}

View File

@@ -0,0 +1,99 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Card, CardContent } from "@/registry/bases/base/ui/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/registry/bases/base/ui/carousel"
export default function CarouselExample() {
return (
<ExampleWrapper className="lg:grid-cols-1">
<CarouselBasic />
<CarouselMultiple />
<CarouselWithGap />
</ExampleWrapper>
)
}
function CarouselBasic() {
return (
<Example title="Basic">
<Carousel className="mx-auto max-w-xs sm:max-w-sm">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1 style-luma:p-2.5">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden sm:inline-flex" />
<CarouselNext className="hidden sm:inline-flex" />
</Carousel>
</Example>
)
}
function CarouselMultiple() {
return (
<Example title="Multiple">
<Carousel
className="mx-auto max-w-xs sm:max-w-sm"
opts={{
align: "start",
}}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="sm:basis-1/2 lg:basis-1/3">
<div className="p-1 style-luma:p-2.5">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden sm:inline-flex" />
<CarouselNext className="hidden sm:inline-flex" />
</Carousel>
</Example>
)
}
function CarouselWithGap() {
return (
<Example title="With Gap">
<Carousel className="mx-auto max-w-xs sm:max-w-sm">
<CarouselContent className="-ml-1">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="pl-1 md:basis-1/2">
<div className="p-1 style-luma:p-2.5">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-2xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden sm:inline-flex" />
<CarouselNext className="hidden sm:inline-flex" />
</Carousel>
</Example>
)
}

View File

@@ -0,0 +1,582 @@
"use client"
import * as React from "react"
import {
Area,
AreaChart,
Bar,
BarChart,
CartesianGrid,
Label,
Line,
LineChart,
Pie,
PieChart,
PolarAngleAxis,
PolarGrid,
PolarRadiusAxis,
Radar,
RadarChart,
RadialBar,
RadialBarChart,
XAxis,
} from "recharts"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/base/ui/chart"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const areaChartData = [
{ month: "January", desktop: 186 },
{ month: "February", desktop: 305 },
{ month: "March", desktop: 237 },
{ month: "April", desktop: 73 },
{ month: "May", desktop: 209 },
{ month: "June", desktop: 214 },
]
const areaChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export default function ChartExample() {
return (
<ExampleWrapper>
<ChartAreaExample />
<ChartBarExample />
<ChartLineExample />
<ChartPieExample />
<ChartRadialExample />
<ChartRadarExample />
</ExampleWrapper>
)
}
function ChartAreaExample() {
return (
<Example title="Area Chart">
<Card className="w-full">
<CardHeader>
<CardTitle>Area Chart</CardTitle>
<CardDescription>
Showing total visitors for the last 6 months
</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={areaChartConfig}>
<AreaChart
accessibilityLayer
data={areaChartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="desktop"
type="natural"
fill="var(--color-desktop)"
fillOpacity={0.4}
stroke="var(--color-desktop)"
/>
</AreaChart>
</ChartContainer>
</CardContent>
<CardFooter>
<div className="flex w-full items-start gap-2">
<div className="grid gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
className="size-4"
/>
</div>
<div className="flex items-center gap-2 leading-none text-muted-foreground">
January - June 2024
</div>
</div>
</div>
</CardFooter>
</Card>
</Example>
)
}
const barChartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 214, mobile: 140 },
]
const barChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
function ChartBarExample() {
return (
<Example title="Bar Chart">
<Card className="w-full">
<CardHeader>
<CardTitle>Bar Chart - Multiple</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={barChartConfig}>
<BarChart accessibilityLayer data={barChartData}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="dashed" />}
/>
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
</BarChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col items-start gap-2">
<div className="flex gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
className="size-4"
/>
</div>
<div className="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
</Example>
)
}
const lineChartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 214, mobile: 140 },
]
const lineChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
function ChartLineExample() {
return (
<Example title="Line Chart">
<Card className="w-full">
<CardHeader>
<CardTitle>Line Chart - Multiple</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={lineChartConfig}>
<LineChart
accessibilityLayer
data={lineChartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<Line
dataKey="desktop"
type="monotone"
stroke="var(--color-desktop)"
strokeWidth={2}
dot={false}
/>
<Line
dataKey="mobile"
type="monotone"
stroke="var(--color-mobile)"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ChartContainer>
</CardContent>
<CardFooter>
<div className="flex w-full items-start gap-2">
<div className="grid gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
className="size-4"
/>
</div>
<div className="flex items-center gap-2 leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</div>
</div>
</CardFooter>
</Card>
</Example>
)
}
const pieChartData = [
{ browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "firefox", visitors: 287, fill: "var(--color-firefox)" },
{ browser: "edge", visitors: 173, fill: "var(--color-edge)" },
{ browser: "other", visitors: 190, fill: "var(--color-other)" },
]
const pieChartConfig = {
visitors: {
label: "Visitors",
},
chrome: {
label: "Chrome",
color: "var(--chart-1)",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
firefox: {
label: "Firefox",
color: "var(--chart-3)",
},
edge: {
label: "Edge",
color: "var(--chart-4)",
},
other: {
label: "Other",
color: "var(--chart-5)",
},
} satisfies ChartConfig
function ChartPieExample() {
const totalVisitors = React.useMemo(() => {
return pieChartData.reduce((acc, curr) => acc + curr.visitors, 0)
}, [])
return (
<Example title="Pie Chart">
<Card className="w-full">
<CardHeader className="items-center pb-0">
<CardTitle>Pie Chart - Donut with Text</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
<ChartContainer
config={pieChartConfig}
className="mx-auto aspect-square max-h-[250px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Pie
data={pieChartData}
dataKey="visitors"
nameKey="browser"
innerRadius={60}
strokeWidth={5}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-3xl font-bold"
>
{totalVisitors.toLocaleString()}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 24}
className="fill-muted-foreground"
>
Visitors
</tspan>
</text>
)
}
}}
/>
</Pie>
</PieChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
className="size-4"
/>
</div>
<div className="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
</Example>
)
}
const radarChartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
{ month: "April", desktop: 73, mobile: 190 },
{ month: "May", desktop: 209, mobile: 130 },
{ month: "June", desktop: 214, mobile: 140 },
]
const radarChartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies ChartConfig
function ChartRadarExample() {
return (
<Example title="Radar Chart">
<Card className="w-full">
<CardHeader className="items-center pb-4">
<CardTitle>Radar Chart - Multiple</CardTitle>
<CardDescription>
Showing total visitors for the last 6 months
</CardDescription>
</CardHeader>
<CardContent className="pb-0">
<ChartContainer
config={radarChartConfig}
className="mx-auto aspect-square max-h-[250px]"
>
<RadarChart data={radarChartData}>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<PolarAngleAxis dataKey="month" />
<PolarGrid />
<Radar
dataKey="desktop"
fill="var(--color-desktop)"
fillOpacity={0.6}
/>
<Radar dataKey="mobile" fill="var(--color-mobile)" />
</RadarChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
className="size-4"
/>
</div>
<div className="flex items-center gap-2 leading-none text-muted-foreground">
January - June 2024
</div>
</CardFooter>
</Card>
</Example>
)
}
const radialChartData = [
{ browser: "safari", visitors: 1260, fill: "var(--color-safari)" },
]
const radialChartConfig = {
visitors: {
label: "Visitors",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
} satisfies ChartConfig
function ChartRadialExample() {
return (
<Example title="Radial Chart">
<Card className="w-full">
<CardHeader>
<CardTitle>Radial Chart - Shape</CardTitle>
<CardDescription>January - June 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
<ChartContainer
config={radialChartConfig}
className="mx-auto aspect-square max-h-[210px]"
>
<RadialBarChart
data={radialChartData}
endAngle={100}
innerRadius={64}
outerRadius={94}
>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-muted last:fill-background"
polarRadius={[86, 74]}
/>
<RadialBar dataKey="visitors" background />
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-4xl font-bold"
>
{radialChartData[0].visitors.toLocaleString()}
</tspan>
<tspan
x={viewBox.cx}
y={(viewBox.cy || 0) + 24}
className="fill-muted-foreground"
>
Visitors
</tspan>
</text>
)
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col gap-2">
<div className="flex items-center gap-2 leading-none font-medium">
Trending up by 5.2% this month{" "}
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="ChartUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
className="size-4"
/>
</div>
<div className="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter>
</Card>
</Example>
)
}

View File

@@ -0,0 +1,260 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Checkbox } from "@/registry/bases/base/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldTitle,
} from "@/registry/bases/base/ui/field"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/base/ui/table"
export default function CheckboxExample() {
return (
<ExampleWrapper>
<CheckboxBasic />
<CheckboxWithDescription />
<CheckboxInvalid />
<CheckboxDisabled />
<CheckboxWithTitle />
<CheckboxInTable />
<CheckboxGroup />
</ExampleWrapper>
)
}
function CheckboxBasic() {
return (
<Example title="Basic">
<Field orientation="horizontal">
<Checkbox id="terms" />
<FieldLabel htmlFor="terms">Accept terms and conditions</FieldLabel>
</Field>
</Example>
)
}
function CheckboxWithDescription() {
return (
<Example title="With Description">
<Field orientation="horizontal">
<Checkbox id="terms-2" defaultChecked />
<FieldContent>
<FieldLabel htmlFor="terms-2">Accept terms and conditions</FieldLabel>
<FieldDescription>
By clicking this checkbox, you agree to the terms and conditions.
</FieldDescription>
</FieldContent>
</Field>
</Example>
)
}
function CheckboxInvalid() {
return (
<Example title="Invalid">
<Field orientation="horizontal" data-invalid>
<Checkbox id="terms-3" aria-invalid />
<FieldLabel htmlFor="terms-3">Accept terms and conditions</FieldLabel>
</Field>
</Example>
)
}
function CheckboxDisabled() {
return (
<Example title="Disabled">
<Field orientation="horizontal">
<Checkbox id="toggle" disabled />
<FieldLabel htmlFor="toggle">Enable notifications</FieldLabel>
</Field>
</Example>
)
}
function CheckboxWithTitle() {
return (
<Example title="With Title">
<FieldGroup>
<FieldLabel htmlFor="toggle-2">
<Field orientation="horizontal">
<Checkbox id="toggle-2" defaultChecked />
<FieldContent>
<FieldTitle>Enable notifications</FieldTitle>
<FieldDescription>
You can enable or disable notifications at any time.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldLabel htmlFor="toggle-4">
<Field orientation="horizontal" data-disabled>
<Checkbox id="toggle-4" disabled />
<FieldContent>
<FieldTitle>Enable notifications</FieldTitle>
<FieldDescription>
You can enable or disable notifications at any time.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
</FieldGroup>
</Example>
)
}
const tableData = [
{
id: "1",
name: "Sarah Chen",
email: "sarah.chen@example.com",
role: "Admin",
},
{
id: "2",
name: "Marcus Rodriguez",
email: "marcus.rodriguez@example.com",
role: "User",
},
{
id: "3",
name: "Priya Patel",
email: "priya.patel@example.com",
role: "User",
},
{
id: "4",
name: "David Kim",
email: "david.kim@example.com",
role: "Editor",
},
]
function CheckboxInTable() {
const [selectedRows, setSelectedRows] = React.useState<Set<string>>(
new Set(["1"])
)
const selectAll = selectedRows.size === tableData.length
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedRows(new Set(tableData.map((row) => row.id)))
} else {
setSelectedRows(new Set())
}
}
const handleSelectRow = (id: string, checked: boolean) => {
const newSelected = new Set(selectedRows)
if (checked) {
newSelected.add(id)
} else {
newSelected.delete(id)
}
setSelectedRows(newSelected)
}
return (
<Example title="In Table">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-8">
<Checkbox
id="select-all"
checked={selectAll}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tableData.map((row) => (
<TableRow
key={row.id}
data-state={selectedRows.has(row.id) ? "selected" : undefined}
>
<TableCell>
<Checkbox
id={`row-${row.id}`}
checked={selectedRows.has(row.id)}
onCheckedChange={(checked) =>
handleSelectRow(row.id, checked === true)
}
/>
</TableCell>
<TableCell className="font-medium">{row.name}</TableCell>
<TableCell>{row.email}</TableCell>
<TableCell>{row.role}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Example>
)
}
function CheckboxGroup() {
return (
<Example title="Group">
<Field>
<FieldLabel>Show these items on the desktop:</FieldLabel>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-hard-disks-ljj" />
<FieldLabel
htmlFor="finder-pref-9k2-hard-disks-ljj"
className="font-normal"
>
Hard disks
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-external-disks-1yg" />
<FieldLabel
htmlFor="finder-pref-9k2-external-disks-1yg"
className="font-normal"
>
External disks
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-cds-dvds-fzt" />
<FieldLabel
htmlFor="finder-pref-9k2-cds-dvds-fzt"
className="font-normal"
>
CDs, DVDs, and iPods
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="finder-pref-9k2-connected-servers-6l2" />
<FieldLabel
htmlFor="finder-pref-9k2-connected-servers-6l2"
className="font-normal"
>
Connected servers
</FieldLabel>
</Field>
</Field>
</Example>
)
}

View File

@@ -0,0 +1,241 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/base/ui/collapsible"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import { Tabs, TabsList, TabsTrigger } from "@/registry/bases/base/ui/tabs"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function CollapsibleExample() {
return (
<ExampleWrapper>
<CollapsibleFileTree />
<CollapsibleSettings />
</ExampleWrapper>
)
}
type FileTreeItem = { name: string } | { name: string; items: FileTreeItem[] }
function CollapsibleFileTree() {
const fileTree: FileTreeItem[] = [
{
name: "components",
items: [
{
name: "ui",
items: [
{ name: "button.tsx" },
{ name: "card.tsx" },
{ name: "dialog.tsx" },
{ name: "input.tsx" },
{ name: "select.tsx" },
{ name: "table.tsx" },
],
},
{ name: "login-form.tsx" },
{ name: "register-form.tsx" },
],
},
{
name: "lib",
items: [{ name: "utils.ts" }, { name: "cn.ts" }, { name: "api.ts" }],
},
{
name: "hooks",
items: [
{ name: "use-media-query.ts" },
{ name: "use-debounce.ts" },
{ name: "use-local-storage.ts" },
],
},
{
name: "types",
items: [{ name: "index.d.ts" }, { name: "api.d.ts" }],
},
{
name: "public",
items: [
{ name: "favicon.ico" },
{ name: "logo.svg" },
{ name: "images" },
],
},
{ name: "app.tsx" },
{ name: "layout.tsx" },
{ name: "globals.css" },
{ name: "package.json" },
{ name: "tsconfig.json" },
{ name: "README.md" },
{ name: ".gitignore" },
]
const renderItem = (fileItem: FileTreeItem) => {
if ("items" in fileItem) {
return (
<Collapsible key={fileItem.name}>
<CollapsibleTrigger
render={
<Button
variant="ghost"
size="sm"
className="group w-full justify-start transition-none hover:bg-accent hover:text-accent-foreground"
/>
}
>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="transition-transform group-data-[state=open]:rotate-90"
/>
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="Folder01Icon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
{fileItem.name}
</CollapsibleTrigger>
<CollapsibleContent className="mt-1 ml-5 style-lyra:ml-4">
<div className="flex flex-col gap-1">
{fileItem.items.map((child) => renderItem(child))}
</div>
</CollapsibleContent>
</Collapsible>
)
}
return (
<Button
key={fileItem.name}
variant="link"
size="sm"
className="w-full justify-start gap-2 text-foreground"
>
<IconPlaceholder
lucide="FileIcon"
tabler="IconFile"
hugeicons="File01Icon"
phosphor="FileIcon"
remixicon="RiFileLine"
/>
<span>{fileItem.name}</span>
</Button>
)
}
return (
<Example title="File Tree" className="items-center">
<Card className="mx-auto w-full max-w-[16rem] gap-2" size="sm">
<CardHeader>
<Tabs defaultValue="explorer">
<TabsList className="w-full">
<TabsTrigger value="explorer">Explorer</TabsTrigger>
<TabsTrigger value="settings">Outline</TabsTrigger>
</TabsList>
</Tabs>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-1">
{fileTree.map((item) => renderItem(item))}
</div>
</CardContent>
</Card>
</Example>
)
}
function CollapsibleSettings() {
const [isOpen, setIsOpen] = React.useState(false)
return (
<Example title="Settings" className="items-center">
<Card className="mx-auto w-full max-w-xs" size="sm">
<CardHeader>
<CardTitle>Radius</CardTitle>
<CardDescription>
Set the corner radius of the element.
</CardDescription>
</CardHeader>
<CardContent>
<Collapsible
open={isOpen}
onOpenChange={setIsOpen}
className="flex items-start gap-2"
>
<FieldGroup className="grid w-full grid-cols-2 gap-2">
<Field>
<FieldLabel htmlFor="radius-x" className="sr-only">
Radius X
</FieldLabel>
<Input id="radius" placeholder="0" defaultValue={0} />
</Field>
<Field>
<FieldLabel htmlFor="radius-y" className="sr-only">
Radius Y
</FieldLabel>
<Input id="radius" placeholder="0" defaultValue={0} />
</Field>
<CollapsibleContent className="col-span-full grid grid-cols-subgrid gap-2">
<Field>
<FieldLabel htmlFor="radius-x" className="sr-only">
Radius X
</FieldLabel>
<Input id="radius" placeholder="0" defaultValue={0} />
</Field>
<Field>
<FieldLabel htmlFor="radius-y" className="sr-only">
Radius Y
</FieldLabel>
<Input id="radius" placeholder="0" defaultValue={0} />
</Field>
</CollapsibleContent>
</FieldGroup>
<CollapsibleTrigger
render={<Button variant="outline" size="icon" />}
>
{isOpen ? (
<IconPlaceholder
lucide="MinimizeIcon"
tabler="IconMinimize"
hugeicons="MinusSignIcon"
phosphor="MinusIcon"
remixicon="RiSubtractLine"
/>
) : (
<IconPlaceholder
lucide="MaximizeIcon"
tabler="IconMaximize"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
)}
</CollapsibleTrigger>
</Collapsible>
</CardContent>
</Card>
</Example>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,590 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { Card, CardContent } from "@/registry/bases/base/ui/card"
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/registry/bases/base/ui/command"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function CommandExample() {
return (
<ExampleWrapper>
<CommandInline />
<CommandBasic />
<CommandWithShortcuts />
<CommandWithGroups />
<CommandManyItems />
</ExampleWrapper>
)
}
function CommandInline() {
return (
<Example title="Inline">
<Card className="w-full p-0">
<CardContent className="p-0">
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
/>
<span>Calendar</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="SmileIcon"
tabler="IconMoodSmile"
hugeicons="SmileIcon"
phosphor="SmileyIcon"
remixicon="RiEmotionLine"
/>
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CalculatorIcon"
tabler="IconCalculator"
hugeicons="CalculatorIcon"
phosphor="CalculatorIcon"
remixicon="RiCalculatorLine"
/>
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CardContent>
</Card>
</Example>
)
}
function CommandBasic() {
const [open, setOpen] = React.useState(false)
return (
<Example title="Basic">
<div className="flex flex-col gap-4">
<Button
onClick={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>Calendar</CommandItem>
<CommandItem>Search Emoji</CommandItem>
<CommandItem>Calculator</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandDialog>
</div>
</Example>
)
}
function CommandWithShortcuts() {
const [open, setOpen] = React.useState(false)
return (
<Example title="With Shortcuts">
<div className="flex flex-col gap-4">
<Button
onClick={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Settings">
<CommandItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandDialog>
</div>
</Example>
)
}
function CommandWithGroups() {
const [open, setOpen] = React.useState(false)
return (
<Example title="With Groups">
<div className="flex flex-col gap-4">
<Button
onClick={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem>
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
/>
<span>Calendar</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="SmileIcon"
tabler="IconMoodSmile"
hugeicons="SmileIcon"
phosphor="SmileyIcon"
remixicon="RiEmotionLine"
/>
<span>Search Emoji</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CalculatorIcon"
tabler="IconCalculator"
hugeicons="CalculatorIcon"
phosphor="CalculatorIcon"
remixicon="RiCalculatorLine"
/>
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandDialog>
</div>
</Example>
)
}
function CommandManyItems() {
const [open, setOpen] = React.useState(false)
return (
<Example title="Many Groups & Items">
<div className="flex flex-col gap-4">
<Button
onClick={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Navigation">
<CommandItem>
<IconPlaceholder
lucide="HomeIcon"
tabler="IconHome"
hugeicons="HomeIcon"
phosphor="HouseIcon"
remixicon="RiHomeLine"
/>
<span>Home</span>
<CommandShortcut>⌘H</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="InboxIcon"
tabler="IconInbox"
hugeicons="InboxIcon"
phosphor="TrayIcon"
remixicon="RiInboxLine"
/>
<span>Inbox</span>
<CommandShortcut>⌘I</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="File02Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
<span>Documents</span>
<CommandShortcut>⌘D</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="FolderIcon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
<span>Folders</span>
<CommandShortcut>⌘F</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
<span>New File</span>
<CommandShortcut>⌘N</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="FolderPlusIcon"
tabler="IconFolderPlus"
hugeicons="FolderAddIcon"
phosphor="FolderPlusIcon"
remixicon="RiFolderAddLine"
/>
<span>New Folder</span>
<CommandShortcut>⇧⌘N</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
<span>Copy</span>
<CommandShortcut>⌘C</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
<span>Cut</span>
<CommandShortcut>⌘X</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="ClipboardPasteIcon"
tabler="IconClipboard"
hugeicons="ClipboardIcon"
phosphor="ClipboardIcon"
remixicon="RiClipboardLine"
/>
<span>Paste</span>
<CommandShortcut>⌘V</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
<span>Delete</span>
<CommandShortcut>⌫</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="View">
<CommandItem>
<IconPlaceholder
lucide="LayoutGridIcon"
tabler="IconLayoutGrid"
hugeicons="GridIcon"
phosphor="GridFourIcon"
remixicon="RiGridLine"
/>
<span>Grid View</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="ListIcon"
tabler="IconList"
hugeicons="Menu05Icon"
phosphor="ListIcon"
remixicon="RiListUnordered"
/>
<span>List View</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="ZoomInIcon"
tabler="IconZoomIn"
hugeicons="ZoomInAreaIcon"
phosphor="MagnifyingGlassPlusIcon"
remixicon="RiZoomInLine"
/>
<span>Zoom In</span>
<CommandShortcut>⌘+</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="ZoomOutIcon"
tabler="IconZoomOut"
hugeicons="ZoomOutAreaIcon"
phosphor="MagnifyingGlassMinusIcon"
remixicon="RiSearchEyeLine"
/>
<span>Zoom Out</span>
<CommandShortcut>⌘-</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Account">
<CommandItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
<span>Notifications</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="HelpCircleIcon"
tabler="IconHelpCircle"
hugeicons="HelpCircleIcon"
phosphor="QuestionIcon"
remixicon="RiQuestionLine"
/>
<span>Help & Support</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Tools">
<CommandItem>
<IconPlaceholder
lucide="CalculatorIcon"
tabler="IconCalculator"
hugeicons="CalculatorIcon"
phosphor="CalculatorIcon"
remixicon="RiCalculatorLine"
/>
<span>Calculator</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
/>
<span>Calendar</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="ImageIcon"
tabler="IconPhoto"
hugeicons="ImageIcon"
phosphor="ImageIcon"
remixicon="RiImageLine"
/>
<span>Image Editor</span>
</CommandItem>
<CommandItem>
<IconPlaceholder
lucide="CodeIcon"
tabler="IconCode"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
<span>Code Editor</span>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</CommandDialog>
</div>
</Example>
)
}

View File

@@ -0,0 +1,663 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/base/ui/alert-dialog"
import { Badge } from "@/registry/bases/base/ui/badge"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "@/registry/bases/base/ui/combobox"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
import { Textarea } from "@/registry/bases/base/ui/textarea"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function ComponentExample() {
return (
<ExampleWrapper>
<CardExample />
<FormExample />
</ExampleWrapper>
)
}
function CardExample() {
return (
<Example title="Card" className="items-center justify-center">
<Card className="relative w-full max-w-sm overflow-hidden pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" />
<img
src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="Photo by mymind on Unsplash"
title="Photo by mymind on Unsplash"
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
/>
<CardHeader>
<CardTitle>Observability Plus is replacing Monitoring</CardTitle>
<CardDescription>
Switch to the improved way to explore your data, with natural
language. Monitoring will no longer be available on the Pro plan in
November, 2025
</CardDescription>
</CardHeader>
<CardFooter>
<AlertDialog>
<AlertDialogTrigger render={<Button />}>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
Show Dialog
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogMedia>
<IconPlaceholder
lucide="BluetoothIcon"
tabler="IconBluetooth"
hugeicons="BluetoothIcon"
phosphor="BluetoothIcon"
remixicon="RiBluetoothLine"
/>
</AlertDialogMedia>
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to this
device?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Badge variant="secondary" className="ml-auto">
Warning
</Badge>
</CardFooter>
</Card>
</Example>
)
}
const frameworks = [
"Next.js",
"SvelteKit",
"Nuxt.js",
"Remix",
"Astro",
] as const
const roleItems = [
{ label: "Developer", value: "developer" },
{ label: "Designer", value: "designer" },
{ label: "Manager", value: "manager" },
{ label: "Other", value: "other" },
]
function FormExample() {
const [notifications, setNotifications] = React.useState({
email: true,
sms: false,
push: true,
})
const [theme, setTheme] = React.useState("light")
return (
<Example title="Form">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>User Information</CardTitle>
<CardDescription>Please fill in your details below</CardDescription>
<CardAction>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="ghost" size="icon" />}
>
<IconPlaceholder
lucide="MoreVerticalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeVerticalIcon"
remixicon="RiMore2Line"
/>
<span className="sr-only">More options</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>File</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="FileIcon"
tabler="IconFile"
hugeicons="FileIcon"
phosphor="FileIcon"
remixicon="RiFileLine"
/>
New File
<DropdownMenuShortcut>⌘N</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="FolderIcon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
New Folder
<DropdownMenuShortcut>⇧⌘N</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="FolderOpenIcon"
tabler="IconFolderOpen"
hugeicons="FolderOpenIcon"
phosphor="FolderOpenIcon"
remixicon="RiFolderOpenLine"
/>
Open Recent
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Recent Projects</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="FileCodeIcon"
tabler="IconFileCode"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
Project Alpha
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="FileCodeIcon"
tabler="IconFileCode"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
Project Beta
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
More Projects
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<IconPlaceholder
lucide="FileCodeIcon"
tabler="IconFileCode"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
Project Gamma
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="FileCodeIcon"
tabler="IconFileCode"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
Project Delta
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="FolderSearchIcon"
tabler="IconFolderSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
Browse...
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconPlaceholder
lucide="SaveIcon"
tabler="IconDeviceFloppy"
hugeicons="FloppyDiskIcon"
phosphor="FloppyDiskIcon"
remixicon="RiSaveLine"
/>
Save
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="DownloadIcon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
/>
Export
<DropdownMenuShortcut>⇧⌘E</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>View</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={notifications.email}
onCheckedChange={(checked) =>
setNotifications({
...notifications,
email: checked === true,
})
}
>
<IconPlaceholder
lucide="EyeIcon"
tabler="IconEye"
hugeicons="EyeIcon"
phosphor="EyeIcon"
remixicon="RiEyeLine"
/>
Show Sidebar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={notifications.sms}
onCheckedChange={(checked) =>
setNotifications({
...notifications,
sms: checked === true,
})
}
>
<IconPlaceholder
lucide="LayoutIcon"
tabler="IconLayout"
hugeicons="LayoutIcon"
phosphor="LayoutIcon"
remixicon="RiLayoutLine"
/>
Show Status Bar
</DropdownMenuCheckboxItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="PaletteIcon"
tabler="IconPalette"
hugeicons="PaintBoardIcon"
phosphor="PaletteIcon"
remixicon="RiPaletteLine"
/>
Theme
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={theme}
onValueChange={setTheme}
>
<DropdownMenuRadioItem value="light">
<IconPlaceholder
lucide="SunIcon"
tabler="IconSun"
hugeicons="SunIcon"
phosphor="SunIcon"
remixicon="RiSunLine"
/>
Light
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark">
<IconPlaceholder
lucide="MoonIcon"
tabler="IconMoon"
hugeicons="MoonIcon"
phosphor="MoonIcon"
remixicon="RiMoonLine"
/>
Dark
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system">
<IconPlaceholder
lucide="MonitorIcon"
tabler="IconDeviceDesktop"
hugeicons="ComputerIcon"
phosphor="MonitorIcon"
remixicon="RiComputerLine"
/>
System
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Account</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
Profile
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
Settings
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Preferences</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="KeyboardIcon"
tabler="IconKeyboard"
hugeicons="KeyboardIcon"
phosphor="KeyboardIcon"
remixicon="RiKeyboardLine"
/>
Keyboard Shortcuts
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="LanguagesIcon"
tabler="IconLanguage"
hugeicons="LanguageCircleIcon"
phosphor="TranslateIcon"
remixicon="RiTranslate"
/>
Language
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
Notifications
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuLabel>
Notification Types
</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={notifications.push}
onCheckedChange={(checked) =>
setNotifications({
...notifications,
push: checked === true,
})
}
>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
Push Notifications
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={notifications.email}
onCheckedChange={(checked) =>
setNotifications({
...notifications,
email: checked === true,
})
}
>
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="MailIcon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
Email Notifications
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShieldIcon"
tabler="IconShield"
hugeicons="ShieldIcon"
phosphor="ShieldIcon"
remixicon="RiShieldLine"
/>
Privacy & Security
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="HelpCircleIcon"
tabler="IconHelpCircle"
hugeicons="HelpCircleIcon"
phosphor="QuestionIcon"
remixicon="RiQuestionLine"
/>
Help & Support
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="File01Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
Documentation
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign Out
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</CardAction>
</CardHeader>
<CardContent>
<form>
<FieldGroup>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="small-form-name">Name</FieldLabel>
<Input
id="small-form-name"
placeholder="Enter your name"
required
/>
</Field>
<Field>
<FieldLabel htmlFor="small-form-role">Role</FieldLabel>
<Select items={roleItems} defaultValue={null}>
<SelectTrigger id="small-form-role">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{roleItems.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
<Field>
<FieldLabel htmlFor="small-form-framework">
Framework
</FieldLabel>
<Combobox items={frameworks}>
<ComboboxInput
id="small-form-framework"
placeholder="Select a framework"
required
/>
<ComboboxContent>
<ComboboxEmpty>No frameworks found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
<Field>
<FieldLabel htmlFor="small-form-comments">Comments</FieldLabel>
<Textarea
id="small-form-comments"
placeholder="Add any additional comments"
/>
</Field>
<Field orientation="horizontal">
<Button type="submit">Submit</Button>
<Button variant="outline" type="button">
Cancel
</Button>
</Field>
</FieldGroup>
</form>
</CardContent>
</Card>
</Example>
)
}

View File

@@ -0,0 +1,597 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/registry/bases/base/ui/context-menu"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function ContextMenuExample() {
return (
<ExampleWrapper>
<ContextMenuBasic />
<ContextMenuWithIcons />
<ContextMenuWithSides />
<ContextMenuWithShortcuts />
<ContextMenuWithSubmenu />
<ContextMenuWithGroups />
<ContextMenuWithCheckboxes />
<ContextMenuWithRadio />
<ContextMenuWithDestructive />
<ContextMenuInDialog />
<ContextMenuWithInset />
</ExampleWrapper>
)
}
function ContextMenuBasic() {
return (
<Example title="Basic">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem disabled>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithIcons() {
return (
<Example title="With Icons">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</ContextMenuItem>
<ContextMenuItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</ContextMenuItem>
<ContextMenuItem>
<IconPlaceholder
lucide="ClipboardPasteIcon"
tabler="IconClipboard"
hugeicons="ClipboardIcon"
phosphor="ClipboardIcon"
remixicon="RiClipboardLine"
/>
Paste
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithShortcuts() {
return (
<Example title="With Shortcuts">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
Back
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>
Forward
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Reload
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>
Save
<ContextMenuShortcut>⌘S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Save As...
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithSubmenu() {
return (
<Example title="With Submenu">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
Copy
<ContextMenuShortcut>⌘C</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Cut
<ContextMenuShortcut>⌘X</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSub>
<ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuGroup>
<ContextMenuItem>Save Page...</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
<ContextMenuItem>Name Window...</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">Delete</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithGroups() {
return (
<Example title="With Groups, Labels & Separators">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuLabel>File</ContextMenuLabel>
<ContextMenuItem>
New File
<ContextMenuShortcut>⌘N</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Open File
<ContextMenuShortcut>⌘O</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Save
<ContextMenuShortcut>⌘S</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel>Edit</ContextMenuLabel>
<ContextMenuItem>
Undo
<ContextMenuShortcut>⌘Z</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Redo
<ContextMenuShortcut>⇧⌘Z</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>
Cut
<ContextMenuShortcut>⌘X</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Copy
<ContextMenuShortcut>⌘C</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Paste
<ContextMenuShortcut>⌘V</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">
Delete
<ContextMenuShortcut>⌫</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithCheckboxes() {
return (
<Example title="With Checkboxes">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuCheckboxItem defaultChecked>
Show Bookmarks Bar
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem defaultChecked>
Show Developer Tools
</ContextMenuCheckboxItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithRadio() {
const [user, setUser] = React.useState("pedro")
const [theme, setTheme] = React.useState("light")
return (
<Example title="With Radio Group">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuLabel>People</ContextMenuLabel>
<ContextMenuRadioGroup value={user} onValueChange={setUser}>
<ContextMenuRadioItem value="pedro">
Pedro Duarte
</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">
Colm Tuite
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel>Theme</ContextMenuLabel>
<ContextMenuRadioGroup value={theme} onValueChange={setTheme}>
<ContextMenuRadioItem value="light">Light</ContextMenuRadioItem>
<ContextMenuRadioItem value="dark">Dark</ContextMenuRadioItem>
<ContextMenuRadioItem value="system">System</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithDestructive() {
return (
<Example title="With Destructive Items">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
<IconPlaceholder
lucide="PencilIcon"
tabler="IconPencil"
hugeicons="EditIcon"
phosphor="PencilIcon"
remixicon="RiPencilLine"
/>
Edit
</ContextMenuItem>
<ContextMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare"
hugeicons="ShareIcon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
Share
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>
<IconPlaceholder
lucide="ArchiveIcon"
tabler="IconArchive"
hugeicons="Archive02Icon"
phosphor="ArchiveIcon"
remixicon="RiArchiveLine"
/>
Archive
</ContextMenuItem>
<ContextMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}
function ContextMenuWithSides() {
return (
<Example title="With Sides" containerClassName="col-span-2">
<div className="flex flex-wrap justify-center gap-2">
{(
[
"inline-start",
"left",
"top",
"bottom",
"right",
"inline-end",
] as const
).map((side) => (
<ContextMenu key={side}>
<ContextMenuTrigger className="flex aspect-[2/0.5] items-center justify-center rounded-lg border p-4 text-sm capitalize">
{side.replace("-", " ")}
</ContextMenuTrigger>
<ContextMenuContent side={side}>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
))}
</div>
</Example>
)
}
function ContextMenuInDialog() {
return (
<Example title="In Dialog">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Context Menu Example</DialogTitle>
<DialogDescription>
Right click on the area below to see the context menu.
</DialogDescription>
</DialogHeader>
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</ContextMenuItem>
<ContextMenuItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</ContextMenuItem>
<ContextMenuItem>
<IconPlaceholder
lucide="ClipboardPasteIcon"
tabler="IconClipboard"
hugeicons="ClipboardIcon"
phosphor="ClipboardIcon"
remixicon="RiClipboardLine"
/>
Paste
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuSub>
<ContextMenuSubTrigger>More Options</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuGroup>
<ContextMenuItem>Save Page...</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
<ContextMenuItem>Name Window...</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>
</DialogContent>
</Dialog>
</Example>
)
}
function ContextMenuWithInset() {
const [showBookmarks, setShowBookmarks] = React.useState(true)
const [showUrls, setShowUrls] = React.useState(false)
const [theme, setTheme] = React.useState("system")
return (
<Example title="With Inset">
<ContextMenu>
<ContextMenuTrigger className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent className="w-44">
<ContextMenuGroup>
<ContextMenuLabel>Actions</ContextMenuLabel>
<ContextMenuItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</ContextMenuItem>
<ContextMenuItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</ContextMenuItem>
<ContextMenuItem inset>Paste</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel inset>Appearance</ContextMenuLabel>
<ContextMenuCheckboxItem
inset
checked={showBookmarks}
onCheckedChange={setShowBookmarks}
>
Bookmarks
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
inset
checked={showUrls}
onCheckedChange={setShowUrls}
>
Full URLs
</ContextMenuCheckboxItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel inset>Theme</ContextMenuLabel>
<ContextMenuRadioGroup value={theme} onValueChange={setTheme}>
<ContextMenuRadioItem inset value="light">
Light
</ContextMenuRadioItem>
<ContextMenuRadioItem inset value="dark">
Dark
</ContextMenuRadioItem>
<ContextMenuRadioItem inset value="system">
System
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuSub>
<ContextMenuSubTrigger inset>More Options</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuGroup>
<ContextMenuItem>Save Page...</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>
</Example>
)
}

View File

@@ -0,0 +1,414 @@
"use client"
import * as React from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/base/ui/alert-dialog"
import { Badge } from "@/registry/bases/base/ui/badge"
import { Button } from "@/registry/bases/base/ui/button"
import { ButtonGroup } from "@/registry/bases/base/ui/button-group"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import { Checkbox } from "@/registry/bases/base/ui/checkbox"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/bases/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/registry/bases/base/ui/input-group"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/base/ui/item"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/base/ui/radio-group"
import { Slider } from "@/registry/bases/base/ui/slider"
import { Switch } from "@/registry/bases/base/ui/switch"
import { Textarea } from "@/registry/bases/base/ui/textarea"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export function Demo() {
const [sliderValue, setSliderValue] = React.useState<number[]>([500])
const handleSliderValueChange = React.useCallback(
(value: number | readonly number[]) => {
if (typeof value === "number") {
setSliderValue([value])
} else {
setSliderValue([...value])
}
},
[]
)
return (
<div className="flex min-h-screen w-full flex-col items-center justify-center bg-muted p-4 sm:p-6 lg:p-12 dark:bg-background">
<div className="grid max-w-3xl gap-4 sm:grid-cols-2">
<div className="flex flex-col gap-4">
<Card>
<CardHeader>
<CardTitle>Style Overview</CardTitle>
<CardDescription className="line-clamp-2">
Designers love packing quirky glyphs into test phrases. This is
a preview of the typography styles.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-6 gap-3">
{[
"--background",
"--foreground",
"--primary",
"--secondary",
"--muted",
"--accent",
"--border",
"--chart-1",
"--chart-2",
"--chart-3",
"--chart-4",
"--chart-5",
].map((variant) => (
<div
key={variant}
className="flex flex-col flex-wrap items-center gap-2"
>
<div
className="relative aspect-square w-full rounded-lg bg-(--color) after:absolute after:inset-0 after:rounded-lg after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten"
style={
{
"--color": `var(${variant})`,
} as React.CSSProperties
}
/>
<div className="hidden max-w-14 truncate font-mono text-[0.60rem] md:block">
{variant}
</div>
</div>
))}
</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="grid grid-cols-8 place-items-center gap-4">
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="Delete02Icon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare"
hugeicons="Share03Icon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="ShoppingBagIcon"
tabler="IconShoppingBag"
hugeicons="ShoppingBag01Icon"
phosphor="BagIcon"
remixicon="RiShoppingBagLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeIcon"
remixicon="RiMoreLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="Loader2Icon"
tabler="IconLoader"
hugeicons="Loading03Icon"
phosphor="SpinnerIcon"
remixicon="RiLoaderLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="MinusIcon"
tabler="IconMinus"
hugeicons="MinusSignIcon"
phosphor="MinusIcon"
remixicon="RiSubtractLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft02Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</Card>
<Card className="flex size-8 items-center justify-center p-0 shadow-none *:[svg]:size-4">
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="Settings01Icon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
</Card>
</div>
</CardContent>
</Card>
</div>
<div className="flex flex-col gap-4">
<Card className="w-full">
<CardContent className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-2">
<Button>Button</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
</div>
<Item variant="outline">
<ItemContent>
<ItemTitle>Two-factor authentication</ItemTitle>
<ItemDescription className="text-pretty xl:hidden 2xl:block">
Verify via email or phone number.
</ItemDescription>
</ItemContent>
<ItemActions className="hidden md:flex">
<Button size="sm" variant="secondary">
Enable
</Button>
</ItemActions>
</Item>
</div>
<Slider
value={sliderValue}
onValueChange={handleSliderValueChange}
max={1000}
min={0}
step={10}
className="flex-1"
aria-label="Slider"
/>
<FieldGroup>
<Field>
<InputGroup>
<InputGroupInput placeholder="Name" />
<InputGroupAddon align="inline-end">
<InputGroupText>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<Field className="flex-1">
<Textarea placeholder="Message" className="resize-none" />
</Field>
</FieldGroup>
<div className="flex items-center gap-2">
<div className="flex gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<RadioGroup
defaultValue="apple"
className="ml-auto flex w-fit gap-3"
>
<RadioGroupItem value="apple" />
<RadioGroupItem value="banana" />
</RadioGroup>
<div className="flex gap-3">
<Checkbox defaultChecked />
<Checkbox />
</div>
</div>
<div className="flex items-center gap-4">
<AlertDialog>
<AlertDialogTrigger render={<Button variant="outline" />}>
<span className="hidden md:block">Alert Dialog</span>
<span className="block md:hidden">Dialog</span>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>
Allow accessory to connect?
</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to
this device and your data?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ButtonGroup>
<Button variant="outline">Button Group</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" size="icon" />}
>
<IconPlaceholder
lucide="ChevronUpIcon"
tabler="IconChevronUp"
hugeicons="ArrowUp01Icon"
phosphor="CaretUpIcon"
remixicon="RiArrowUpSLine"
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
side="top"
className="w-fit"
>
<DropdownMenuGroup>
<DropdownMenuLabel>Quick Actions</DropdownMenuLabel>
<DropdownMenuItem>Mute Conversation</DropdownMenuItem>
<DropdownMenuItem>Mark as Read</DropdownMenuItem>
<DropdownMenuItem>Block User</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Conversation</DropdownMenuLabel>
<DropdownMenuItem>Share Conversation</DropdownMenuItem>
<DropdownMenuItem>Copy Conversation</DropdownMenuItem>
<DropdownMenuItem>Report Conversation</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
<Switch defaultChecked className="ml-auto" />
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,601 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { Checkbox } from "@/registry/bases/base/ui/checkbox"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/base/ui/input-group"
import { Kbd } from "@/registry/bases/base/ui/kbd"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/base/ui/native-select"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
import { Switch } from "@/registry/bases/base/ui/switch"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/base/ui/tabs"
import { Textarea } from "@/registry/bases/base/ui/textarea"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/base/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function DialogExample() {
return (
<ExampleWrapper>
<DialogWithForm />
<DialogScrollableContent />
<DialogWithStickyFooter />
<DialogNoCloseButton />
<DialogChatSettings />
</ExampleWrapper>
)
}
function DialogWithForm() {
return (
<Example title="With Form" className="items-center justify-center">
<Dialog>
<form>
<DialogTrigger render={<Button variant="outline" />}>
Edit Profile
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you&apos;re
done. Your profile will be updated immediately.
</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field>
<FieldLabel htmlFor="name-1">Name</FieldLabel>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</Field>
<Field>
<FieldLabel htmlFor="username-1">Username</FieldLabel>
<Input
id="username-1"
name="username"
defaultValue="@peduarte"
/>
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
</Example>
)
}
function DialogScrollableContent() {
return (
<Example title="Scrollable Content" className="items-center justify-center">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Scrollable Content
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Scrollable Content</DialogTitle>
<DialogDescription>
This is a dialog with scrollable content.
</DialogDescription>
</DialogHeader>
<div className="no-scrollbar max-h-[70vh] overflow-y-auto style-vega:-mx-6 style-vega:px-6 style-nova:-mx-4 style-nova:px-4 style-lyra:-mx-4 style-lyra:px-4 style-maia:-mx-6 style-maia:px-6 style-mira:-mx-4 style-mira:px-4 style-luma:-mx-6 style-luma:px-6 style-rhea:-mx-6 style-rhea:px-6">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
</DialogContent>
</Dialog>
</Example>
)
}
function DialogWithStickyFooter() {
return (
<Example title="With Sticky Footer" className="items-center justify-center">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Sticky Footer
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Scrollable Content</DialogTitle>
<DialogDescription>
This is a dialog with scrollable content.
</DialogDescription>
</DialogHeader>
<div className="no-scrollbar max-h-[70vh] overflow-y-auto style-vega:-mx-6 style-vega:px-6 style-nova:-mx-4 style-nova:px-4 style-lyra:-mx-4 style-lyra:px-4 style-maia:-mx-6 style-maia:px-6 style-mira:-mx-4 style-mira:px-4 style-luma:-mx-6 style-luma:px-6 style-rhea:-mx-6 style-rhea:px-6">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>
Close
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</Example>
)
}
function DialogNoCloseButton() {
return (
<Example title="No Close Button" className="items-center justify-center">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
No Close Button
</DialogTrigger>
<DialogContent showCloseButton={false}>
<DialogHeader>
<DialogTitle>No Close Button</DialogTitle>
<DialogDescription>
This dialog doesn&apos;t have a close button in the top-right
corner.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>
Close
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</Example>
)
}
const spokenLanguages = [
{ label: "Auto", value: "auto" },
{ label: "English", value: "en" },
{ label: "Spanish", value: "es" },
{ label: "French", value: "fr" },
{ label: "German", value: "de" },
{ label: "Italian", value: "it" },
{ label: "Portuguese", value: "pt" },
{ label: "Russian", value: "ru" },
{ label: "Chinese", value: "zh" },
{ label: "Japanese", value: "ja" },
{ label: "Korean", value: "ko" },
{ label: "Arabic", value: "ar" },
{ label: "Hindi", value: "hi" },
{ label: "Bengali", value: "bn" },
{ label: "Telugu", value: "te" },
{ label: "Marathi", value: "mr" },
{ label: "Kannada", value: "kn" },
{ label: "Malayalam", value: "ml" },
]
const voices = [
{ label: "Samantha", value: "samantha" },
{ label: "Alex", value: "alex" },
{ label: "Fred", value: "fred" },
{ label: "Victoria", value: "victoria" },
{ label: "Tom", value: "tom" },
{ label: "Karen", value: "karen" },
{ label: "Sam", value: "sam" },
{ label: "Daniel", value: "daniel" },
]
const themes = [
{ label: "Light", value: "light" },
{ label: "Dark", value: "dark" },
{ label: "System", value: "system" },
]
const accents = [
{ label: "Default", value: "default" },
{ label: "Red", value: "red" },
{ label: "Blue", value: "blue" },
{ label: "Green", value: "green" },
{ label: "Purple", value: "purple" },
{ label: "Pink", value: "pink" },
]
function DialogChatSettings() {
const [tab, setTab] = React.useState("general")
const [theme, setTheme] = React.useState("system")
const [accentColor, setAccentColor] = React.useState("default")
const [spokenLanguage, setSpokenLanguage] = React.useState("en")
const [voice, setVoice] = React.useState("samantha")
return (
<Example title="Chat Settings" className="items-center justify-center">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Chat Settings
</DialogTrigger>
<DialogContent className="min-w-md">
<DialogHeader>
<DialogTitle>Chat Settings</DialogTitle>
<DialogDescription>
Customize your chat settings: theme, accent color, spoken
language, voice, personality, and custom instructions.
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4">
<NativeSelect
value={tab}
onChange={(e) => setTab(e.target.value)}
className="w-full md:hidden"
>
<NativeSelectOption value="general">General</NativeSelectOption>
<NativeSelectOption value="notifications">
Notifications
</NativeSelectOption>
<NativeSelectOption value="personalization">
Personalization
</NativeSelectOption>
<NativeSelectOption value="security">Security</NativeSelectOption>
</NativeSelect>
<Tabs value={tab} onValueChange={setTab}>
<TabsList className="hidden w-full md:flex">
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="personalization">
Personalization
</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
</TabsList>
<div className="border **:data-[slot=select-trigger]:min-w-[125px] style-vega:min-h-[550px] style-vega:rounded-lg style-vega:p-6 style-nova:min-h-[460px] style-nova:rounded-lg style-nova:p-4 style-lyra:min-h-[450px] style-lyra:rounded-none style-lyra:p-4 style-maia:min-h-[550px] style-maia:rounded-xl style-maia:p-6 style-mira:min-h-[450px] style-mira:rounded-md style-mira:p-4 style-luma:min-h-[550px] style-luma:rounded-xl style-luma:p-6 style-rhea:min-h-[480px] style-rhea:rounded-2xl style-rhea:p-6">
<TabsContent value="general">
<FieldSet>
<FieldGroup>
<Field orientation="horizontal">
<FieldLabel htmlFor="theme">Theme</FieldLabel>
<Select
items={themes}
value={theme}
onValueChange={(value) => setTheme(value as string)}
>
<SelectTrigger id="theme">
<SelectValue />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{themes.map((theme) => (
<SelectItem
key={theme.value}
value={theme.value}
>
{theme.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldLabel htmlFor="accent-color">
Accent Color
</FieldLabel>
<Select
items={accents}
value={accentColor}
onValueChange={(value) =>
setAccentColor(value as string)
}
>
<SelectTrigger id="accent-color">
<SelectValue />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{accents.map((accent) => (
<SelectItem
key={accent.value}
value={accent.value}
>
{accent.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="responsive">
<FieldContent>
<FieldLabel htmlFor="spoken-language">
Spoken Language
</FieldLabel>
<FieldDescription>
For best results, select the language you mainly
speak. If it&apos;s not listed, it may still be
supported via auto-detection.
</FieldDescription>
</FieldContent>
<Select
items={spokenLanguages}
value={spokenLanguage}
onValueChange={(value) =>
setSpokenLanguage(value as string)
}
>
<SelectTrigger id="spoken-language">
<SelectValue />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{spokenLanguages.map((language) => (
<SelectItem
key={language.value}
value={language.value}
>
{language.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldLabel htmlFor="voice">Voice</FieldLabel>
<Select
items={voices}
value={voice}
onValueChange={(value) => setVoice(value as string)}
>
<SelectTrigger id="voice">
<SelectValue />
</SelectTrigger>
<SelectContent align="end">
<SelectGroup>
{voices.map((voice) => (
<SelectItem
key={voice.value}
value={voice.value}
>
{voice.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
</FieldGroup>
</FieldSet>
</TabsContent>
<TabsContent value="notifications">
<FieldGroup>
<FieldSet>
<FieldLabel>Responses</FieldLabel>
<FieldDescription>
Get notified when ChatGPT responds to requests that take
time, like research or image generation.
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
<Field orientation="horizontal">
<Checkbox id="push" defaultChecked disabled />
<FieldLabel htmlFor="push" className="font-normal">
Push notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<FieldSeparator />
<FieldSet>
<FieldLabel>Tasks</FieldLabel>
<FieldDescription>
Get notified when tasks you&apos;ve created have
updates. <a href="#">Manage tasks</a>
</FieldDescription>
<FieldGroup data-slot="checkbox-group">
<Field orientation="horizontal">
<Checkbox id="push-tasks" />
<FieldLabel
htmlFor="push-tasks"
className="font-normal"
>
Push notifications
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="email-tasks" />
<FieldLabel
htmlFor="email-tasks"
className="font-normal"
>
Email notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
</FieldGroup>
</TabsContent>
<TabsContent value="personalization">
<FieldGroup>
<Field orientation="responsive">
<FieldLabel htmlFor="nickname">Nickname</FieldLabel>
<InputGroup>
<InputGroupInput
id="nickname"
placeholder="Broski"
className="@md/field-group:max-w-[200px]"
/>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={<InputGroupButton size="icon-xs" />}
>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</TooltipTrigger>
<TooltipContent className="flex items-center gap-2">
Used to identify you in the chat. <Kbd>N</Kbd>
</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
</Field>
<FieldSeparator />
<Field
orientation="responsive"
className="@md/field-group:flex-col @2xl/field-group:flex-row"
>
<FieldContent>
<FieldLabel htmlFor="about">More about you</FieldLabel>
<FieldDescription>
Tell us more about yourself. This will be used to help
us personalize your experience.
</FieldDescription>
</FieldContent>
<Textarea
id="about"
placeholder="I'm a software engineer..."
className="min-h-[120px] @md/field-group:min-w-full @2xl/field-group:min-w-[300px]"
/>
</Field>
<FieldSeparator />
<FieldLabel>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="customization">
Enable customizations
</FieldLabel>
<FieldDescription>
Enable customizations to make ChatGPT more
personalized.
</FieldDescription>
</FieldContent>
<Switch id="customization" defaultChecked />
</Field>
</FieldLabel>
</FieldGroup>
</TabsContent>
<TabsContent value="security">
<FieldGroup>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="2fa">
Multi-factor authentication
</FieldLabel>
<FieldDescription>
Enable multi-factor authentication to secure your
account. If you do not have a two-factor
authentication device, you can use a one-time code
sent to your email.
</FieldDescription>
</FieldContent>
<Switch id="2fa" />
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Log out</FieldTitle>
<FieldDescription>
Log out of your account on this device.
</FieldDescription>
</FieldContent>
<Button variant="outline" size="sm">
Log Out
</Button>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Log out of all devices</FieldTitle>
<FieldDescription>
This will log you out of all devices, including the
current session. It may take up to 30 minutes for the
changes to take effect.
</FieldDescription>
</FieldContent>
<Button variant="outline" size="sm">
Log Out All
</Button>
</Field>
</FieldGroup>
</TabsContent>
</div>
</Tabs>
</div>
</DialogContent>
</Dialog>
</Example>
)
}

View File

@@ -0,0 +1,484 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/bases/base/ui/drawer"
const PARAGRAPHS = [
"Your changes are saved automatically as you type.",
"Delivery usually takes three to five business days, depending on your location and the shipping method you selected at checkout.",
"We use your email address for account notifications and order updates. You can change this anytime from your profile settings.",
"Two-factor authentication adds an extra layer of security to your account. When enabled, you'll need to enter a code from your authenticator app in addition to your password. We recommend enabling it if you store payment methods or sensitive information.",
"By continuing, you agree to our Terms of Service and Privacy Policy. We collect usage data to improve the product, personalize your experience, and troubleshoot issues. You can export or delete your data at any time from the account settings page. Third-party integrations may have their own policies, and you should review them before connecting external services.",
"Refunds are processed within five to ten business days after we receive your return. Items must be unused and in their original packaging to qualify. Shipping costs are non-refundable unless the return is due to our error or a defective product. Once approved, the refund is issued to your original payment method; credit card refunds may take an additional billing cycle to appear on your statement. If you paid with store credit, the balance is restored to your account immediately. Contact support if you haven't received your refund after two weeks.",
"Last updated March 12, 2026.",
"Upgrade to Pro for unlimited projects, priority support, and advanced analytics. Cancel anytime from billing settings.",
"You haven't verified your email yet. Check your inbox for a confirmation link—we sent it when you signed up. The link expires after 24 hours, but you can request a new one below.",
"API requests are rate-limited to 1,000 calls per hour on the free plan. Exceeding the limit returns a 429 response with a Retry-After header. Upgrade to a paid plan for higher limits and dedicated support. Webhook deliveries are retried up to three times with exponential backoff if your endpoint returns a non-2xx status.",
"No payment method on file.",
"Our design team rebuilt the checkout flow last quarter after interviews with forty-two customers. The biggest friction point was surprise fees at the final step, so we moved shipping and tax estimates earlier in the process. Early tests show a twelve percent drop in cart abandonment. We're still rolling out the update region by region, and you may see the old flow until your account is migrated.",
]
export default function DrawerExample() {
return (
<ExampleWrapper>
<DrawerDemo />
<DrawerSwipeHandleExample />
<DrawerPosition />
<DrawerCustomWidthAndHeight />
<DrawerScrollable />
<DrawerSnapPoints />
<DrawerNested />
<DrawerNonModal />
</ExampleWrapper>
)
}
function DrawerDemo() {
return (
<Example title="Demo">
<div className="flex flex-wrap gap-2">
<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>
Open Drawer
</DrawerTrigger>
<DrawerContent>
<div className="p-4">
<div className="h-80 w-full bg-muted" />
</div>
</DrawerContent>
</Drawer>
<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>
Header
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Edit profile</DrawerTitle>
<DrawerDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</DrawerDescription>
</DrawerHeader>
<div className="p-4">
<div className="h-80 w-full bg-muted" />
</div>
</DrawerContent>
</Drawer>
<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>
Footer
</DrawerTrigger>
<DrawerContent>
<div className="p-4">
<div className="h-80 w-full bg-muted" />
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline" />}>
Cancel
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>
Header and Footer
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Edit profile</DrawerTitle>
<DrawerDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</DrawerDescription>
</DrawerHeader>
<div className="p-4">
<div className="h-80 w-full bg-muted" />
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline" />}>
Cancel
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>
Edge to Edge
</DrawerTrigger>
<DrawerContent>
<div className="h-80 w-full bg-blue-200" />
</DrawerContent>
</Drawer>
</div>
</Example>
)
}
function DrawerSwipeHandleExample() {
return (
<Example title="Swipe Handle">
<div className="flex flex-wrap gap-2">
{DRAWER_SIDES.map((side) => (
<Drawer key={side} swipeDirection={side} showSwipeHandle>
<DrawerTrigger
render={<Button variant="outline" className="capitalize" />}
>
{side}
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle className="capitalize">Drawer</DrawerTitle>
<DrawerDescription>
Drawer with a swipe handle.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 p-4">
<div className="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full" />
</div>
</DrawerContent>
</Drawer>
))}
</div>
</Example>
)
}
function DrawerCustomWidthAndHeight() {
return (
<Example title="Custom Width and Height">
<div className="flex flex-wrap gap-2">
<Drawer swipeDirection="down">
<DrawerTrigger render={<Button variant="outline" />}>
Down
</DrawerTrigger>
<DrawerContent className="data-[swipe-direction=down]:h-64">
<DrawerHeader>
<DrawerTitle>Down drawer</DrawerTitle>
<DrawerDescription>
Drawer with a custom height.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 scroll-fade scrollbar-thin overflow-y-auto p-4">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
{PARAGRAPHS[index % PARAGRAPHS.length]}
</p>
))}
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer swipeDirection="up">
<DrawerTrigger render={<Button variant="outline" />}>
Up
</DrawerTrigger>
<DrawerContent className="data-[swipe-direction=up]:h-[50vh]">
<DrawerHeader>
<DrawerTitle>Up drawer</DrawerTitle>
<DrawerDescription>
Drawer with a custom height.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 scroll-fade overflow-y-auto p-4">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
{PARAGRAPHS[index % PARAGRAPHS.length]}
</p>
))}
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer swipeDirection="left">
<DrawerTrigger render={<Button variant="outline" />}>
Left
</DrawerTrigger>
<DrawerContent className="data-[swipe-direction=left]:w-xl">
<DrawerHeader>
<DrawerTitle>Left drawer</DrawerTitle>
<DrawerDescription>Drawer with a custom width.</DrawerDescription>
</DrawerHeader>
<div className="flex-1 scroll-fade overflow-y-auto p-4">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
{PARAGRAPHS[index % PARAGRAPHS.length]}
</p>
))}
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Drawer swipeDirection="right">
<DrawerTrigger render={<Button variant="outline" />}>
Right
</DrawerTrigger>
<DrawerContent className="data-[swipe-direction=right]:w-xs">
<DrawerHeader>
<DrawerTitle>Right drawer</DrawerTitle>
<DrawerDescription>Drawer with a custom width.</DrawerDescription>
</DrawerHeader>
<div className="flex-1 scroll-fade overflow-y-auto p-4">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
{PARAGRAPHS[index % PARAGRAPHS.length]}
</p>
))}
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</div>
</Example>
)
}
const DRAWER_SIDES = ["up", "right", "down", "left"] as const
function DrawerPosition() {
return (
<Example title="Position">
<div className="flex flex-wrap gap-2">
{DRAWER_SIDES.map((side) => (
<Drawer key={side} swipeDirection={side}>
<DrawerTrigger
render={<Button variant="outline" className="capitalize" />}
>
{side}
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>
Set your daily activity goal.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 p-4">
<div className="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full" />
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline" />}>
Cancel
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
))}
</div>
</Example>
)
}
function DrawerScrollable() {
return (
<Example title="Scrollable Content">
<div className="flex flex-wrap gap-2">
{DRAWER_SIDES.map((side) => (
<Drawer key={side} swipeDirection={side}>
<DrawerTrigger
render={<Button variant="outline" className="capitalize" />}
>
{side}
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>
Set your daily activity goal.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 scroll-fade overflow-y-auto p-4">
{Array.from({ length: 20 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
{PARAGRAPHS[index % PARAGRAPHS.length]}
</p>
))}
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline" />}>
Cancel
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
))}
</div>
</Example>
)
}
const SNAP_POINTS = ["31rem", 1]
function DrawerSnapPoints() {
return (
<Example title="Snap Points">
<Drawer snapPoints={SNAP_POINTS} showSwipeHandle>
<DrawerTrigger render={<Button variant="outline" />}>
Open Snap Drawer
</DrawerTrigger>
<DrawerContent className="max-h-[calc(100dvh-1rem)]">
<DrawerHeader>
<DrawerTitle>Snap points</DrawerTitle>
<DrawerDescription>
Drag the drawer to snap between a compact peek and a near
full-height view.
</DrawerDescription>
</DrawerHeader>
<div className="grid flex-1 scroll-fade gap-3 overflow-y-auto p-4">
{Array.from({ length: 16 }).map((_, index) => (
<div key={index} className="h-12 bg-muted" />
))}
</div>
</DrawerContent>
</Drawer>
</Example>
)
}
function DrawerNested() {
return (
<Example title="Nested">
<div className="flex flex-wrap gap-2">
{DRAWER_SIDES.map((side) => (
<Drawer key={side} swipeDirection={side} showSwipeHandle>
<DrawerTrigger
render={<Button variant="outline" className="capitalize" />}
>
{side}
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle className="capitalize">{side} drawer</DrawerTitle>
<DrawerDescription>
Open another drawer from the same direction.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 p-4">
<div className="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full" />
</div>
<DrawerFooter>
<Drawer swipeDirection={side}>
<DrawerTrigger render={<Button />}>
Open nested drawer
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Nested drawer</DrawerTitle>
<DrawerDescription>
The parent drawer stays mounted behind this one.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 p-4">
<div className="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full" />
</div>
<DrawerFooter>
<Drawer swipeDirection={side}>
<DrawerTrigger render={<Button />}>
Open third drawer
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Third drawer</DrawerTitle>
<DrawerDescription>
This is the frontmost drawer in the stack.
</DrawerDescription>
</DrawerHeader>
<div className="flex-1 p-4">
<div className="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full" />
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
))}
</div>
</Example>
)
}
function DrawerNonModal() {
return (
<Example title="Non Modal">
<Drawer modal={false} disablePointerDismissal swipeDirection="right">
<DrawerTrigger render={<Button variant="outline" />}>
Non Modal
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Non Modal Drawer</DrawerTitle>
</DrawerHeader>
<div className="flex-1 p-4">
<div className="bg-muted group-data-[swipe-axis=x]/drawer-popup:size-full group-data-[swipe-axis=y]/drawer-popup:h-80 group-data-[swipe-axis=y]/drawer-popup:w-full" />
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</Example>
)
}

View File

@@ -0,0 +1,993 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/base/ui/avatar"
import { Button } from "@/registry/bases/base/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function DropdownMenuExample() {
return (
<ExampleWrapper>
<DropdownMenuBasic />
<DropdownMenuComplex />
<DropdownMenuSides />
<DropdownMenuWithIcons />
<DropdownMenuWithShortcuts />
<DropdownMenuWithSubmenu />
<DropdownMenuWithCheckboxes />
<DropdownMenuWithCheckboxesIcons />
<DropdownMenuWithRadio />
<DropdownMenuWithRadioIcons />
<DropdownMenuWithDestructive />
<DropdownMenuWithAvatar />
<DropdownMenuInDialog />
<DropdownMenuWithInset />
</ExampleWrapper>
)
}
function DropdownMenuBasic() {
return (
<Example title="Basic">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>GitHub</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuItem disabled>API</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuSides() {
return (
<Example title="Sides" containerClassName="col-span-2">
<div className="flex flex-wrap justify-center gap-2">
{(
[
"inline-start",
"left",
"top",
"bottom",
"right",
"inline-end",
] as const
).map((side) => (
<DropdownMenu key={side}>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit capitalize" />}
>
{side.replace("-", " ")}
</DropdownMenuTrigger>
<DropdownMenuContent side={side}>
<DropdownMenuGroup>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
))}
</div>
</Example>
)
}
function DropdownMenuWithIcons() {
return (
<Example title="With Icons">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
Profile
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
Settings
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithShortcuts() {
return (
<Example title="With Shortcuts">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Keyboard shortcuts
<DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithSubmenu() {
return (
<Example title="With Submenu">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Message</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>More...</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuItem>
New Team
<DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithCheckboxes() {
const [showStatusBar, setShowStatusBar] = React.useState(true)
const [showActivityBar, setShowActivityBar] = React.useState(false)
const [showPanel, setShowPanel] = React.useState(false)
return (
<Example title="With Checkboxes">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Checkboxes
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-40">
<DropdownMenuGroup>
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={showStatusBar}
onCheckedChange={setShowStatusBar}
>
<IconPlaceholder
lucide="LayoutIcon"
tabler="IconLayout"
hugeicons="LayoutIcon"
phosphor="LayoutIcon"
remixicon="RiLayoutLine"
/>
Status Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showActivityBar}
onCheckedChange={setShowActivityBar}
disabled
>
<IconPlaceholder
lucide="ActivityIcon"
tabler="IconActivity"
hugeicons="ActivityIcon"
phosphor="ActivityIcon"
remixicon="RiPulseLine"
/>
Activity Bar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showPanel}
onCheckedChange={setShowPanel}
>
<IconPlaceholder
lucide="PanelLeftIcon"
tabler="IconLayoutSidebar"
hugeicons="LayoutLeftIcon"
phosphor="SidebarIcon"
remixicon="RiSideBarLine"
/>
Panel
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithRadio() {
const [position, setPosition] = React.useState("bottom")
return (
<Example title="With Radio Group">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Radio Group
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={position}
onValueChange={setPosition}
>
<DropdownMenuRadioItem value="top">
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUp01Icon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
Top
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bottom">
<IconPlaceholder
lucide="ArrowDownIcon"
tabler="IconArrowDown"
hugeicons="ArrowDown01Icon"
phosphor="ArrowDownIcon"
remixicon="RiArrowDownLine"
/>
Bottom
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="right" disabled>
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
Right
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithCheckboxesIcons() {
const [notifications, setNotifications] = React.useState({
email: true,
sms: false,
push: true,
})
return (
<Example title="Checkboxes with Icons">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Notifications
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>Notification Preferences</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={notifications.email}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, email: checked === true })
}
>
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="MailIcon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
Email notifications
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={notifications.sms}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, sms: checked === true })
}
>
<IconPlaceholder
lucide="MessageSquareIcon"
tabler="IconMessage"
hugeicons="MessageIcon"
phosphor="ChatCircleIcon"
remixicon="RiChat1Line"
/>
SMS notifications
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={notifications.push}
onCheckedChange={(checked) =>
setNotifications({ ...notifications, push: checked === true })
}
>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
Push notifications
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithRadioIcons() {
const [paymentMethod, setPaymentMethod] = React.useState("card")
return (
<Example title="Radio with Icons">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Payment Method
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>Select Payment Method</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={paymentMethod}
onValueChange={setPaymentMethod}
>
<DropdownMenuRadioItem value="card">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Credit Card
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="paypal">
<IconPlaceholder
lucide="WalletIcon"
tabler="IconWallet"
hugeicons="WalletIcon"
phosphor="WalletIcon"
remixicon="RiWalletLine"
/>
PayPal
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="bank">
<IconPlaceholder
lucide="Building2Icon"
tabler="IconBuildingBank"
hugeicons="BankIcon"
phosphor="BankIcon"
remixicon="RiBankLine"
/>
Bank Transfer
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithDestructive() {
return (
<Example title="With Destructive Items">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Actions
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<IconPlaceholder
lucide="PencilIcon"
tabler="IconPencil"
hugeicons="EditIcon"
phosphor="PencilIcon"
remixicon="RiPencilLine"
/>
Edit
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShareIcon"
tabler="IconShare"
hugeicons="ShareIcon"
phosphor="ShareIcon"
remixicon="RiShareLine"
/>
Share
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconPlaceholder
lucide="ArchiveIcon"
tabler="IconArchive"
hugeicons="Archive02Icon"
phosphor="ArchiveIcon"
remixicon="RiArchiveLine"
/>
Archive
</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuWithAvatar() {
const menuContent = (
<>
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="BadgeCheckIcon"
tabler="IconRosetteDiscountCheck"
hugeicons="CheckmarkBadgeIcon"
phosphor="CheckCircleIcon"
remixicon="RiCheckboxCircleLine"
/>
Account
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign Out
</DropdownMenuItem>
</>
)
return (
<Example title="With Avatar">
<div className="flex items-center justify-between gap-4">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
className="h-12 justify-start px-2 md:max-w-[200px] style-sera:font-normal style-sera:tracking-normal style-sera:normal-case"
/>
}
>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="Shadcn" />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">shadcn</span>
<span className="truncate text-xs text-muted-foreground">
shadcn@example.com
</span>
</div>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretUpDownIcon"
remixicon="RiArrowUpDownLine"
className="ml-auto text-muted-foreground"
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-(--anchor-width) min-w-56">
{menuContent}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="rounded-full" />
}
>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="shadcn" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="top">
{menuContent}
</DropdownMenuContent>
</DropdownMenu>
</div>
</Example>
)
}
function DropdownMenuInDialog() {
return (
<Example title="In Dialog">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dropdown Menu Example</DialogTitle>
<DialogDescription>
Click the button below to see the dropdown menu.
</DialogDescription>
</DialogHeader>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open Menu
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ClipboardPasteIcon"
tabler="IconClipboard"
hugeicons="ClipboardIcon"
phosphor="ClipboardIcon"
remixicon="RiClipboardLine"
/>
Paste
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger>More Options</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Save Page...</DropdownMenuItem>
<DropdownMenuItem>Create Shortcut...</DropdownMenuItem>
<DropdownMenuItem>Name Window...</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Developer Tools</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</DialogContent>
</Dialog>
</Example>
)
}
function DropdownMenuWithInset() {
const [showBookmarks, setShowBookmarks] = React.useState(true)
const [showUrls, setShowUrls] = React.useState(false)
const [theme, setTheme] = React.useState("system")
return (
<Example title="With Inset">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open
</DropdownMenuTrigger>
<DropdownMenuContent className="w-44">
<DropdownMenuGroup>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</DropdownMenuItem>
<DropdownMenuItem inset>Paste</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel inset>Appearance</DropdownMenuLabel>
<DropdownMenuCheckboxItem
inset
checked={showBookmarks}
onCheckedChange={setShowBookmarks}
>
Bookmarks
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
inset
checked={showUrls}
onCheckedChange={setShowUrls}
>
Full URLs
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel inset>Theme</DropdownMenuLabel>
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
<DropdownMenuRadioItem inset value="light">
Light
</DropdownMenuRadioItem>
<DropdownMenuRadioItem inset value="dark">
Dark
</DropdownMenuRadioItem>
<DropdownMenuRadioItem inset value="system">
System
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger inset>More Options</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuItem>Save Page...</DropdownMenuItem>
<DropdownMenuItem>Create Shortcut...</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}
function DropdownMenuComplex() {
const [showSidebar, setShowSidebar] = React.useState(true)
const [showStatusBar, setShowStatusBar] = React.useState(false)
return (
<Example title="Complex">
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="outline" className="w-fit" />}
>
Complex Menu
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuGroup>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
Profile
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Billing
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
Settings
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>View</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={showSidebar}
onCheckedChange={setShowSidebar}
>
<IconPlaceholder
lucide="PanelLeftIcon"
tabler="IconLayoutSidebar"
hugeicons="LayoutLeftIcon"
phosphor="SidebarIcon"
remixicon="RiSideBarLine"
/>
Sidebar
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={showStatusBar}
onCheckedChange={setShowStatusBar}
>
<IconPlaceholder
lucide="LayoutIcon"
tabler="IconLayout"
hugeicons="LayoutIcon"
phosphor="LayoutIcon"
remixicon="RiLayoutLine"
/>
Status Bar
</DropdownMenuCheckboxItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="UsersIcon"
tabler="IconUsers"
hugeicons="UserGroupIcon"
phosphor="UsersIcon"
remixicon="RiTeamLine"
/>
Invite Users
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="MailIcon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
Email
</DropdownMenuItem>
<DropdownMenuItem>
<IconPlaceholder
lucide="MessageSquareIcon"
tabler="IconMessage"
hugeicons="MessageIcon"
phosphor="ChatCircleIcon"
remixicon="RiChat1Line"
/>
Message
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="PlusCircleIcon"
tabler="IconCirclePlus"
hugeicons="AddCircleIcon"
phosphor="PlusCircleIcon"
remixicon="RiAddCircleLine"
/>
More...
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="HelpCircleIcon"
tabler="IconHelpCircle"
hugeicons="HelpCircleIcon"
phosphor="QuestionIcon"
remixicon="RiQuestionLine"
/>
Support
</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign Out
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</Example>
)
}

View File

@@ -0,0 +1,263 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/base/ui/empty"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/base/ui/input-group"
import { Kbd } from "@/registry/bases/base/ui/kbd"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function EmptyExample() {
return (
<ExampleWrapper>
<EmptyBasic />
<EmptyWithMutedBackground />
<EmptyWithBorder />
<EmptyWithIcon />
<EmptyWithMutedBackgroundAlt />
<EmptyInCard />
</ExampleWrapper>
)
}
function EmptyBasic() {
return (
<Example title="Basic">
<Empty>
<EmptyHeader>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven&apos;t created any projects yet. Get started by creating
your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button render={<a href="#" />} nativeButton={false}>
Create project
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button
variant="link"
render={<a href="#" />}
className="text-muted-foreground"
nativeButton={false}
>
Learn more{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</Button>
</EmptyContent>
</Empty>
</Example>
)
}
function EmptyWithMutedBackground() {
return (
<Example title="With Muted Background">
<Empty className="bg-muted">
<EmptyHeader>
<EmptyTitle>No results found</EmptyTitle>
<EmptyDescription>
No results found for your search. Try adjusting your search terms.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Try again</Button>
<Button
variant="link"
render={<a href="#" />}
className="text-muted-foreground"
nativeButton={false}
>
Learn more{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</Button>
</EmptyContent>
</Empty>
</Example>
)
}
function EmptyWithBorder() {
return (
<Example title="With Border">
<Empty className="border">
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try searching
for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
</Example>
)
}
function EmptyWithIcon() {
return (
<Example title="With Icon">
<Empty className="border">
<EmptyHeader>
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="Folder01Icon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
</EmptyMedia>
<EmptyTitle>Nothing to see here</EmptyTitle>
<EmptyDescription>
No posts have been created yet. Get started by{" "}
<a href="#">creating your first post</a>.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
New Post
</Button>
</EmptyContent>
</Empty>
</Example>
)
}
function EmptyWithMutedBackgroundAlt() {
return (
<Example title="With Muted Background Alt">
<Empty className="bg-muted/50">
<EmptyHeader>
<EmptyTitle>404 - Not Found</EmptyTitle>
<EmptyDescription>
The page you&apos;re looking for doesn&apos;t exist. Try searching
for what you need below.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<InputGroup className="w-3/4">
<InputGroupInput placeholder="Try searching for pages..." />
<InputGroupAddon>
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>/</Kbd>
</InputGroupAddon>
</InputGroup>
<EmptyDescription>
Need help? <a href="#">Contact support</a>
</EmptyDescription>
</EmptyContent>
</Empty>
</Example>
)
}
function EmptyInCard() {
return (
<Example title="In Card">
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="Folder01Icon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven&apos;t created any projects yet. Get started by creating
your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button render={<a href="#" />} nativeButton={false}>
Create project
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button
variant="link"
render={<a href="#" />}
className="text-muted-foreground"
nativeButton={false}
>
Learn more{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</Button>
</EmptyContent>
</Empty>
</Example>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/registry/bases/base/ui/hover-card"
export default function HoverCardExample() {
return (
<ExampleWrapper>
<HoverCardSides />
<HoverCardInDialog />
</ExampleWrapper>
)
}
const HOVER_CARD_SIDES = [
"inline-start",
"left",
"top",
"bottom",
"right",
"inline-end",
] as const
function HoverCardSides() {
return (
<Example title="Sides" containerClassName="col-span-2">
<div className="flex flex-wrap items-center justify-center gap-2">
{HOVER_CARD_SIDES.map((side) => (
<HoverCard key={side}>
<HoverCardTrigger
delay={100}
closeDelay={100}
render={<Button variant="outline" className="capitalize" />}
>
{side.replace("-", " ")}
</HoverCardTrigger>
<HoverCardContent side={side}>
<div className="flex flex-col style-vega:gap-2 style-nova:gap-1.5 style-lyra:gap-1 style-maia:gap-2 style-mira:gap-1 style-luma:gap-2">
<h4 className="font-medium">Hover Card</h4>
<p>
This hover card appears on the {side.replace("-", " ")} side
of the trigger.
</p>
</div>
</HoverCardContent>
</HoverCard>
))}
</div>
</Example>
)
}
function HoverCardInDialog() {
return (
<Example title="In Dialog">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Hover Card Example</DialogTitle>
<DialogDescription>
Hover over the button below to see the hover card.
</DialogDescription>
</DialogHeader>
<HoverCard>
<HoverCardTrigger
delay={100}
closeDelay={100}
render={<Button variant="outline" className="w-fit" />}
>
Hover me
</HoverCardTrigger>
<HoverCardContent>
<div className="flex flex-col style-vega:gap-2 style-nova:gap-1.5 style-lyra:gap-1 style-maia:gap-2 style-mira:gap-1 style-luma:gap-2">
<h4 className="font-medium">Hover Card</h4>
<p>
This hover card appears inside a dialog. Hover over the button
to see it.
</p>
</div>
</HoverCardContent>
</HoverCard>
</DialogContent>
</Dialog>
</Example>
)
}

View File

@@ -0,0 +1,268 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/base/ui/native-select"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
export default function InputExample() {
return (
<ExampleWrapper>
<InputBasic />
<InputInvalid />
<InputWithLabel />
<InputWithDescription />
<InputDisabled />
<InputTypes />
<InputWithSelect />
<InputWithButton />
<InputWithNativeSelect />
<InputForm />
</ExampleWrapper>
)
}
function InputBasic() {
return (
<Example title="Basic">
<Input type="email" placeholder="Email" />
</Example>
)
}
function InputInvalid() {
return (
<Example title="Invalid">
<Input type="text" placeholder="Error" aria-invalid="true" />
</Example>
)
}
function InputWithLabel() {
return (
<Example title="With Label">
<Field>
<FieldLabel htmlFor="input-demo-email">Email</FieldLabel>
<Input
id="input-demo-email"
type="email"
placeholder="name@example.com"
/>
</Field>
</Example>
)
}
function InputWithDescription() {
return (
<Example title="With Description">
<Field>
<FieldLabel htmlFor="input-demo-username">Username</FieldLabel>
<Input
id="input-demo-username"
type="text"
placeholder="Enter your username"
/>
<FieldDescription>
Choose a unique username for your account.
</FieldDescription>
</Field>
</Example>
)
}
function InputDisabled() {
return (
<Example title="Disabled">
<Field>
<FieldLabel htmlFor="input-demo-disabled">Email</FieldLabel>
<Input
id="input-demo-disabled"
type="email"
placeholder="Email"
disabled
/>
</Field>
</Example>
)
}
function InputTypes() {
return (
<Example title="Input Types">
<div className="flex w-full flex-col gap-6">
<Field>
<FieldLabel htmlFor="input-demo-password">Password</FieldLabel>
<Input
id="input-demo-password"
type="password"
placeholder="Password"
/>
</Field>
<Field>
<FieldLabel htmlFor="input-demo-tel">Phone</FieldLabel>
<Input
id="input-demo-tel"
type="tel"
placeholder="+1 (555) 123-4567"
/>
</Field>
<Field>
<FieldLabel htmlFor="input-demo-url">URL</FieldLabel>
<Input
id="input-demo-url"
type="url"
placeholder="https://example.com"
/>
</Field>
<Field>
<FieldLabel htmlFor="input-demo-search">Search</FieldLabel>
<Input id="input-demo-search" type="search" placeholder="Search" />
</Field>
<Field>
<FieldLabel htmlFor="input-demo-number">Number</FieldLabel>
<Input id="input-demo-number" type="number" placeholder="123" />
</Field>
<Field>
<FieldLabel htmlFor="input-demo-date">Date</FieldLabel>
<Input id="input-demo-date" type="date" />
</Field>
<Field>
<FieldLabel htmlFor="input-demo-time">Time</FieldLabel>
<Input id="input-demo-time" type="time" />
</Field>
<Field>
<FieldLabel htmlFor="input-demo-file">File</FieldLabel>
<Input id="input-demo-file" type="file" />
</Field>
</div>
</Example>
)
}
function InputWithSelect() {
return (
<Example title="With Select">
<div className="flex w-full gap-2">
<Input type="text" placeholder="Enter amount" className="flex-1" />
<Select defaultValue="usd">
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="usd">USD</SelectItem>
<SelectItem value="eur">EUR</SelectItem>
<SelectItem value="gbp">GBP</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</Example>
)
}
function InputWithButton() {
return (
<Example title="With Button">
<div className="flex w-full gap-2">
<Input type="search" placeholder="Search..." className="flex-1" />
<Button>Search</Button>
</div>
</Example>
)
}
function InputWithNativeSelect() {
return (
<Example title="With Native Select">
<div className="flex w-full gap-2">
<Input type="tel" placeholder="(555) 123-4567" className="flex-1" />
<NativeSelect defaultValue="+1">
<NativeSelectOption value="+1">+1</NativeSelectOption>
<NativeSelectOption value="+44">+44</NativeSelectOption>
<NativeSelectOption value="+46">+46</NativeSelectOption>
</NativeSelect>
</div>
</Example>
)
}
function InputForm() {
return (
<Example title="Form">
<form className="w-full">
<FieldGroup>
<Field>
<FieldLabel htmlFor="form-name">Name</FieldLabel>
<Input id="form-name" type="text" placeholder="John Doe" />
</Field>
<Field>
<FieldLabel htmlFor="form-email">Email</FieldLabel>
<Input
id="form-email"
type="email"
placeholder="john@example.com"
/>
<FieldDescription>
We&apos;ll never share your email with anyone.
</FieldDescription>
</Field>
<div className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="form-phone">Phone</FieldLabel>
<Input
id="form-phone"
type="tel"
placeholder="+1 (555) 123-4567"
/>
</Field>
<Field>
<FieldLabel htmlFor="form-country">Country</FieldLabel>
<Select defaultValue="us">
<SelectTrigger id="form-country">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="uk">United Kingdom</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
<Field>
<FieldLabel htmlFor="form-address">Address</FieldLabel>
<Input id="form-address" type="text" placeholder="123 Main St" />
</Field>
<Field orientation="horizontal">
<Button type="button" variant="outline">
Cancel
</Button>
<Button type="submit">Submit</Button>
</Field>
</FieldGroup>
</form>
</Example>
)
}

View File

@@ -0,0 +1,911 @@
"use client"
import { useState } from "react"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
ButtonGroup,
ButtonGroupText,
} from "@/registry/bases/base/ui/button-group"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/registry/bases/base/ui/input-group"
import { Kbd, KbdGroup } from "@/registry/bases/base/ui/kbd"
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/registry/bases/base/ui/popover"
import { Spinner } from "@/registry/bases/base/ui/spinner"
import { Textarea } from "@/registry/bases/base/ui/textarea"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/base/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function InputGroupExample() {
const [country, setCountry] = useState("+1")
return (
<ExampleWrapper className="min-w-0">
<InputGroupBasic />
<InputGroupWithAddons />
<InputGroupWithButtons />
<InputGroupWithTooltip country={country} setCountry={setCountry} />
<InputGroupWithKbd />
<InputGroupInCard />
<InputGroupTextareaExamples />
</ExampleWrapper>
)
}
function InputGroupBasic() {
return (
<Example title="Basic">
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-default-01">
Default (No Input Group)
</FieldLabel>
<Input placeholder="Placeholder" id="input-default-01" />
</Field>
<Field>
<FieldLabel htmlFor="input-group-02">Input Group</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-02" placeholder="Placeholder" />
</InputGroup>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="input-disabled-03">Disabled</FieldLabel>
<InputGroup>
<InputGroupInput
id="input-disabled-03"
placeholder="This field is disabled"
disabled
/>
</InputGroup>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="input-invalid-04">Invalid</FieldLabel>
<InputGroup>
<InputGroupInput
id="input-invalid-04"
placeholder="This field is invalid"
aria-invalid="true"
/>
</InputGroup>
</Field>
</FieldGroup>
</Example>
)
}
function InputGroupWithAddons() {
return (
<Example title="With Addons">
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-icon-left-05">
Addon (inline-start)
</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-left-05" />
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
className="text-muted-foreground"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-right-07">
Addon (inline-end)
</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-right-07" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="EyeOffIcon"
tabler="IconEyeClosed"
hugeicons="ViewOffIcon"
phosphor="EyeSlashIcon"
remixicon="RiEyeOffLine"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-both-09">
Addon (inline-start and inline-end)
</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-both-09" />
<InputGroupAddon>
<IconPlaceholder
lucide="MicIcon"
tabler="IconMicrophone"
hugeicons="VoiceIcon"
phosphor="MicrophoneIcon"
remixicon="RiMicLine"
className="text-muted-foreground"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="RadioIcon"
tabler="IconPlayerRecordFilled"
hugeicons="RecordIcon"
phosphor="RecordIcon"
remixicon="RiRecordCircleLine"
className="animate-pulse text-red-500"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-addon-20">Addon (block-start)</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="input-addon-20" />
<InputGroupAddon align="block-start">
<InputGroupText>First Name</InputGroupText>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
className="ml-auto text-muted-foreground"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-addon-21">Addon (block-end)</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="input-addon-21" />
<InputGroupAddon align="block-end">
<InputGroupText>20/240 characters</InputGroupText>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
className="ml-auto text-muted-foreground"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-icon-both-10">Multiple Icons</FieldLabel>
<InputGroup>
<InputGroupInput id="input-icon-both-10" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="StarIcon"
tabler="IconStar"
hugeicons="StarIcon"
phosphor="StarIcon"
remixicon="RiStarLine"
/>
<InputGroupButton
size="icon-xs"
onClick={() => toast("Copied to clipboard")}
>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon>
<IconPlaceholder
lucide="RadioIcon"
tabler="IconPlayerRecordFilled"
hugeicons="RecordIcon"
phosphor="RecordIcon"
remixicon="RiRecordCircleLine"
className="animate-pulse text-red-500"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-description-10">Description</FieldLabel>
<InputGroup>
<InputGroupInput id="input-description-10" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-label-10">Label</FieldLabel>
<InputGroup>
<InputGroupAddon>
<FieldLabel htmlFor="input-label-10">Label</FieldLabel>
</InputGroupAddon>
<InputGroupInput id="input-label-10" />
</InputGroup>
<InputGroup>
<InputGroupInput id="input-optional-12" aria-label="Optional" />
<InputGroupAddon align="inline-end">
<InputGroupText>(optional)</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</Example>
)
}
function InputGroupWithButtons() {
return (
<Example title="With Buttons">
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-button-13">Button</FieldLabel>
<InputGroup>
<InputGroupInput id="input-button-13" />
<InputGroupAddon>
<InputGroupButton>Default</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-14" />
<InputGroupAddon>
<InputGroupButton variant="outline">Outline</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-15" />
<InputGroupAddon>
<InputGroupButton variant="secondary">Secondary</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-16" />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary">Button</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-17" />
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-xs">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-button-18" />
<InputGroupAddon align="inline-end">
<InputGroupButton variant="secondary" size="icon-xs">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</Example>
)
}
function InputGroupWithTooltip({
country,
setCountry,
}: {
country: string
setCountry: (value: string) => void
}) {
return (
<Example title="With Tooltip, Dropdown, Popover">
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-tooltip-20">Tooltip</FieldLabel>
<InputGroup>
<InputGroupInput id="input-tooltip-20" />
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton className="rounded-full" size="icon-xs" />
}
>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-dropdown-21">Dropdown</FieldLabel>
<InputGroup>
<InputGroupInput id="input-dropdown-21" />
<InputGroupAddon>
<DropdownMenu>
<DropdownMenuTrigger
render={
<InputGroupButton className="text-muted-foreground tabular-nums" />
}
>
{country}{" "}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDownIcon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className="min-w-16"
sideOffset={10}
alignOffset={-8}
>
<DropdownMenuItem onClick={() => setCountry("+1")}>
+1
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setCountry("+44")}>
+44
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setCountry("+46")}>
+46
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-secure-19">Popover</FieldLabel>
<InputGroup>
<Popover>
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
<InputGroupButton variant="secondary" size="icon-xs">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupButton>
</PopoverTrigger>
<PopoverContent align="start">
<PopoverHeader>
<PopoverTitle>Your connection is not secure.</PopoverTitle>
<PopoverDescription>
You should not enter any sensitive information on this site.
</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
<InputGroupAddon className="pl-1 text-muted-foreground">
https://
</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton
size="icon-xs"
onClick={() => toast("Added to favorites")}
>
<IconPlaceholder
lucide="StarIcon"
tabler="IconStar"
hugeicons="StarIcon"
phosphor="StarIcon"
remixicon="RiStarLine"
/>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="url">Button Group</FieldLabel>
<ButtonGroup>
<ButtonGroupText>https://</ButtonGroupText>
<InputGroup>
<InputGroupInput id="url" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupAddon>
</InputGroup>
<ButtonGroupText>.com</ButtonGroupText>
</ButtonGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function InputGroupWithKbd() {
return (
<Example title="With Kbd">
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-kbd-22">Input Group with Kbd</FieldLabel>
<InputGroup>
<InputGroupInput id="input-kbd-22" />
<InputGroupAddon>
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput id="input-kbd-23" />
<InputGroupAddon align="inline-end">
<Kbd>⌘K</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput
id="input-search-apps-24"
placeholder="Search for Apps..."
/>
<InputGroupAddon align="inline-end">Ask AI</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>Tab</Kbd>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput
id="input-search-type-25"
placeholder="Type to search..."
/>
<InputGroupAddon align="inline-start">
<IconPlaceholder
lucide="SparklesIcon"
tabler="IconServerSpark"
hugeicons="SparklesIcon"
phosphor="SparkleIcon"
remixicon="RiSparklingLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>C</Kbd>
</KbdGroup>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-username-26">Username</FieldLabel>
<InputGroup>
<InputGroupInput id="input-username-26" defaultValue="shadcn" />
<InputGroupAddon align="inline-end">
<div className="flex size-4 items-center justify-center rounded-full bg-green-500 dark:bg-green-800">
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="size-3 text-white"
/>
</div>
</InputGroupAddon>
</InputGroup>
<FieldDescription className="text-green-700">
This username is available.
</FieldDescription>
</Field>
<InputGroup>
<InputGroupInput
id="input-search-docs-27"
placeholder="Search documentation..."
/>
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled="true">
<InputGroupInput
id="input-search-disabled-28"
placeholder="Search documentation..."
disabled
/>
<InputGroupAddon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</InputGroupAddon>
<InputGroupAddon align="inline-end">Disabled</InputGroupAddon>
</InputGroup>
<FieldGroup className="grid grid-cols-2 gap-4">
<Field>
<FieldLabel htmlFor="input-group-11">First Name</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-11" placeholder="First Name" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="input-group-12">Last Name</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-12" placeholder="Last Name" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
<Field data-disabled="true">
<FieldLabel htmlFor="input-group-29">
Loading (&quot;data-disabled=&quot;true&quot;)
</FieldLabel>
<InputGroup>
<InputGroupInput
id="input-group-29"
disabled
defaultValue="shadcn"
/>
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function InputGroupInCard() {
return (
<Example title="In Card">
<Card className="w-full">
<CardHeader>
<CardTitle>Card with Input Group</CardTitle>
<CardDescription>This is a card with an input group.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email-input">Email Address</FieldLabel>
<InputGroup>
<InputGroupInput
id="email-input"
type="email"
placeholder="you@example.com"
/>
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="MailIcon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="website-input">Website URL</FieldLabel>
<InputGroup>
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupInput id="website-input" placeholder="example.com" />
<InputGroupAddon align="inline-end">
<IconPlaceholder
lucide="ExternalLinkIcon"
tabler="IconExternalLink"
hugeicons="LinkSquare02Icon"
phosphor="ArrowSquareOutIcon"
remixicon="RiExternalLinkLine"
/>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="feedback-textarea">
Feedback & Comments
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="feedback-textarea"
placeholder="Share your thoughts..."
className="min-h-[100px]"
/>
<InputGroupAddon align="block-end">
<InputGroupText>0/500 characters</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="justify-end gap-2">
<Button variant="outline">Cancel</Button>
<Button>Submit</Button>
</CardFooter>
</Card>
</Example>
)
}
function InputGroupTextareaExamples() {
return (
<Example title="Textarea">
<FieldGroup>
<Field>
<FieldLabel htmlFor="textarea-header-footer-12">
Default Textarea (No Input Group)
</FieldLabel>
<Textarea
id="textarea-header-footer-12"
placeholder="Enter your text here..."
/>
</Field>
<Field>
<FieldLabel htmlFor="textarea-header-footer-13">
Input Group
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-13"
placeholder="Enter your text here..."
/>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="textarea-header-footer-14">Invalid</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-14"
placeholder="Enter your text here..."
aria-invalid="true"
/>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field data-disabled="true">
<FieldLabel htmlFor="textarea-header-footer-15">Disabled</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-15"
placeholder="Enter your text here..."
disabled
/>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="prompt-31">Addon (block-start)</FieldLabel>
<InputGroup>
<InputGroupTextarea id="prompt-31" />
<InputGroupAddon align="block-start">
<InputGroupText>Ask, Search or Chat...</InputGroupText>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
className="ml-auto text-muted-foreground"
/>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="textarea-header-footer-30">
Addon (block-end)
</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-header-footer-30"
placeholder="Enter your text here..."
/>
<InputGroupAddon align="block-end">
<InputGroupText>0/280 characters</InputGroupText>
<InputGroupButton
variant="default"
size="icon-xs"
className="ml-auto rounded-full"
>
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUpIcon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="textarea-comment-31">Addon (Buttons)</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-comment-31"
placeholder="Share your thoughts..."
className="min-h-[120px]"
/>
<InputGroupAddon align="block-end">
<InputGroupButton variant="ghost" className="ml-auto" size="sm">
Cancel
</InputGroupButton>
<InputGroupButton variant="default" size="sm">
Post Comment
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel htmlFor="textarea-code-32">Code Editor</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="textarea-code-32"
placeholder="console.log('Hello, world!');"
className="min-h-[300px] py-3"
/>
<InputGroupAddon align="block-start" className="border-b">
<InputGroupText className="font-mono font-medium">
<IconPlaceholder
lucide="CodeIcon"
tabler="IconBrandJavascript"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
script.js
</InputGroupText>
<InputGroupButton size="icon-xs" className="ml-auto">
<IconPlaceholder
lucide="RefreshCwIcon"
tabler="IconRefresh"
hugeicons="RefreshIcon"
phosphor="ArrowClockwiseIcon"
remixicon="RiRefreshLine"
/>
</InputGroupButton>
<InputGroupButton size="icon-xs" variant="ghost">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</InputGroupButton>
</InputGroupAddon>
<InputGroupAddon align="block-end" className="border-t">
<InputGroupText>Line 1, Column 1</InputGroupText>
<InputGroupText className="ml-auto">JavaScript</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</FieldGroup>
</Example>
)
}

View File

@@ -0,0 +1,292 @@
"use client"
import * as React from "react"
import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/registry/bases/base/ui/input-otp"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function InputOTPExample() {
return (
<ExampleWrapper>
<InputOTPForm />
<InputOTPSimple />
<InputOTPPattern />
<InputOTPWithSeparator />
<InputOTPAlphanumeric />
<InputOTPDisabled />
<InputOTPFourDigits />
<InputOTPInvalid />
</ExampleWrapper>
)
}
function InputOTPSimple() {
return (
<Example title="Simple">
<Field>
<FieldLabel htmlFor="simple">Simple</FieldLabel>
<InputOTP id="simple" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
</Example>
)
}
function InputOTPPattern() {
return (
<Example title="Digits Only">
<Field>
<FieldLabel htmlFor="digits-only">Digits Only</FieldLabel>
<InputOTP id="digits-only" maxLength={6} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
</Example>
)
}
function InputOTPWithSeparator() {
const [value, setValue] = React.useState("123456")
return (
<Example title="With Separator">
<Field>
<FieldLabel htmlFor="with-separator">With Separator</FieldLabel>
<InputOTP
id="with-separator"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
</Example>
)
}
function InputOTPAlphanumeric() {
return (
<Example title="Alphanumeric">
<Field>
<FieldLabel htmlFor="alphanumeric">Alphanumeric</FieldLabel>
<FieldDescription>Accepts both letters and numbers.</FieldDescription>
<InputOTP
id="alphanumeric"
maxLength={6}
pattern={REGEXP_ONLY_DIGITS_AND_CHARS}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
</Example>
)
}
function InputOTPDisabled() {
return (
<Example title="Disabled">
<Field>
<FieldLabel htmlFor="disabled">Disabled</FieldLabel>
<InputOTP id="disabled" maxLength={6} disabled value="123456">
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
</Example>
)
}
function InputOTPFourDigits() {
return (
<Example title="4 Digits">
<Field>
<FieldLabel htmlFor="four-digits">4 Digits</FieldLabel>
<FieldDescription>Common pattern for PIN codes.</FieldDescription>
<InputOTP id="four-digits" maxLength={4} pattern={REGEXP_ONLY_DIGITS}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
</Field>
</Example>
)
}
function InputOTPInvalid() {
const [value, setValue] = React.useState("000000")
return (
<Example title="Invalid State">
<Field>
<FieldLabel htmlFor="invalid">Invalid State</FieldLabel>
<FieldDescription>
Example showing the invalid error state.
</FieldDescription>
<InputOTP id="invalid" maxLength={6} value={value} onChange={setValue}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
<FieldError errors={[{ message: "Invalid code. Please try again." }]} />
</Field>
</Example>
)
}
function InputOTPForm() {
return (
<Example title="Form">
<Card className="mx-auto max-w-md">
<CardHeader>
<CardTitle>Verify your login</CardTitle>
<CardDescription>
Enter the verification code we sent to your email address:{" "}
<span className="font-medium">m@example.com</span>.
</CardDescription>
</CardHeader>
<CardContent>
<form>
<Field>
<div className="flex items-center justify-between">
<FieldLabel htmlFor="otp-verification">
Verification code
</FieldLabel>
<Button variant="outline" size="xs">
<IconPlaceholder
lucide="RefreshCwIcon"
hugeicons="RefreshIcon"
tabler="IconRefresh"
phosphor="ArrowClockwiseIcon"
remixicon="RiRefreshLine"
data-icon="inline-start"
/>
Resend Code
</Button>
</div>
<InputOTP maxLength={6} id="otp-verification" required>
<InputOTPGroup className="*:data-[slot=input-otp-slot]:text-xl style-vega:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-maia:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-mira:*:data-[slot=input-otp-slot]:w-11 style-luma:*:data-[slot=input-otp-slot]:h-16 style-luma:*:data-[slot=input-otp-slot]:w-12">
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup className="*:data-[slot=input-otp-slot]:text-xl style-vega:*:data-[slot=input-otp-slot]:h-16 style-vega:*:data-[slot=input-otp-slot]:w-12 style-nova:*:data-[slot=input-otp-slot]:h-12 style-nova:*:data-[slot=input-otp-slot]:w-11 style-lyra:*:data-[slot=input-otp-slot]:h-12 style-lyra:*:data-[slot=input-otp-slot]:w-11 style-maia:*:data-[slot=input-otp-slot]:h-16 style-maia:*:data-[slot=input-otp-slot]:w-12 style-mira:*:data-[slot=input-otp-slot]:h-12 style-mira:*:data-[slot=input-otp-slot]:w-11 style-luma:*:data-[slot=input-otp-slot]:h-16 style-luma:*:data-[slot=input-otp-slot]:w-12">
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
<a href="#">I no longer have access to this email address.</a>
</FieldDescription>
</Field>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Verify
</Button>
<div className="text-sm text-muted-foreground">
Having trouble signing in?{" "}
<a
href="#"
className="underline underline-offset-4 transition-colors hover:text-primary"
>
Contact support
</a>
</div>
</CardFooter>
</Card>
</Example>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/base/ui/input-group"
import { Kbd, KbdGroup } from "@/registry/bases/base/ui/kbd"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/base/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function KbdExample() {
return (
<ExampleWrapper>
<KbdBasic />
<KbdModifierKeys />
<KbdGroupExample />
<KbdArrowKeys />
<KbdWithIcons />
<KbdWithIconsAndText />
<KbdInInputGroup />
<KbdInTooltip />
<KbdWithSamp />
</ExampleWrapper>
)
}
function KbdBasic() {
return (
<Example title="Basic">
<div className="flex items-center gap-2">
<Kbd>Ctrl</Kbd>
<Kbd>⌘K</Kbd>
<Kbd>Ctrl + B</Kbd>
</div>
</Example>
)
}
function KbdModifierKeys() {
return (
<Example title="Modifier Keys">
<div className="flex items-center gap-2">
<Kbd>⌘</Kbd>
<Kbd>C</Kbd>
</div>
</Example>
)
}
function KbdGroupExample() {
return (
<Example title="KbdGroup">
<KbdGroup>
<Kbd>Ctrl</Kbd>
<Kbd>Shift</Kbd>
<Kbd>P</Kbd>
</KbdGroup>
</Example>
)
}
function KbdArrowKeys() {
return (
<Example title="Arrow Keys">
<div className="flex items-center gap-2">
<Kbd>↑</Kbd>
<Kbd>↓</Kbd>
<Kbd>←</Kbd>
<Kbd>→</Kbd>
</div>
</Example>
)
}
function KbdWithIcons() {
return (
<Example title="With Icons">
<KbdGroup>
<Kbd>
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
</Kbd>
<Kbd>
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft01Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
/>
</Kbd>
<Kbd>
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</Kbd>
</KbdGroup>
</Example>
)
}
function KbdWithIconsAndText() {
return (
<Example title="With Icons and Text">
<KbdGroup>
<Kbd>
<IconPlaceholder
lucide="ArrowLeftIcon"
tabler="IconArrowLeft"
hugeicons="ArrowLeft01Icon"
phosphor="ArrowLeftIcon"
remixicon="RiArrowLeftLine"
/>
Left
</Kbd>
<Kbd>
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
Voice Enabled
</Kbd>
</KbdGroup>
</Example>
)
}
function KbdInInputGroup() {
return (
<Example title="InputGroup">
<InputGroup>
<InputGroupInput />
<InputGroupAddon>
<Kbd>Space</Kbd>
</InputGroupAddon>
</InputGroup>
</Example>
)
}
function KbdInTooltip() {
return (
<Example title="Tooltip">
<Tooltip>
<TooltipTrigger render={<Button size="icon-sm" variant="outline" />}>
<IconPlaceholder
lucide="SaveIcon"
tabler="IconDeviceFloppy"
hugeicons="FloppyDiskIcon"
phosphor="FloppyDiskIcon"
remixicon="RiSaveLine"
/>
</TooltipTrigger>
<TooltipContent className="pr-1.5">
<div className="flex items-center gap-2">
Save Changes <Kbd>S</Kbd>
</div>
</TooltipContent>
</Tooltip>
</Example>
)
}
function KbdWithSamp() {
return (
<Example title="With samp">
<Kbd>
<samp>File</samp>
</Kbd>
</Example>
)
}

View File

@@ -0,0 +1,64 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Checkbox } from "@/registry/bases/base/ui/checkbox"
import { Field } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import { Label } from "@/registry/bases/base/ui/label"
import { Textarea } from "@/registry/bases/base/ui/textarea"
export default function LabelExample() {
return (
<ExampleWrapper>
<LabelWithCheckbox />
<LabelWithInput />
<LabelDisabled />
<LabelWithTextarea />
</ExampleWrapper>
)
}
function LabelWithCheckbox() {
return (
<Example title="With Checkbox">
<Field orientation="horizontal">
<Checkbox id="label-demo-terms" />
<Label htmlFor="label-demo-terms">Accept terms and conditions</Label>
</Field>
</Example>
)
}
function LabelWithInput() {
return (
<Example title="With Input">
<Field>
<Label htmlFor="label-demo-username">Username</Label>
<Input id="label-demo-username" placeholder="Username" />
</Field>
</Example>
)
}
function LabelDisabled() {
return (
<Example title="Disabled">
<Field data-disabled={true}>
<Label htmlFor="label-demo-disabled">Disabled</Label>
<Input id="label-demo-disabled" placeholder="Disabled" disabled />
</Field>
</Example>
)
}
function LabelWithTextarea() {
return (
<Example title="With Textarea">
<Field>
<Label htmlFor="label-demo-message">Message</Label>
<Textarea id="label-demo-message" placeholder="Message" />
</Field>
</Example>
)
}

View File

@@ -0,0 +1,328 @@
"use client"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/bases/base/ui/accordion"
import { Button } from "@/registry/bases/base/ui/button"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/bases/base/ui/drawer"
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/registry/bases/base/ui/marker"
import { Spinner } from "@/registry/bases/base/ui/spinner"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function MarkerExamples() {
return (
<ExampleWrapper>
<MarkerExample />
<MarkerBorder />
<MarkerSeparator />
<MarkerAccordion />
<MarkerDrawer />
</ExampleWrapper>
)
}
function MarkerExample() {
return (
<Example title="Markers" className="gap-8">
<Marker>
<MarkerContent>A default marker</MarkerContent>
</Marker>
<Marker>
<MarkerIcon>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileDescription"
hugeicons="File01Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
</MarkerIcon>
<MarkerContent>Marker with icon</MarkerContent>
</Marker>
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Marker with a spinner</MarkerContent>
</Marker>
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent className="shimmer">
Marker with shimmer effect
</MarkerContent>
</Marker>
<Marker role="status">
<MarkerContent className="shimmer">Thinking...</MarkerContent>
</Marker>
<Marker render={<a href="#" />}>
<MarkerIcon>
<IconPlaceholder
lucide="GitBranchIcon"
tabler="IconGitBranch"
hugeicons="GitBranchIcon"
phosphor="GitBranchIcon"
remixicon="RiGitBranchLine"
/>
</MarkerIcon>
<MarkerContent>Marker as a link</MarkerContent>
</Marker>
<Marker
render={
<button
onClick={() => toast("You clicked the button")}
className="transition-colors hover:text-foreground"
/>
}
>
<MarkerIcon>
<IconPlaceholder
lucide="ClockIcon"
tabler="IconClock"
hugeicons="Clock01Icon"
phosphor="ClockIcon"
remixicon="RiTimeLine"
/>
</MarkerIcon>
<MarkerContent className="flex-1">
<div>Marker as a button</div>
</MarkerContent>
<MarkerIcon>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
/>
</MarkerIcon>
</Marker>
<Marker>
<MarkerIcon>
<IconPlaceholder
lucide="CircleUserIcon"
tabler="IconUserCircle"
hugeicons="UserCircleIcon"
phosphor="UserCircleIcon"
remixicon="RiUserSmileLine"
/>
</MarkerIcon>
<MarkerContent>Rhea joined the chat</MarkerContent>
</Marker>
<Marker className="justify-center">
<MarkerContent>
<strong className="font-medium">Olivia Rose</strong> left the chat
</MarkerContent>
</Marker>
<Marker className="flex-col">
<MarkerIcon>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileDescription"
hugeicons="File01Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
</MarkerIcon>
<MarkerContent>Marker with icon at the top</MarkerContent>
</Marker>
</Example>
)
}
function MarkerBorder() {
return (
<Example title="Border" className="gap-3">
<Marker variant="border">
<MarkerIcon>
<IconPlaceholder
lucide="GitBranchIcon"
tabler="IconGitBranch"
hugeicons="GitBranchIcon"
phosphor="GitBranchIcon"
remixicon="RiGitBranchLine"
/>
</MarkerIcon>
<MarkerContent>Switched to release-candidate</MarkerContent>
</Marker>
<Marker variant="border">
<MarkerIcon>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="Search01Icon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</MarkerIcon>
<MarkerContent>Reviewed 8 related files</MarkerContent>
</Marker>
<Marker variant="border">
<MarkerIcon>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileDescription"
hugeicons="File01Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
</MarkerIcon>
<MarkerContent>Opened implementation notes</MarkerContent>
</Marker>
</Example>
)
}
function MarkerSeparator() {
return (
<Example title="Separator" className="gap-8">
<Marker variant="separator">
<MarkerContent>Worked for 42s</MarkerContent>
</Marker>
<Marker variant="separator" role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Compacting conversation</MarkerContent>
</Marker>
<Marker variant="separator" role="status">
<MarkerContent className="shimmer">Reading 4 files</MarkerContent>
</Marker>
<Marker variant="separator">
<MarkerIcon>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
</MarkerIcon>
<MarkerContent>Conversation compacted</MarkerContent>
</Marker>
<Marker variant="separator">
<MarkerContent>
With a <a href="#">link to learn more</a>
</MarkerContent>
</Marker>
<Marker variant="separator">
<MarkerContent>
<Button variant="outline">
<IconPlaceholder
lucide="GitBranchIcon"
tabler="IconGitBranch"
hugeicons="GitBranchIcon"
phosphor="GitBranchIcon"
remixicon="RiGitBranchLine"
/>
Button
</Button>
</MarkerContent>
</Marker>
</Example>
)
}
function MarkerAccordion() {
return (
<Example title="Accordion" containerClassName="min-h-72">
<Accordion>
<AccordionItem value="1">
<AccordionTrigger>
<Marker>
<MarkerIcon>
<IconPlaceholder
lucide="ClockIcon"
tabler="IconClock"
hugeicons="Clock01Icon"
phosphor="ClockIcon"
remixicon="RiTimeLine"
/>
</MarkerIcon>
<MarkerContent>Worked for 42s</MarkerContent>
</Marker>
</AccordionTrigger>
<AccordionContent>
<p className="text-sm text-muted-foreground">
The user asked for a list of all the files in the current
directory. The assistant responded with a list of all the files in
the current directory.
</p>
</AccordionContent>
</AccordionItem>
</Accordion>
</Example>
)
}
function MarkerDrawer() {
return (
<Example title="Drawer">
<Drawer swipeDirection="right">
<Marker variant="separator">
<DrawerTrigger render={<Button variant="outline" />}>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
data-icon="inline-start"
/>
Explored 4 files
</DrawerTrigger>
</Marker>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>File Activity</DrawerTitle>
<DrawerDescription>
Files read while preparing the response.
</DrawerDescription>
</DrawerHeader>
<div className="grid gap-3 px-4 text-sm">
<div className="flex items-center justify-between gap-4 rounded-md border px-3 py-2">
<span className="truncate">app/chat/page.tsx</span>
<span className="text-muted-foreground">read</span>
</div>
<div className="flex items-center justify-between gap-4 rounded-md border px-3 py-2">
<span className="truncate">components/message.tsx</span>
<span className="text-muted-foreground">read</span>
</div>
<div className="flex items-center justify-between gap-4 rounded-md border px-3 py-2">
<span className="truncate">lib/ai.ts</span>
<span className="text-muted-foreground">read</span>
</div>
</div>
<DrawerFooter>
<DrawerClose render={<Button variant="outline" />}>
Close
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</Example>
)
}

View File

@@ -0,0 +1,774 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/registry/bases/base/ui/menubar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function MenubarExample() {
return (
<ExampleWrapper>
<MenubarBasic />
<MenubarWithSubmenu />
<MenubarSides />
<MenubarWithCheckboxes />
<MenubarWithRadio />
<MenubarWithIcons />
<MenubarWithShortcuts />
<MenubarFormat />
<MenubarInsert />
<MenubarDestructive />
<MenubarInDialog />
<MenubarWithInset />
</ExampleWrapper>
)
}
function MenubarBasic() {
return (
<Example title="Basic">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>
New Window <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled>New Incognito Window</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Print... <MenubarShortcut>⌘P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarSides() {
return (
<Example title="Sides" containerClassName="col-span-2">
<div className="flex flex-wrap justify-center gap-2">
{(
[
"inline-start",
"left",
"top",
"bottom",
"right",
"inline-end",
] as const
).map((side) => (
<Menubar key={side}>
<MenubarMenu>
<MenubarTrigger className="capitalize">
{side.replace("-", " ")}
</MenubarTrigger>
<MenubarContent side={side}>
<MenubarGroup>
<MenubarItem>New Tab</MenubarItem>
<MenubarItem>New Window</MenubarItem>
<MenubarItem>New Incognito Window</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
))}
</div>
</Example>
)
}
function MenubarWithSubmenu() {
return (
<Example title="With Submenu">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarSub>
<MenubarSubTrigger>Share</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Email link</MenubarItem>
<MenubarItem>Messages</MenubarItem>
<MenubarItem>Notes</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
Print... <MenubarShortcut>⌘P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>Find</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Find...</MenubarItem>
<MenubarItem>Find Next</MenubarItem>
<MenubarItem>Find Previous</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>Cut</MenubarItem>
<MenubarItem>Copy</MenubarItem>
<MenubarItem>Paste</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarWithCheckboxes() {
return (
<Example title="With Checkboxes">
<Menubar>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent className="w-64">
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
<MenubarCheckboxItem checked>
Always Show Full URLs
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>
Reload <MenubarShortcut>⌘R</MenubarShortcut>
</MenubarItem>
<MenubarItem disabled inset>
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Format</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem checked>Strikethrough</MenubarCheckboxItem>
<MenubarCheckboxItem>Code</MenubarCheckboxItem>
<MenubarCheckboxItem>Superscript</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarWithRadio() {
const [user, setUser] = React.useState("benoit")
const [theme, setTheme] = React.useState("system")
return (
<Example title="With Radio">
<Menubar>
<MenubarMenu>
<MenubarTrigger>Profiles</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value={user} onValueChange={setUser}>
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
<MenubarRadioItem value="luis">Luis</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarItem inset>Edit...</MenubarItem>
<MenubarItem inset>Add Profile...</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Theme</MenubarTrigger>
<MenubarContent>
<MenubarRadioGroup value={theme} onValueChange={setTheme}>
<MenubarRadioItem value="light">Light</MenubarRadioItem>
<MenubarRadioItem value="dark">Dark</MenubarRadioItem>
<MenubarRadioItem value="system">System</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarWithIcons() {
return (
<Example title="With Icons">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<IconPlaceholder
lucide="FileIcon"
tabler="IconFile"
hugeicons="FileIcon"
phosphor="FileIcon"
remixicon="RiFileLine"
/>
New File <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="FolderIcon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
Open Folder
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
<IconPlaceholder
lucide="SaveIcon"
tabler="IconDeviceFloppy"
hugeicons="FloppyDiskIcon"
phosphor="FloppyDiskIcon"
remixicon="RiSaveLine"
/>
Save <MenubarShortcut>⌘S</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>More</MenubarTrigger>
<MenubarContent>
<MenubarGroup>
<MenubarItem>
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
Settings
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
Help
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">
<IconPlaceholder
lucide="CircleDashedIcon"
tabler="IconCircleDashed"
hugeicons="DashedLineCircleIcon"
phosphor="CircleDashedIcon"
remixicon="RiLoaderLine"
/>
Delete
</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarWithShortcuts() {
return (
<Example title="With Shortcuts">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
</MenubarItem>
<MenubarItem>
New Window <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Print... <MenubarShortcut>⌘P</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarItem>
Cut <MenubarShortcut>⌘X</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Copy <MenubarShortcut>⌘C</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Paste <MenubarShortcut>⌘V</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarFormat() {
return (
<Example title="Format">
<Menubar>
<MenubarMenu>
<MenubarTrigger>Format</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
Bold <MenubarShortcut>⌘B</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
Italic <MenubarShortcut>⌘I</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
Underline <MenubarShortcut>⌘U</MenubarShortcut>
</MenubarItem>
<MenubarSeparator />
<MenubarCheckboxItem checked>Strikethrough</MenubarCheckboxItem>
<MenubarCheckboxItem>Code</MenubarCheckboxItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarCheckboxItem>Show Ruler</MenubarCheckboxItem>
<MenubarCheckboxItem checked>Show Grid</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarItem inset>Zoom In</MenubarItem>
<MenubarItem inset>Zoom Out</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarInsert() {
return (
<Example title="Insert">
<Menubar>
<MenubarMenu>
<MenubarTrigger>Insert</MenubarTrigger>
<MenubarContent>
<MenubarSub>
<MenubarSubTrigger>
<IconPlaceholder
lucide="ImageIcon"
tabler="IconPhoto"
hugeicons="ImageIcon"
phosphor="ImageIcon"
remixicon="RiImageLine"
/>
Media
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Image</MenubarItem>
<MenubarItem>Video</MenubarItem>
<MenubarItem>Audio</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem>
<IconPlaceholder
lucide="LinkIcon"
tabler="IconLink"
hugeicons="LinkIcon"
phosphor="LinkIcon"
remixicon="RiLinksLine"
/>
Link <MenubarShortcut>⌘K</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="TableIcon"
tabler="IconTable"
hugeicons="TableIcon"
phosphor="TableIcon"
remixicon="RiTableLine"
/>
Table
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Tools</MenubarTrigger>
<MenubarContent className="w-44">
<MenubarItem>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
Find & Replace <MenubarShortcut>⌘F</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
/>
Spell Check
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarDestructive() {
return (
<Example title="Destructive">
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent className="w-40">
<MenubarItem>
<IconPlaceholder
lucide="FileIcon"
tabler="IconFile"
hugeicons="FileIcon"
phosphor="FileIcon"
remixicon="RiFileLine"
/>
New File <MenubarShortcut>⌘N</MenubarShortcut>
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="FolderIcon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
Open Folder
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete File <MenubarShortcut>⌘⌫</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Account</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
Profile
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
Settings
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">
<IconPlaceholder
lucide="LogOutIcon"
tabler="IconLogout"
hugeicons="LogoutIcon"
phosphor="SignOutIcon"
remixicon="RiLogoutBoxLine"
/>
Sign out
</MenubarItem>
<MenubarSeparator />
<MenubarItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}
function MenubarInDialog() {
return (
<Example title="In Dialog">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Menubar Example</DialogTitle>
<DialogDescription>
Use the menubar below to see the menu options.
</DialogDescription>
</DialogHeader>
<Menubar>
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarContent>
<MenubarItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="ClipboardPasteIcon"
tabler="IconClipboard"
hugeicons="ClipboardIcon"
phosphor="ClipboardIcon"
remixicon="RiClipboardLine"
/>
Paste
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>More Options</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem>Save Page...</MenubarItem>
<MenubarItem>Create Shortcut...</MenubarItem>
<MenubarItem>Name Window...</MenubarItem>
<MenubarSeparator />
<MenubarItem>Developer Tools</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSeparator />
<MenubarItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarContent>
<MenubarItem>
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
</MenubarItem>
<MenubarItem>
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</DialogContent>
</Dialog>
</Example>
)
}
function MenubarWithInset() {
const [showBookmarks, setShowBookmarks] = React.useState(true)
const [showUrls, setShowUrls] = React.useState(false)
const [theme, setTheme] = React.useState("system")
return (
<Example title="With Inset">
<Menubar>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent className="w-44">
<MenubarGroup>
<MenubarLabel>Actions</MenubarLabel>
<MenubarItem>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
Copy
</MenubarItem>
<MenubarItem>
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
Cut
</MenubarItem>
<MenubarItem inset>Paste</MenubarItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarLabel inset>Appearance</MenubarLabel>
<MenubarCheckboxItem
inset
checked={showBookmarks}
onCheckedChange={setShowBookmarks}
>
Bookmarks
</MenubarCheckboxItem>
<MenubarCheckboxItem
inset
checked={showUrls}
onCheckedChange={setShowUrls}
>
Full URLs
</MenubarCheckboxItem>
</MenubarGroup>
<MenubarSeparator />
<MenubarGroup>
<MenubarLabel inset>Theme</MenubarLabel>
<MenubarRadioGroup value={theme} onValueChange={setTheme}>
<MenubarRadioItem inset value="light">
Light
</MenubarRadioItem>
<MenubarRadioItem inset value="dark">
Dark
</MenubarRadioItem>
<MenubarRadioItem inset value="system">
System
</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarGroup>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger inset>More Options</MenubarSubTrigger>
<MenubarSubContent>
<MenubarGroup>
<MenubarItem>Save Page...</MenubarItem>
<MenubarItem>Create Shortcut...</MenubarItem>
</MenubarGroup>
</MenubarSubContent>
</MenubarSub>
</MenubarContent>
</MenubarMenu>
</Menubar>
</Example>
)
}

View File

@@ -0,0 +1,948 @@
import type { UIMessage } from "ai"
import { createChat } from "@/lib/ai"
import { type MessagePartRenderProps } from "@/components/message-parts"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Attachment,
AttachmentAction,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentGroup,
AttachmentMedia,
AttachmentTitle,
} from "@/registry/bases/base/ui/attachment"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/base/ui/avatar"
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/registry/bases/base/ui/bubble"
import { Button } from "@/registry/bases/base/ui/button"
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/registry/bases/base/ui/marker"
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageGroup,
MessageHeader,
} from "@/registry/bases/base/ui/message"
import { Spinner } from "@/registry/bases/base/ui/spinner"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
type MessagePartsData = {
deployment: {
status: "healthy" | "degraded"
region: string
latency: number
}
}
type MessagePartsTools = {
getDeploymentHealth: {
input: {
project: string
region: string
}
output: {
status: "healthy" | "degraded"
p95: number
errors: number
recommendation: string
}
}
}
type MessagePartsMessage = UIMessage<
unknown,
MessagePartsData,
MessagePartsTools
>
const messagePartsChat = createChat<MessagePartsMessage>()
.user("Can you review this screenshot and check the deployment?", {
files: [
{
mediaType: "image/jpeg",
filename: "checkout-error.jpg",
url: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=900&auto=format&fit=crop&q=80",
},
],
})
.assistant(({ writer }) => {
writer.reasoning(
"The user included an image and asked for deployment status, so I should inspect the screenshot context and call the deployment health tool before answering."
)
const deployment = writer.tool("getDeploymentHealth", {
title: "Checking deployment health",
input: {
project: "checkout",
region: "iad1",
},
})
deployment.output({
status: "healthy",
p95: 186,
errors: 2,
recommendation: "Keep the deployment live and monitor error rate.",
})
writer.data({
type: "data-deployment",
id: "deployment-health",
data: {
status: "healthy",
region: "iad1",
latency: 186,
},
})
writer.text(
"The deployment looks healthy. P95 latency is **186ms** with **2 recent errors**, so I would keep it live and monitor error rate for the next 15 minutes."
)
writer.sourceUrl({
sourceId: "source-deployment-docs",
title: "Deployment health checks",
url: "https://vercel.com/docs/deployments",
})
writer.sourceDocument({
sourceId: "source-runbook",
title: "Checkout release runbook",
filename: "checkout-release-runbook.md",
mediaType: "text/markdown",
})
})
const messagePartsMessages = messagePartsChat.get()
type MessagePartsPart = MessagePartsMessage["parts"][number]
export default function MessageExample() {
return (
<ExampleWrapper>
<MessageDefault />
<MessageWithAvatar />
<MessageGroupExample />
<MessageGroupChat />
<MessageHeaderFooter />
<MessageActions />
<MessageAttachment />
<MessageAttachmentGroup />
</ExampleWrapper>
)
}
function MessageTextPart({
part,
message,
}: MessagePartRenderProps<MessagePartsPart, MessagePartsMessage>) {
if (part.type !== "text") {
return null
}
return (
<Bubble
align={message.role === "user" ? "end" : "start"}
variant={message.role === "user" ? "default" : "ghost"}
>
<BubbleContent>
<span className="whitespace-pre-wrap">{part.text}</span>
</BubbleContent>
</Bubble>
)
}
function MessageDefault() {
return (
<Example title="Message">
<div className="flex w-full max-w-md min-w-0 flex-col gap-10">
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Deploying to prod real quick.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>It&apos;s 4:55 PM. On a Friday.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>It&apos;s a one-line change.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<BubbleGroup>
<Bubble variant="muted">
<BubbleContent>
It&apos;s always a one-line change 😭. Make sure to run the
tests this time.
</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>Alright, go for it.</BubbleContent>
</Bubble>
</BubbleGroup>
</MessageContent>
</Message>
</div>
</Example>
)
}
function MessageWithAvatar() {
return (
<Example title="Avatar">
<div className="flex w-full max-w-md flex-col gap-10">
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
className="grayscale"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>Something went wrong. Any idea?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
I checked the latest deployment and found the build failed
during dependency installation.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>Something went wrong. Any idea?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://avatar.vercel.sh/shadcn"
alt="@shadcn"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent className="space-y-2">
<div>
I checked the latest deployment and found the build failed
during dependency installation.
</div>
<div>Can you share the exact error message from the logs?</div>
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="destructive">
<BubbleContent>Something went wrong. Any idea?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</div>
</Example>
)
}
function MessageGroupExample() {
return (
<Example title="Group">
<div className="flex w-full max-w-md flex-col gap-10">
<MessageGroup>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>I checked the registry addresses.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The component and example JSON now live under the UI registry.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
<MessageGroup>
<Message align="end">
<MessageContent>
<Bubble variant="muted">
<BubbleContent>I checked the registry addresses.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The component and example JSON now live under the UI registry.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
<MessageGroup>
<Message align="end">
<MessageAvatar />
<MessageContent>
<Bubble variant="muted">
<BubbleContent>I checked the registry addresses.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://avatar.vercel.sh/shadcn"
alt="@shadcn"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The component and example JSON now live under the UI registry.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
<MessageGroup>
<Message>
<MessageAvatar />
<MessageContent>
<Bubble variant="muted">
<BubbleContent>I checked the registry addresses.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://avatar.vercel.sh/shadcn"
alt="@shadcn"
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The component and example JSON now live under the UI registry.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
</div>
</Example>
)
}
function MessageGroupChat() {
return (
<Example title="Group Chat">
<div className="flex w-full max-w-md flex-col gap-10">
<Message align="end">
<MessageContent>
<Bubble variant="tinted">
<BubbleContent>
Why is the dashboard showing zero users
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<MessageGroup>
<Message>
<MessageAvatar />
<MessageContent>
<Bubble variant="muted">
<BubbleContent>Define &quot;zero.&quot;</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>What do you mean?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageGroup>
<Message align="end">
<MessageContent>
<Bubble variant="tinted">
<BubbleContent>
<span className="font-medium">@eve</span> can you check?
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Checking the logs...</MarkerContent>
</Marker>
</Message>
<Message>
<MessageContent>
<MessageHeader>Eve</MessageHeader>
<Bubble variant="ghost">
<BubbleContent>
<span className="whitespace-pre-wrap">
{`I found the following error in the logs:
- Error: No users found
- Stack trace:
- at src/users/services/user-service.ts:123
- at src/users/services/get-user.ts:230
Can you check if the users are being created?`}
</span>
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</div>
</Example>
)
}
function MessageHeaderFooter() {
return (
<Example title="Header and Footer">
<div className="flex w-full max-w-md flex-col gap-12">
<Message align="end">
<MessageContent>
<MessageHeader className="justify-end">You</MessageHeader>
<Bubble>
<BubbleContent>
Can we keep this style quiet and send the update today?
</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span>Delivered</span>
<Button variant="ghost" size="xs">
Undo
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="start">
<MessageContent>
<MessageHeader>
<span>Olivia</span>
<span className="ml-auto font-normal">1m ago</span>
</MessageHeader>
<Bubble variant="muted">
<BubbleContent>
I checked the logs. The retry finished and the missing invoices
are included now.
</BubbleContent>
<BubbleReactions>
<Button variant="ghost" size="xs">
Thanks
</Button>
</BubbleReactions>
</Bubble>
<MessageFooter className="gap-2">
<span>From Support queue</span>
<Button variant="ghost" size="xs">
Copy
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<MessageHeader>Marina</MessageHeader>
<Bubble variant="outline">
<BubbleContent>
I drafted the reply with the exact filter path and a link to the
export history.
</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span>Draft ready</span>
<Button variant="outline" size="xs">
Review
</Button>
<Button size="xs">Send</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="start">
<MessageContent>
<MessageHeader>Assistant note</MessageHeader>
<Bubble variant="ghost">
<BubbleContent>
Ghost messages keep long-form assistant notes aligned with the
surrounding text instead of adding a frame.
</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span>Internal only</span>
<Button variant="ghost" size="xs">
Pin
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<MessageHeader className="justify-end">
Automation warning
</MessageHeader>
<Bubble variant="destructive">
<BubbleContent>
The old export link expires in 10 minutes. Regenerate before
sending this transcript externally.
</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span>Needs action</span>
<Button variant="destructive" size="xs">
Regenerate
</Button>
</MessageFooter>
</MessageContent>
</Message>
</div>
</Example>
)
}
function MessageAttachment() {
return (
<Example title="Attachment">
<div className="flex w-full max-w-sm flex-col gap-8">
<Message align="end">
<MessageContent>
<Attachment orientation="vertical">
<AttachmentMedia variant="image">
<img
src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80"
alt="Workspace"
/>
</AttachmentMedia>
</Attachment>
<Bubble>
<BubbleContent>
Here&apos;s the image. Can you add it to the PDF? Use it for the
cover page.
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
Done. Here&apos;s the PDF with the image added as the cover
page.
</BubbleContent>
</Bubble>
<Attachment>
<AttachmentMedia>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="FileIcon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction
type="button"
title="Download"
aria-label="Download"
size="icon-sm"
variant="secondary"
>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="Download01Icon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
/>
</AttachmentAction>
</AttachmentActions>
</Attachment>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>Thanks. Looks good.</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</div>
</Example>
)
}
function MessageAttachmentGroup() {
return (
<Example title="Attachment Group">
<div className="flex w-full max-w-sm flex-col gap-8">
<Message align="end">
<MessageContent>
<AttachmentGroup className="w-[80%]">
<Attachment orientation="vertical">
<AttachmentMedia variant="image">
<img
src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80"
alt="Workspace"
/>
</AttachmentMedia>
</Attachment>
<Attachment orientation="vertical">
<AttachmentMedia variant="image">
<img
src="https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=900&auto=format&fit=crop&q=80"
alt="Desk"
/>
</AttachmentMedia>
</Attachment>
<Attachment orientation="vertical">
<AttachmentMedia variant="image">
<img
src="https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=900&auto=format&fit=crop&q=80"
alt="Office"
/>
</AttachmentMedia>
</Attachment>
</AttachmentGroup>
<Bubble>
<BubbleContent>
Here are the brand photos. Can you add them to the deck?
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
Done. Here&apos;s the draft deck and the exported assets.
</BubbleContent>
</Bubble>
<AttachmentGroup className="w-full">
<Attachment className="w-64">
<AttachmentMedia>
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="FileIcon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>brand-deck.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 3.1 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction
type="button"
title="Download"
aria-label="Download brand-deck.pdf"
size="icon-sm"
variant="secondary"
>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="Download01Icon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
/>
</AttachmentAction>
</AttachmentActions>
</Attachment>
<Attachment className="w-64">
<AttachmentMedia>
<IconPlaceholder
lucide="PresentationIcon"
tabler="IconPresentation"
hugeicons="Presentation01Icon"
phosphor="PresentationIcon"
remixicon="RiSlideshowLine"
/>
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>brand-deck.key</AttachmentTitle>
<AttachmentDescription>Keynote · 9 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction
type="button"
title="Download"
aria-label="Download brand-deck.key"
size="icon-sm"
variant="secondary"
>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="Download01Icon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
/>
</AttachmentAction>
</AttachmentActions>
</Attachment>
<Attachment className="w-64">
<AttachmentMedia>
<IconPlaceholder
lucide="FileArchiveIcon"
tabler="IconFileZip"
hugeicons="File01Icon"
phosphor="FileZipIcon"
remixicon="RiFileZipLine"
/>
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>brand-assets.zip</AttachmentTitle>
<AttachmentDescription>ZIP · 4.2 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction
type="button"
title="Download"
aria-label="Download brand-assets.zip"
size="icon-sm"
variant="secondary"
>
<IconPlaceholder
lucide="DownloadIcon"
tabler="IconDownload"
hugeicons="Download01Icon"
phosphor="DownloadIcon"
remixicon="RiDownloadLine"
/>
</AttachmentAction>
</AttachmentActions>
</Attachment>
</AttachmentGroup>
</MessageContent>
</Message>
</div>
</Example>
)
}
function MessageActions() {
return (
<Example title="Actions">
<div className="flex w-full max-w-md flex-col gap-10">
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>
What is the status of the deployment?
</BubbleContent>
</Bubble>
<MessageFooter>
<Button
variant="ghost"
size="icon"
aria-label="Copy"
title="Copy"
>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</Button>
<Button
variant="ghost"
size="icon"
aria-label="Retry"
title="Retry"
>
<IconPlaceholder
lucide="RefreshCcwIcon"
tabler="IconRefresh"
hugeicons="ReloadIcon"
phosphor="ArrowClockwiseIcon"
remixicon="RiRefreshLine"
/>
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage
src="https://github.com/evilrabbit.png"
alt="@evilrabbit"
/>
<AvatarFallback>ER</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>
The install failure is coming from the workspace package. I can
open the exact log line or retry the deployment.
</BubbleContent>
</Bubble>
<MessageFooter>
<Button
variant="ghost"
size="icon"
aria-label="Copy"
title="Copy"
>
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</Button>
<Button
variant="ghost"
size="icon"
aria-label="Like: thumbs up"
title="Like: thumbs up"
>
<IconPlaceholder
lucide="ThumbsUpIcon"
tabler="IconThumbUp"
hugeicons="ThumbsUpIcon"
phosphor="ThumbsUpIcon"
remixicon="RiThumbUpLine"
/>
</Button>
<Button
variant="ghost"
size="icon"
aria-label="Like: thumbs down"
title="Like: thumbs down"
>
<IconPlaceholder
lucide="ThumbsDownIcon"
tabler="IconThumbDown"
hugeicons="ThumbsDownIcon"
phosphor="ThumbsDownIcon"
remixicon="RiThumbDownLine"
/>
</Button>
</MessageFooter>
</MessageContent>
</Message>
<Message align="end">
<MessageContent>
<Bubble>
<BubbleContent>
Okay drop me a link. I&apos;ll check it out.
</BubbleContent>
</Bubble>
<MessageFooter className="gap-2">
<span className="font-normal text-destructive">
Failed to send
</span>
<Button variant="secondary" size="xs">
Retry
</Button>
</MessageFooter>
</MessageContent>
</Message>
</div>
</Example>
)
}

View File

@@ -0,0 +1,252 @@
"use client"
import * as React from "react"
import { useChat } from "@ai-sdk/react"
import { createChat, getMessageText } from "@/lib/ai"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Bubble, BubbleContent } from "@/registry/bases/base/ui/bubble"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/registry/bases/base/ui/input-group"
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/registry/bases/base/ui/marker"
import { Message, MessageContent } from "@/registry/bases/base/ui/message"
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/registry/bases/base/ui/message-scroller"
import { Spinner } from "@/registry/bases/base/ui/spinner"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function MessageScrollerExample() {
return (
<ExampleWrapper>
<MessageScrollerDemo />
</ExampleWrapper>
)
}
const chat = createChat()
.user("Hello there!")
.sleep(2000)
.assistant("Hey, how's it going?")
.user(
`I'm prototyping an AI chat surface for our product docs.
Can you sketch a sensible component breakdown and call out anything I'd regret baking into v1?`
)
.sleep(2000)
.assistant(
`## Recommended layout
Treat the chat as three layers so scrolling and the composer never fight each other:
| Layer | Responsibility |
| --- | --- |
| **Shell** | Card or page frame, title, status indicator |
| **Transcript** | \`overflow-y-auto\` message list |
| **Composer** | Input group, send, stop |
### Message rendering
- **User messages**: right-aligned, muted background, \`max-w-[80%]\`.
- **Assistant messages**: full width within the column; use \`whitespace-pre-wrap\` for text.
### Suggested component split
\`\`\`tsx
<ChatCard>
<ChatHeader status={status} />
<ChatTranscript messages={messages} />
<ChatComposer onSubmit={sendMessage} onStop={stop} />
</ChatCard>
\`\`\`
### v1 pitfalls to avoid
1. **Inlining transport logic in the UI** — keep scripted/demo transport beside your example, not inside shared components.
2. **Fixed transcript height without \`overflow-hidden\`** — parent must clip, child must scroll, or the composer jumps.
3. **Not giving the scroller a ref or auto-scroll flag** — long replies should stay pinned to the bottom unless the user scrolls up.
### Practical next step
Start with plain \`whitespace-pre-wrap\` text to validate spacing and scroll behavior. Once the transcript feels right, you can swap assistant text rendering to a richer renderer if you need it.
Want me to walk through auto-scroll vs. manual scroll-to-bottom next?`
)
.user(
"What about message spacing when one reply is short and the next is really long?"
)
.sleep(2000)
.assistant(
`Good question. The scroll container should own vertical rhythm, not individual bubbles.
Use a consistent \`gap\` on the message list (something like \`gap-4\` or \`gap-6\`) so short and long messages sit on the same grid. For assistant replies that span many paragraphs, keep everything in one bubble rather than splitting on every line break — otherwise the transcript feels choppy when you're skimming.
When a long reply streams in, pin the viewport to the bottom with \`MessageScroller autoScroll\` until the user scrolls up. If they've scrolled away, show a scroll-to-bottom affordance instead of yanking them back mid-read.
That's usually enough for v1. Fancy diff animations or per-paragraph fade-ins can wait until you've validated that the base scroll + composer layout holds up on real devices.`
)
.user("Thanks, that helps.")
.sleep(1000)
.assistant(
"Happy to help — send another message when you're ready to keep stepping through the demo."
)
const initialMessages = chat.get(0)
const transport = chat.transport({
delayMs: 10,
})
function MessageScrollerDemo() {
const { messages, sendMessage, status, stop } = useChat({
messages: initialMessages,
transport,
})
const nextMessage = chat.next(messages)
const isBusy = status === "submitted" || status === "streaming"
function handleFormSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
if (!nextMessage || isBusy) {
return
}
void sendMessage(nextMessage)
}
return (
<Example title="Chat" containerClassName="self-start">
<Card className="h-140">
<CardHeader>
<CardTitle>How can I help you today?</CardTitle>
<CardDescription>Status: {status}</CardDescription>
</CardHeader>
<CardContent className="min-h-0 flex-1 overflow-hidden p-0">
<MessageScrollerProvider>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent className="p-(--card-spacing)">
{messages.map((message) => (
<MessageScrollerItem
key={message.id}
messageId={message.id}
scrollAnchor={message.role === "user"}
>
<Message
align={message.role === "user" ? "end" : "start"}
>
<MessageContent>
{message.parts.map((part, index) => {
if (part.type !== "text") {
return null
}
return (
<Bubble
key={`${message.id}-${index}`}
variant={
message.role === "user" ? "default" : "muted"
}
>
<BubbleContent className="whitespace-pre-wrap">
{part.text}
</BubbleContent>
</Bubble>
)
})}
</MessageContent>
</Message>
</MessageScrollerItem>
))}
{status === "submitted" ? (
<MessageScrollerItem scrollAnchor={false}>
<Marker role="status">
<MarkerIcon>
<Spinner />
</MarkerIcon>
<MarkerContent>Thinking...</MarkerContent>
</Marker>
</MessageScrollerItem>
) : null}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
</CardContent>
<CardFooter>
<form onSubmit={handleFormSubmit} className="w-full" id="chat-form">
<InputGroup>
<InputGroupTextarea
placeholder="Ask me anything..."
className="h-10 min-h-10 overflow-y-auto"
value={
isBusy ? "" : nextMessage ? getMessageText(nextMessage) : ""
}
readOnly
/>
<InputGroupAddon align="block-end" className="p-2">
<InputGroupButton
variant="default"
size="icon-sm"
type="submit"
disabled={!nextMessage || isBusy}
className="ml-auto data-[hidden=true]:hidden"
data-hidden={isBusy}
>
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUp02Icon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
<span className="sr-only">Send</span>
</InputGroupButton>
<InputGroupButton
size="icon-sm"
type="button"
data-hidden={!isBusy}
className="ml-auto data-[hidden=true]:hidden"
onClick={() => stop()}
>
<IconPlaceholder
lucide="StopCircleIcon"
tabler="IconPlayerStop"
hugeicons="StopCircleIcon"
phosphor="StopCircleIcon"
remixicon="RiStopCircleLine"
/>
<span className="sr-only">Stop</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</form>
</CardFooter>
</Card>
</Example>
)
}

View File

@@ -0,0 +1,129 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/registry/bases/base/ui/native-select"
export default function NativeSelectExample() {
return (
<ExampleWrapper>
<NativeSelectBasic />
<NativeSelectWithGroups />
<NativeSelectSizes />
<NativeSelectWithField />
<NativeSelectDisabled />
<NativeSelectInvalid />
</ExampleWrapper>
)
}
function NativeSelectBasic() {
return (
<Example title="Basic">
<NativeSelect>
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
<NativeSelectOption value="grapes" disabled>
Grapes
</NativeSelectOption>
<NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
</NativeSelect>
</Example>
)
}
function NativeSelectWithGroups() {
return (
<Example title="With Groups">
<NativeSelect>
<NativeSelectOption value="">Select a food</NativeSelectOption>
<NativeSelectOptGroup label="Fruits">
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
</NativeSelectOptGroup>
<NativeSelectOptGroup label="Vegetables">
<NativeSelectOption value="carrot">Carrot</NativeSelectOption>
<NativeSelectOption value="broccoli">Broccoli</NativeSelectOption>
<NativeSelectOption value="spinach">Spinach</NativeSelectOption>
</NativeSelectOptGroup>
</NativeSelect>
</Example>
)
}
function NativeSelectSizes() {
return (
<Example title="Sizes">
<div className="flex flex-col gap-4">
<NativeSelect size="sm">
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
</NativeSelect>
<NativeSelect size="default">
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
</NativeSelect>
</div>
</Example>
)
}
function NativeSelectWithField() {
return (
<Example title="With Field">
<Field>
<FieldLabel htmlFor="native-select-country">Country</FieldLabel>
<NativeSelect id="native-select-country">
<NativeSelectOption value="">Select a country</NativeSelectOption>
<NativeSelectOption value="us">United States</NativeSelectOption>
<NativeSelectOption value="uk">United Kingdom</NativeSelectOption>
<NativeSelectOption value="ca">Canada</NativeSelectOption>
<NativeSelectOption value="au">Australia</NativeSelectOption>
</NativeSelect>
<FieldDescription>Select your country of residence.</FieldDescription>
</Field>
</Example>
)
}
function NativeSelectDisabled() {
return (
<Example title="Disabled">
<NativeSelect disabled>
<NativeSelectOption value="">Disabled</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
</NativeSelect>
</Example>
)
}
function NativeSelectInvalid() {
return (
<Example title="Invalid">
<NativeSelect aria-invalid="true">
<NativeSelectOption value="">Error state</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
<NativeSelectOption value="banana">Banana</NativeSelectOption>
<NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
</NativeSelect>
</Example>
)
}

View File

@@ -0,0 +1,185 @@
"use client"
import * as React from "react"
import Link from "next/link"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/registry/bases/base/ui/navigation-menu"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const components: { title: string; href: string; description: string }[] = [
{
title: "Alert Dialog",
href: "/docs/primitives/alert-dialog",
description:
"A modal dialog that interrupts the user with important content and expects a response.",
},
{
title: "Hover Card",
href: "/docs/primitives/hover-card",
description:
"For sighted users to preview content available behind a link.",
},
{
title: "Progress",
href: "/docs/primitives/progress",
description:
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
},
{
title: "Scroll-area",
href: "/docs/primitives/scroll-area",
description: "Visually or semantically separates content.",
},
{
title: "Tabs",
href: "/docs/primitives/tabs",
description:
"A set of layered sections of content—known as tab panels—that are displayed one at a time.",
},
{
title: "Tooltip",
href: "/docs/primitives/tooltip",
description:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
},
]
export default function NavigationMenuExample() {
return (
<ExampleWrapper className="lg:grid-cols-1">
<NavigationMenuBasic />
</ExampleWrapper>
)
}
function NavigationMenuBasic() {
return (
<Example title="Basic">
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="w-96">
<ListItem href="/docs" title="Introduction">
Re-usable components built with Tailwind CSS.
</ListItem>
<ListItem href="/docs/installation" title="Installation">
How to install dependencies and structure your app.
</ListItem>
<ListItem href="/docs/primitives/typography" title="Typography">
Styles for headings, paragraphs, lists...etc
</ListItem>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{components.map((component) => (
<ListItem
key={component.title}
title={component.title}
href={component.href}
>
{component.description}
</ListItem>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>With Icon</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[200px]">
<li>
<NavigationMenuLink
render={
<Link href="#" className="flex-row items-center gap-2" />
}
>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
Backlog
</NavigationMenuLink>
<NavigationMenuLink
render={
<Link href="#" className="flex-row items-center gap-2" />
}
>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
To Do
</NavigationMenuLink>
<NavigationMenuLink
render={
<Link href="#" className="flex-row items-center gap-2" />
}
>
<IconPlaceholder
lucide="CircleAlertIcon"
tabler="IconExclamationCircle"
hugeicons="AlertCircleIcon"
phosphor="WarningCircleIcon"
remixicon="RiErrorWarningLine"
/>
Done
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
render={<Link href="/docs" />}
className={navigationMenuTriggerStyle()}
>
Documentation
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</Example>
)
}
function ListItem({
title,
children,
href,
...props
}: React.ComponentPropsWithoutRef<"li"> & { href: string }) {
return (
<li {...props}>
<NavigationMenuLink render={<Link href={href} />}>
<div className="flex flex-col gap-1 style-vega:text-sm style-nova:text-sm style-lyra:text-xs style-maia:text-sm style-mira:text-xs style-luma:text-sm">
<div className="leading-none font-medium">{title}</div>
<div className="line-clamp-2 text-muted-foreground">{children}</div>
</div>
</NavigationMenuLink>
</li>
)
}

View File

@@ -0,0 +1,128 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Field, FieldLabel } from "@/registry/bases/base/ui/field"
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/registry/bases/base/ui/pagination"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
export default function PaginationExample() {
return (
<ExampleWrapper>
<PaginationBasic />
<PaginationSimple />
<PaginationIconsOnly />
</ExampleWrapper>
)
}
function PaginationBasic() {
return (
<Example title="Basic">
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
</Example>
)
}
function PaginationSimple() {
return (
<Example title="Simple">
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">4</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">5</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
</Example>
)
}
function PaginationIconsOnly() {
return (
<Example title="With Select">
<div className="flex items-center justify-between gap-4">
<Field orientation="horizontal" className="w-fit">
<FieldLabel htmlFor="select-rows-per-page">Rows per page</FieldLabel>
<Select defaultValue="25">
<SelectTrigger className="w-20" id="select-rows-per-page">
<SelectValue />
</SelectTrigger>
<SelectContent align="start">
<SelectGroup>
<SelectItem value="10">10</SelectItem>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Pagination className="mx-0 w-auto">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
</Example>
)
}

View File

@@ -0,0 +1,199 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/registry/bases/base/ui/popover"
export default function PopoverExample() {
return (
<ExampleWrapper>
<PopoverBasic />
<PopoverSides />
<PopoverWithForm />
<PopoverAlignments />
<PopoverInDialog />
</ExampleWrapper>
)
}
function PopoverBasic() {
return (
<Example title="Basic">
<Popover>
<PopoverTrigger render={<Button variant="outline" className="w-fit" />}>
Open Popover
</PopoverTrigger>
<PopoverContent align="start">
<PopoverHeader>
<PopoverTitle>Dimensions</PopoverTitle>
<PopoverDescription>
Set the dimensions for the layer.
</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
</Example>
)
}
function PopoverSides() {
return (
<Example title="Sides">
<div className="flex flex-col gap-2">
<div className="flex flex-wrap gap-2">
{(["inline-start", "left", "top"] as const).map((side) => (
<Popover key={side}>
<PopoverTrigger
render={
<Button variant="outline" className="w-fit capitalize" />
}
>
{side.replace("-", " ")}
</PopoverTrigger>
<PopoverContent side={side} className="w-40">
<p>Popover on {side.replace("-", " ")}</p>
</PopoverContent>
</Popover>
))}
</div>
<div className="flex flex-wrap gap-2">
{(["bottom", "right", "inline-end"] as const).map((side) => (
<Popover key={side}>
<PopoverTrigger
render={
<Button variant="outline" className="w-fit capitalize" />
}
>
{side.replace("-", " ")}
</PopoverTrigger>
<PopoverContent side={side} className="w-40">
<p>Popover on {side.replace("-", " ")}</p>
</PopoverContent>
</Popover>
))}
</div>
</div>
</Example>
)
}
function PopoverWithForm() {
return (
<Example title="With Form">
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Open Popover
</PopoverTrigger>
<PopoverContent className="w-64" align="start">
<PopoverHeader>
<PopoverTitle>Dimensions</PopoverTitle>
<PopoverDescription>
Set the dimensions for the layer.
</PopoverDescription>
</PopoverHeader>
<FieldGroup className="gap-4">
<Field orientation="horizontal">
<FieldLabel htmlFor="width" className="w-1/2">
Width
</FieldLabel>
<Input id="width" defaultValue="100%" />
</Field>
<Field orientation="horizontal">
<FieldLabel htmlFor="height" className="w-1/2">
Height
</FieldLabel>
<Input id="height" defaultValue="25px" />
</Field>
</FieldGroup>
</PopoverContent>
</Popover>
</Example>
)
}
function PopoverAlignments() {
return (
<Example title="Alignments">
<div className="flex gap-6">
<Popover>
<PopoverTrigger render={<Button variant="outline" size="sm" />}>
Start
</PopoverTrigger>
<PopoverContent align="start" className="w-40">
Aligned to start
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger render={<Button variant="outline" size="sm" />}>
Center
</PopoverTrigger>
<PopoverContent align="center" className="w-40">
Aligned to center
</PopoverContent>
</Popover>
<Popover>
<PopoverTrigger render={<Button variant="outline" size="sm" />}>
End
</PopoverTrigger>
<PopoverContent align="end" className="w-40">
Aligned to end
</PopoverContent>
</Popover>
</div>
</Example>
)
}
function PopoverInDialog() {
return (
<Example title="In Dialog">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Popover Example</DialogTitle>
<DialogDescription>
Click the button below to see the popover.
</DialogDescription>
</DialogHeader>
<Popover>
<PopoverTrigger
render={<Button variant="outline" className="w-fit" />}
>
Open Popover
</PopoverTrigger>
<PopoverContent align="start">
<PopoverHeader>
<PopoverTitle>Popover in Dialog</PopoverTitle>
<PopoverDescription>
This popover appears inside a dialog. Click the button to open
it.
</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
</DialogContent>
</Dialog>
</Example>
)
}

View File

@@ -0,0 +1,142 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Item,
ItemActions,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/base/ui/item"
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/registry/bases/base/ui/progress"
import { Slider } from "@/registry/bases/base/ui/slider"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function ProgressExample() {
return (
<ExampleWrapper>
<ProgressValues />
<ProgressWithLabel />
<ProgressControlled />
<FileUploadList />
</ExampleWrapper>
)
}
function ProgressValues() {
return (
<Example title="Progress Bar">
<div className="flex w-full flex-col gap-4">
<Progress value={0} />
<Progress value={25} className="w-full" />
<Progress value={50} />
<Progress value={75} />
<Progress value={100} />
</div>
</Example>
)
}
function ProgressWithLabel() {
return (
<Example title="With Label">
<Progress value={56}>
<ProgressLabel>Upload progress</ProgressLabel>
<ProgressValue />
</Progress>
</Example>
)
}
function ProgressControlled() {
const [value, setValue] = React.useState(50)
return (
<Example title="Controlled">
<div className="flex w-full flex-col gap-4">
<Progress value={value} className="w-full" />
<Slider
value={value}
onValueChange={(value) => setValue(value as number)}
min={0}
max={100}
step={1}
/>
</div>
</Example>
)
}
function FileUploadList() {
const files = React.useMemo(
() => [
{
id: "1",
name: "document.pdf",
progress: 45,
timeRemaining: "2m 30s",
},
{
id: "2",
name: "presentation.pptx",
progress: 78,
timeRemaining: "45s",
},
{
id: "3",
name: "spreadsheet.xlsx",
progress: 12,
timeRemaining: "5m 12s",
},
{
id: "4",
name: "image.jpg",
progress: 100,
timeRemaining: "Complete",
},
],
[]
)
return (
<Example title="File Upload List">
<ItemGroup>
{files.map((file) => (
<Item key={file.id} size="xs" className="px-0">
<ItemMedia variant="icon">
<IconPlaceholder
lucide="FileIcon"
tabler="IconFile"
hugeicons="FileIcon"
phosphor="FileIcon"
remixicon="RiFileLine"
className="size-5"
/>
</ItemMedia>
<ItemContent className="inline-block truncate">
<ItemTitle className="inline">{file.name}</ItemTitle>
</ItemContent>
<ItemContent>
<Progress value={file.progress} className="w-32" />
</ItemContent>
<ItemActions className="w-16 justify-end">
<span className="text-sm text-muted-foreground">
{file.timeRemaining}
</span>
</ItemActions>
</Item>
))}
</ItemGroup>
</Example>
)
}

View File

@@ -0,0 +1,473 @@
"use client"
import * as React from "react"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import {
Questionnaire,
QuestionnaireActions,
QuestionnaireChoice,
QuestionnaireChoiceDescription,
QuestionnaireChoices,
QuestionnaireDescription,
QuestionnaireError,
QuestionnaireInput,
QuestionnaireItem,
QuestionnaireNext,
QuestionnairePrevious,
QuestionnaireProgress,
QuestionnaireSkip,
QuestionnaireSubmit,
QuestionnaireTitle,
} from "@/registry/bases/base/ui/questionnaire"
const questionnaireItems = [
{
choices: [
{ value: "delegation" },
{ value: "questions" },
{ value: "both" },
],
name: "direction",
required: true,
},
{
choices: [
{ value: "progress" },
{ value: "decisions" },
{ value: "risks" },
],
name: "signals",
},
{
choices: [{ value: "week" }, { value: "cycle" }, { value: "later" }],
name: "timing",
required: true,
},
] as const
const taskItems = [
{
choices: [
{ value: "inspect" },
{ value: "implement" },
{ value: "review" },
],
name: "task",
required: true,
},
] as const
const planItems = [
{
choices: [
{ value: "plus" },
{ value: "pro" },
{ value: "enterprise", disabled: true },
],
name: "plan",
required: true,
},
] as const
export default function QuestionnaireExample() {
return (
<ExampleWrapper>
<QuestionnaireStandalone />
<QuestionnaireCard />
<QuestionnaireDialog />
<QuestionnaireNoDescription />
<QuestionnaireDisabled />
</ExampleWrapper>
)
}
function QuestionnaireDisabled() {
return (
<Example title="Disabled" containerClassName="md:col-span-2">
<Questionnaire
className="mx-auto max-w-lg"
defaultItem="plan"
items={planItems}
onSubmit={handleSubmit}
>
<QuestionnaireItem name="plan" required>
<QuestionnaireTitle>Choose a plan</QuestionnaireTitle>
<QuestionnaireDescription>
Enterprise is not available on your account.
</QuestionnaireDescription>
<QuestionnaireChoices>
<QuestionnaireChoice value="plus">
<span className="font-medium">Plus</span>
<QuestionnaireChoiceDescription>
For individuals and small teams
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireChoice value="pro">
<span className="font-medium">Pro</span>
<QuestionnaireChoiceDescription>
For growing businesses
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireChoice value="enterprise" disabled>
<span className="font-medium">Enterprise</span>
<QuestionnaireChoiceDescription>
For large teams and enterprises
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
<QuestionnaireNavigation />
</Questionnaire>
</Example>
)
}
function QuestionnaireNoDescription() {
return (
<Example title="No description" containerClassName="md:col-span-2">
<Questionnaire
className="mx-auto max-w-lg"
defaultItem="task"
items={taskItems}
shortcuts="letters"
onSubmit={handleSubmit}
>
<QuestionnaireProgress />
<QuestionnaireItem name="task" required>
<QuestionnaireTitle>
What should the agent do next?
</QuestionnaireTitle>
<QuestionnaireChoices>
<QuestionnaireChoice value="inspect">
Inspect the codebase
</QuestionnaireChoice>
<QuestionnaireChoice value="implement">
Implement the change
</QuestionnaireChoice>
<QuestionnaireChoice value="review">
Review the result
</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
<QuestionnaireNavigation />
</Questionnaire>
</Example>
)
}
function QuestionnaireStandalone() {
return (
<Example title="Standalone" containerClassName="md:col-span-2">
<Questionnaire
className="mx-auto max-w-lg"
defaultItem="direction"
items={questionnaireItems}
shortcuts="letters"
onSubmit={handleSubmit}
>
<QuestionnaireProgress />
<QuestionnaireQuestions />
<QuestionnaireNavigation />
</Questionnaire>
</Example>
)
}
function QuestionnaireCard() {
return (
<Example title="Card" containerClassName="md:col-span-2">
<Questionnaire
defaultItem="direction"
items={questionnaireItems}
shortcuts="numbers"
onSubmit={handleSubmit}
>
<QuestionnaireCardQuestions />
</Questionnaire>
</Example>
)
}
function QuestionnaireDialog() {
return (
<Example
title="Dialog"
className="items-center justify-center"
containerClassName="md:col-span-2"
>
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open questionnaire
</DialogTrigger>
<DialogContent>
<Questionnaire
defaultItem="direction"
items={questionnaireItems}
onSubmit={handleSubmit}
>
<DialogHeader>
<DialogTitle className="sr-only">
Plan an agent interface
</DialogTitle>
<DialogDescription className="sr-only">
Answer three questions to shape the next prototype.
</DialogDescription>
<QuestionnaireProgress
className="font-semibold tracking-widest text-foreground uppercase"
render={(props, state) => (
<span {...props}>
Question {state.current} of {state.total}
</span>
)}
/>
</DialogHeader>
<QuestionnaireQuestions />
<DialogFooter>
<QuestionnaireNavigation />
</DialogFooter>
</Questionnaire>
</DialogContent>
</Dialog>
</Example>
)
}
function QuestionnaireCardQuestions() {
const directionTitleId = React.useId()
const signalsTitleId = React.useId()
const timingTitleId = React.useId()
return (
<>
<QuestionnaireItem
aria-labelledby={directionTitleId}
name="direction"
required
>
<Card className="mx-auto w-full max-w-lg">
<CardHeader>
<QuestionnaireTitle id={directionTitleId} render={<CardTitle />}>
What should we prototype next?
</QuestionnaireTitle>
<QuestionnaireDescription render={<CardDescription />}>
Choose one direction or write another answer.
</QuestionnaireDescription>
<CardAction>
<QuestionnaireProgress />
</CardAction>
</CardHeader>
<CardContent>
<QuestionnaireChoices>
<QuestionnaireChoice value="delegation">
<span className="font-medium">Sub-agent delegation</span>
<QuestionnaireChoiceDescription>
Show when work is delegated and what comes back.
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireChoice value="questions">
<span className="font-medium">Question prompts</span>
<QuestionnaireChoiceDescription>
Show choices while the agent waits for input.
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireChoice value="both">
<span className="font-medium">Both together</span>
<QuestionnaireChoiceDescription>
Explore one unified interaction pattern.
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireInput
aria-label="Another direction"
placeholder="Type another direction…"
/>
</QuestionnaireChoices>
<QuestionnaireError />
</CardContent>
<CardFooter>
<QuestionnaireNavigation />
</CardFooter>
</Card>
</QuestionnaireItem>
<QuestionnaireItem
aria-labelledby={signalsTitleId}
name="signals"
multiple
>
<Card className="mx-auto w-full max-w-lg">
<CardHeader>
<QuestionnaireTitle id={signalsTitleId} render={<CardTitle />}>
What should every progress update include?
</QuestionnaireTitle>
<QuestionnaireDescription render={<CardDescription />}>
Select all that apply, or skip this question.
</QuestionnaireDescription>
<CardAction>
<QuestionnaireProgress />
</CardAction>
</CardHeader>
<CardContent>
<QuestionnaireChoices>
<QuestionnaireChoice value="progress">
Progress
</QuestionnaireChoice>
<QuestionnaireChoice value="decisions">
Decisions
</QuestionnaireChoice>
<QuestionnaireChoice value="risks">Risks</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</CardContent>
<CardFooter>
<QuestionnaireNavigation />
</CardFooter>
</Card>
</QuestionnaireItem>
<QuestionnaireItem aria-labelledby={timingTitleId} name="timing" required>
<Card className="mx-auto w-full max-w-lg">
<CardHeader>
<QuestionnaireTitle id={timingTitleId} render={<CardTitle />}>
When should this be revisited?
</QuestionnaireTitle>
<QuestionnaireDescription render={<CardDescription />}>
Choose when this should be revisited.
</QuestionnaireDescription>
<CardAction>
<QuestionnaireProgress />
</CardAction>
</CardHeader>
<CardContent>
<QuestionnaireChoices>
<QuestionnaireChoice value="week">This week</QuestionnaireChoice>
<QuestionnaireChoice value="cycle">
Next cycle
</QuestionnaireChoice>
<QuestionnaireChoice value="later">
Revisit later
</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</CardContent>
<CardFooter>
<QuestionnaireNavigation />
</CardFooter>
</Card>
</QuestionnaireItem>
</>
)
}
function QuestionnaireQuestions() {
return (
<>
<QuestionnaireItem name="direction" required>
<QuestionnaireTitle>What should we prototype next?</QuestionnaireTitle>
<QuestionnaireDescription>
Choose one direction or write another answer.
</QuestionnaireDescription>
<QuestionnaireChoices>
<QuestionnaireChoice value="delegation">
<span className="font-medium">Sub-agent delegation</span>
<QuestionnaireChoiceDescription>
Show when work is delegated and what comes back.
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireChoice value="questions">
<span className="font-medium">Question prompts</span>
<QuestionnaireChoiceDescription>
Show choices while the agent waits for input.
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireChoice value="both">
<span className="font-medium">Both together</span>
<QuestionnaireChoiceDescription>
Explore one unified interaction pattern.
</QuestionnaireChoiceDescription>
</QuestionnaireChoice>
<QuestionnaireInput
aria-label="Another direction"
placeholder="Type another direction…"
/>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
<QuestionnaireItem name="signals" multiple>
<QuestionnaireTitle>
What should every progress update include?
</QuestionnaireTitle>
<QuestionnaireDescription>
Select all that apply, or skip this question.
</QuestionnaireDescription>
<QuestionnaireChoices>
<QuestionnaireChoice value="progress">Progress</QuestionnaireChoice>
<QuestionnaireChoice value="decisions">Decisions</QuestionnaireChoice>
<QuestionnaireChoice value="risks">Risks</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
<QuestionnaireItem name="timing" required>
<QuestionnaireTitle>When should this be revisited?</QuestionnaireTitle>
<QuestionnaireDescription>
Choose when this should be revisited.
</QuestionnaireDescription>
<QuestionnaireChoices>
<QuestionnaireChoice value="week">This week</QuestionnaireChoice>
<QuestionnaireChoice value="cycle">Next cycle</QuestionnaireChoice>
<QuestionnaireChoice value="later">Revisit later</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
</>
)
}
function QuestionnaireNavigation() {
return (
<QuestionnaireActions className="w-full">
<QuestionnairePrevious />
<QuestionnaireSkip />
<QuestionnaireNext>Next</QuestionnaireNext>
<QuestionnaireSubmit>Save answers</QuestionnaireSubmit>
</QuestionnaireActions>
)
}
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const values = {
direction: formData.get("direction"),
signals: formData.getAll("signals"),
timing: formData.get("timing"),
}
toast("Questionnaire submitted", {
description: `Direction: ${values.direction ?? "None"} · Progress signals: ${values.signals.join(", ") || "None"} · Timing: ${values.timing ?? "None"}`,
})
}

View File

@@ -0,0 +1,222 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/registry/bases/base/ui/field"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/base/ui/radio-group"
export default function RadioGroupExample() {
return (
<ExampleWrapper>
<RadioGroupBasic />
<RadioGroupWithDescriptions />
<RadioGroupWithFieldSet />
<RadioGroupGrid />
<RadioGroupDisabled />
<RadioGroupInvalid />
</ExampleWrapper>
)
}
function RadioGroupBasic() {
return (
<Example title="Basic">
<RadioGroup defaultValue="comfortable">
<Field orientation="horizontal">
<RadioGroupItem value="default" id="r1" />
<FieldLabel htmlFor="r1" className="font-normal">
Default
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="comfortable" id="r2" />
<FieldLabel htmlFor="r2" className="font-normal">
Comfortable
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="compact" id="r3" />
<FieldLabel htmlFor="r3" className="font-normal">
Compact
</FieldLabel>
</Field>
</RadioGroup>
</Example>
)
}
function RadioGroupWithDescriptions() {
return (
<Example title="With Descriptions">
<RadioGroup defaultValue="plus">
<FieldLabel htmlFor="plus-plan">
<Field orientation="horizontal">
<FieldContent>
<div className="font-medium">Plus</div>
<FieldDescription>
For individuals and small teams
</FieldDescription>
</FieldContent>
<RadioGroupItem value="plus" id="plus-plan" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="pro-plan">
<Field orientation="horizontal">
<FieldContent>
<div className="font-medium">Pro</div>
<FieldDescription>For growing businesses</FieldDescription>
</FieldContent>
<RadioGroupItem value="pro" id="pro-plan" />
</Field>
</FieldLabel>
<FieldLabel htmlFor="enterprise-plan">
<Field orientation="horizontal">
<FieldContent>
<div className="font-medium">Enterprise</div>
<FieldDescription>
For large teams and enterprises
</FieldDescription>
</FieldContent>
<RadioGroupItem value="enterprise" id="enterprise-plan" />
</Field>
</FieldLabel>
</RadioGroup>
</Example>
)
}
function RadioGroupWithFieldSet() {
return (
<Example title="With FieldSet">
<FieldSet>
<FieldLegend>Battery Level</FieldLegend>
<FieldDescription>
Choose your preferred battery level.
</FieldDescription>
<RadioGroup defaultValue="medium">
<Field orientation="horizontal">
<RadioGroupItem value="high" id="battery-high" />
<FieldLabel htmlFor="battery-high" className="font-normal">
High
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="medium" id="battery-medium" />
<FieldLabel htmlFor="battery-medium" className="font-normal">
Medium
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="low" id="battery-low" />
<FieldLabel htmlFor="battery-low" className="font-normal">
Low
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
</Example>
)
}
function RadioGroupGrid() {
return (
<Example title="Grid Layout">
<RadioGroup defaultValue="medium" className="grid grid-cols-2 gap-2">
<FieldLabel htmlFor="size-small">
<Field orientation="horizontal">
<RadioGroupItem value="small" id="size-small" />
<div className="font-medium">Small</div>
</Field>
</FieldLabel>
<FieldLabel htmlFor="size-medium">
<Field orientation="horizontal">
<RadioGroupItem value="medium" id="size-medium" />
<div className="font-medium">Medium</div>
</Field>
</FieldLabel>
<FieldLabel htmlFor="size-large">
<Field orientation="horizontal">
<RadioGroupItem value="large" id="size-large" />
<div className="font-medium">Large</div>
</Field>
</FieldLabel>
<FieldLabel htmlFor="size-xlarge">
<Field orientation="horizontal">
<RadioGroupItem value="xlarge" id="size-xlarge" />
<div className="font-medium">X-Large</div>
</Field>
</FieldLabel>
</RadioGroup>
</Example>
)
}
function RadioGroupDisabled() {
return (
<Example title="Disabled">
<RadioGroup defaultValue="option2" disabled>
<Field orientation="horizontal">
<RadioGroupItem value="option1" id="disabled-1" />
<FieldLabel htmlFor="disabled-1" className="font-normal">
Option 1
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="option2" id="disabled-2" />
<FieldLabel htmlFor="disabled-2" className="font-normal">
Option 2
</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="option3" id="disabled-3" />
<FieldLabel htmlFor="disabled-3" className="font-normal">
Option 3
</FieldLabel>
</Field>
</RadioGroup>
</Example>
)
}
function RadioGroupInvalid() {
return (
<Example title="Invalid">
<FieldSet>
<FieldLegend>Notification Preferences</FieldLegend>
<FieldDescription>
Choose how you want to receive notifications.
</FieldDescription>
<RadioGroup defaultValue="email">
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="email" id="invalid-email" aria-invalid />
<FieldLabel htmlFor="invalid-email" className="font-normal">
Email only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="sms" id="invalid-sms" aria-invalid />
<FieldLabel htmlFor="invalid-sms" className="font-normal">
SMS only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="both" id="invalid-both" aria-invalid />
<FieldLabel htmlFor="invalid-both" className="font-normal">
Both Email & SMS
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
</Example>
)
}

View File

@@ -0,0 +1,158 @@
"use client"
import * as React from "react"
import type { Layout } from "react-resizable-panels"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/registry/bases/base/ui/resizable"
export default function ResizableExample() {
return (
<ExampleWrapper>
<ResizableHorizontal />
<ResizableVertical />
<ResizableWithHandle />
<ResizableNested />
<ResizableControlled />
</ExampleWrapper>
)
}
function ResizableHorizontal() {
return (
<Example title="Horizontal">
<ResizablePanelGroup
orientation="horizontal"
className="min-h-[200px] rounded-lg border"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</Example>
)
}
function ResizableVertical() {
return (
<Example title="Vertical">
<ResizablePanelGroup
orientation="vertical"
className="min-h-[200px] rounded-lg border"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Header</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</Example>
)
}
function ResizableWithHandle() {
return (
<Example title="With Handle">
<ResizablePanelGroup
orientation="horizontal"
className="min-h-[200px] rounded-lg border"
>
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Sidebar</span>
</div>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Content</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</Example>
)
}
function ResizableNested() {
return (
<Example title="Nested">
<ResizablePanelGroup
orientation="horizontal"
className="rounded-lg border"
>
<ResizablePanel defaultSize="50%">
<div className="flex h-[200px] items-center justify-center p-6">
<span className="font-semibold">One</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="50%">
<ResizablePanelGroup orientation="vertical">
<ResizablePanel defaultSize="25%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Two</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="75%">
<div className="flex h-full items-center justify-center p-6">
<span className="font-semibold">Three</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</ResizablePanelGroup>
</Example>
)
}
function ResizableControlled() {
const [layout, setLayout] = React.useState<Layout>({})
return (
<Example title="Controlled">
<ResizablePanelGroup
orientation="horizontal"
className="min-h-[200px] rounded-lg border"
onLayoutChange={setLayout}
>
<ResizablePanel defaultSize="30%" id="left" minSize="20%">
<div className="flex h-full flex-col items-center justify-center gap-2 p-6">
<span className="font-semibold">
{Math.round(layout.left ?? 30)}%
</span>
</div>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel defaultSize="70%" id="right" minSize="30%">
<div className="flex h-full flex-col items-center justify-center gap-2 p-6">
<span className="font-semibold">
{Math.round(layout.right ?? 70)}%
</span>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</Example>
)
}

View File

@@ -0,0 +1,86 @@
import * as React from "react"
import Image from "next/image"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { ScrollArea, ScrollBar } from "@/registry/bases/base/ui/scroll-area"
import { Separator } from "@/registry/bases/base/ui/separator"
const tags = Array.from({ length: 50 }).map(
(_, i, a) => `v1.2.0-beta.${a.length - i}`
)
const works = [
{
artist: "Ornella Binni",
art: "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80",
},
{
artist: "Tom Byrom",
art: "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80",
},
{
artist: "Vladimir Malyav",
art: "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80",
},
] as const
export default function ScrollAreaExample() {
return (
<ExampleWrapper>
<ScrollAreaVertical />
<ScrollAreaHorizontal />
</ExampleWrapper>
)
}
function ScrollAreaVertical() {
return (
<Example title="Vertical">
<ScrollArea className="mx-auto h-72 w-48 rounded-md border style-luma:rounded-2xl style-rhea:rounded-2xl">
<div className="p-4">
<h4 className="mb-4 text-sm leading-none font-medium">Tags</h4>
{tags.map((tag) => (
<React.Fragment key={tag}>
<div className="text-sm">{tag}</div>
<Separator className="my-2" />
</React.Fragment>
))}
</div>
</ScrollArea>
</Example>
)
}
function ScrollAreaHorizontal() {
return (
<Example title="Horizontal">
<ScrollArea className="mx-auto w-full max-w-96 rounded-md border p-4 style-luma:rounded-2xl style-rhea:rounded-2xl">
<div className="flex gap-4">
{works.map((artwork) => (
<figure key={artwork.artist} className="shrink-0">
<div className="overflow-hidden rounded-md">
<Image
src={artwork.art}
alt={`Photo by ${artwork.artist}`}
className="aspect-[3/4] h-fit w-fit object-cover"
width={300}
height={400}
/>
</div>
<figcaption className="pt-2 text-xs text-muted-foreground">
Photo by{" "}
<span className="font-semibold text-foreground">
{artwork.artist}
</span>
</figcaption>
</figure>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</Example>
)
}

View File

@@ -0,0 +1,723 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/base/ui/dialog"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
Item,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/base/ui/item"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/base/ui/native-select"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SelectExample() {
return (
<ExampleWrapper>
<SelectBasic />
<SelectSides />
<SelectWithIcons />
<SelectWithGroups />
<SelectLargeList />
<SelectMultiple />
<SelectSizes />
<SelectPlan />
<SelectWithButton />
<SelectItemAligned />
<SelectWithField />
<SelectInvalid />
<SelectInline />
<SelectDisabled />
<SelectInDialog />
</ExampleWrapper>
)
}
function SelectBasic() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes" },
{ label: "Pineapple", value: "pineapple" },
]
return (
<Example title="Basic">
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectSides() {
const items = [
{ label: "Select", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
]
return (
<Example title="Sides" containerClassName="col-span-2">
<div className="flex flex-wrap justify-center gap-2">
{(
[
"inline-start",
"left",
"top",
"bottom",
"right",
"inline-end",
] as const
).map((side) => (
<Select key={side} items={items}>
<SelectTrigger className="w-28 capitalize">
<SelectValue placeholder={side.replace("-", " ")} />
</SelectTrigger>
<SelectContent side={side} alignItemWithTrigger={false}>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
))}
</div>
</Example>
)
}
function SelectWithIcons() {
const items = [
{
label: (
<>
<IconPlaceholder
lucide="ChartLineIcon"
tabler="IconChartLine"
hugeicons="Chart03Icon"
phosphor="ChartLineIcon"
remixicon="RiLineChartLine"
/>
Chart Type
</>
),
value: null,
},
{
label: (
<>
<IconPlaceholder
lucide="ChartLineIcon"
tabler="IconChartLine"
hugeicons="Chart03Icon"
phosphor="ChartLineIcon"
remixicon="RiLineChartLine"
/>
Line
</>
),
value: "line",
},
{
label: (
<>
<IconPlaceholder
lucide="ChartBarIcon"
tabler="IconChartBar"
hugeicons="Chart03Icon"
phosphor="ChartBarIcon"
remixicon="RiBarChartLine"
/>
Bar
</>
),
value: "bar",
},
{
label: (
<>
<IconPlaceholder
lucide="ChartPieIcon"
tabler="IconChartPie"
hugeicons="Chart03Icon"
phosphor="ChartPieIcon"
remixicon="RiPieChartLine"
/>
Pie
</>
),
value: "pie",
},
]
return (
<Example title="With Icons">
<div className="flex flex-col gap-4">
<Select items={items}>
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Select items={items}>
<SelectTrigger size="default">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</Example>
)
}
function SelectWithGroups() {
const fruits = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
]
const vegetables = [
{ label: "Carrot", value: "carrot" },
{ label: "Broccoli", value: "broccoli" },
{ label: "Spinach", value: "spinach" },
]
const allItems = [
{ label: "Select a fruit", value: null },
...fruits,
...vegetables,
]
return (
<Example title="With Groups & Labels">
<Select items={allItems}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
{vegetables.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectLargeList() {
const items = [
{ label: "Select an item", value: null },
...Array.from({ length: 100 }).map((_, i) => ({
label: `Item ${i}`,
value: `item-${i}`,
})),
]
return (
<Example title="Large List">
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectSizes() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
]
return (
<Example title="Sizes">
<div className="flex flex-col gap-4">
<Select items={items}>
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Select items={items}>
<SelectTrigger size="default">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</Example>
)
}
function SelectWithButton() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
]
return (
<Example title="With Button">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Select items={items}>
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Button variant="outline" size="sm">
Submit
</Button>
</div>
<div className="flex items-center gap-2">
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Button variant="outline">Submit</Button>
</div>
</div>
</Example>
)
}
function SelectItemAligned() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes", disabled: true },
{ label: "Pineapple", value: "pineapple" },
]
return (
<Example title="Item Aligned">
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent alignItemWithTrigger={true}>
<SelectGroup>
{items.map((item) => (
<SelectItem
key={item.value}
value={item.value}
disabled={item.disabled}
>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectWithField() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes" },
{ label: "Pineapple", value: "pineapple" },
]
return (
<Example title="With Field">
<Field>
<FieldLabel htmlFor="select-fruit">Favorite Fruit</FieldLabel>
<Select items={items}>
<SelectTrigger id="select-fruit">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>
Choose your favorite fruit from the list.
</FieldDescription>
</Field>
</Example>
)
}
function SelectInvalid() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes" },
{ label: "Pineapple", value: "pineapple" },
]
return (
<Example title="Invalid">
<div className="flex flex-col gap-4">
<Select items={items}>
<SelectTrigger aria-invalid="true">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Field data-invalid>
<FieldLabel htmlFor="select-fruit-invalid">Favorite Fruit</FieldLabel>
<Select items={items}>
<SelectTrigger id="select-fruit-invalid" aria-invalid>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldError errors={[{ message: "Please select a valid fruit." }]} />
</Field>
</div>
</Example>
)
}
function SelectInline() {
const items = [
{ label: "Filter", value: null },
{ label: "All", value: "all" },
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
]
return (
<Example title="Inline with Input & NativeSelect">
<div className="flex items-center gap-2">
<Input placeholder="Search..." className="flex-1" />
<Select items={items}>
<SelectTrigger className="w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<NativeSelect className="w-[140px]">
<NativeSelectOption value="">Sort by</NativeSelectOption>
<NativeSelectOption value="name">Name</NativeSelectOption>
<NativeSelectOption value="date">Date</NativeSelectOption>
<NativeSelectOption value="status">Status</NativeSelectOption>
</NativeSelect>
</div>
</Example>
)
}
function SelectDisabled() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes", disabled: true },
{ label: "Pineapple", value: "pineapple" },
]
return (
<Example title="Disabled">
<Select items={items} disabled>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem
key={item.value}
value={item.value}
disabled={item.disabled}
>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
const plans = [
{
name: "Starter",
description: "Perfect for individuals getting started.",
},
{
name: "Professional",
description: "Ideal for growing teams and businesses.",
},
{
name: "Enterprise",
description: "Advanced features for large organizations.",
},
]
function SelectPlan() {
return (
<Example title="Subscription Plan">
<Select
defaultValue={plans[0]}
itemToStringValue={(plan: (typeof plans)[number]) => plan.name}
>
<SelectTrigger className="h-auto! w-72">
<SelectValue>
{(value: (typeof plans)[number]) => <SelectPlanItem plan={value} />}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{plans.map((plan) => (
<SelectItem key={plan.name} value={plan}>
<SelectPlanItem plan={plan} />
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectPlanItem({ plan }: { plan: (typeof plans)[number] }) {
return (
<Item size="xs" className="w-full p-0">
<ItemContent className="gap-0 normal-case">
<ItemTitle className="font-sans">{plan.name}</ItemTitle>
<ItemDescription className="text-xs font-normal tracking-normal">
{plan.description}
</ItemDescription>
</ItemContent>
</Item>
)
}
function SelectMultiple() {
const items = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes" },
{ label: "Pineapple", value: "pineapple" },
{ label: "Strawberry", value: "strawberry" },
{ label: "Watermelon", value: "watermelon" },
]
return (
<Example title="Multiple Selection">
<Select items={items} multiple defaultValue={[]}>
<SelectTrigger className="w-72">
<SelectValue>
{(value: string[]) => {
if (value.length === 0) {
return "Select fruits"
}
if (value.length === 1) {
return items.find((item) => item.value === value[0])?.label
}
return `${value.length} fruits selected`
}}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectInDialog() {
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
{ label: "Grapes", value: "grapes" },
{ label: "Pineapple", value: "pineapple" },
]
return (
<Example title="In Dialog">
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Open Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Select Example</DialogTitle>
<DialogDescription>
Use the select below to choose a fruit.
</DialogDescription>
</DialogHeader>
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</DialogContent>
</Dialog>
</Example>
)
}

View File

@@ -0,0 +1,100 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Separator } from "@/registry/bases/base/ui/separator"
export default function SeparatorExample() {
return (
<ExampleWrapper>
<SeparatorHorizontal />
<SeparatorVertical />
<SeparatorVerticalMenu />
<SeparatorInList />
</ExampleWrapper>
)
}
function SeparatorHorizontal() {
return (
<Example title="Horizontal">
<div className="flex flex-col gap-4 text-sm style-lyra:text-xs/relaxed">
<div className="flex flex-col gap-1">
<div className="leading-none font-medium">shadcn/ui</div>
<div className="text-muted-foreground">
The Foundation for your Design System
</div>
</div>
<Separator />
<div>
A set of beautifully designed components that you can customize,
extend, and build on.
</div>
</div>
</Example>
)
}
function SeparatorVertical() {
return (
<Example title="Vertical">
<div className="flex h-5 items-center gap-4 text-sm style-lyra:text-xs/relaxed">
<div>Blog</div>
<Separator orientation="vertical" />
<div>Docs</div>
<Separator orientation="vertical" />
<div>Source</div>
</div>
</Example>
)
}
function SeparatorVerticalMenu() {
return (
<Example title="Vertical Menu">
<div className="flex items-center gap-2 text-sm md:gap-4 style-lyra:text-xs/relaxed">
<div className="flex flex-col gap-1">
<span className="font-medium">Settings</span>
<span className="text-xs text-muted-foreground">
Manage preferences
</span>
</div>
<Separator orientation="vertical" />
<div className="flex flex-col gap-1">
<span className="font-medium">Account</span>
<span className="text-xs text-muted-foreground">
Profile & security
</span>
</div>
<Separator orientation="vertical" />
<div className="flex flex-col gap-1">
<span className="font-medium">Help</span>
<span className="text-xs text-muted-foreground">Support & docs</span>
</div>
</div>
</Example>
)
}
function SeparatorInList() {
return (
<Example title="In List">
<div className="flex flex-col gap-2 text-sm style-lyra:text-xs/relaxed">
<dl className="flex items-center justify-between">
<dt>Item 1</dt>
<dd className="text-muted-foreground">Value 1</dd>
</dl>
<Separator />
<dl className="flex items-center justify-between">
<dt>Item 2</dt>
<dd className="text-muted-foreground">Value 2</dd>
</dl>
<Separator />
<dl className="flex items-center justify-between">
<dt>Item 3</dt>
<dd className="text-muted-foreground">Value 3</dd>
</dl>
</div>
</Example>
)
}

View File

@@ -0,0 +1,138 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/registry/bases/base/ui/sheet"
export default function SheetExample() {
return (
<ExampleWrapper>
<SheetWithForm />
<SheetNoCloseButton />
<SheetWithSides />
</ExampleWrapper>
)
}
function SheetWithForm() {
return (
<Example title="With Form">
<Sheet>
<SheetTrigger render={<Button variant="outline" />}>Open</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</SheetDescription>
</SheetHeader>
<div className="style-vega:px-4 style-nova:px-4 style-lyra:px-4 style-maia:px-6 style-mira:px-6 style-luma:px-6 style-rhea:px-6">
<FieldGroup>
<Field>
<FieldLabel htmlFor="sheet-demo-name">Name</FieldLabel>
<Input id="sheet-demo-name" defaultValue="Pedro Duarte" />
</Field>
<Field>
<FieldLabel htmlFor="sheet-demo-username">Username</FieldLabel>
<Input id="sheet-demo-username" defaultValue="@peduarte" />
</Field>
</FieldGroup>
</div>
<SheetFooter>
<Button type="submit">Save changes</Button>
<SheetClose render={<Button variant="outline" />}>Close</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</Example>
)
}
function SheetNoCloseButton() {
return (
<Example title="No Close Button">
<Sheet>
<SheetTrigger render={<Button variant="outline" />}>
No Close Button
</SheetTrigger>
<SheetContent showCloseButton={false}>
<SheetHeader>
<SheetTitle>No Close Button</SheetTitle>
<SheetDescription>
This sheet doesn&apos;t have a close button in the top-right
corner. You can only close it using the button below.
</SheetDescription>
</SheetHeader>
</SheetContent>
</Sheet>
</Example>
)
}
const SHEET_SIDES = ["top", "right", "bottom", "left"] as const
function SheetWithSides() {
return (
<Example title="Sides">
<div className="flex flex-wrap gap-2">
{SHEET_SIDES.map((side) => (
<Sheet key={side}>
<SheetTrigger
render={<Button variant="outline" className="capitalize" />}
>
{side}
</SheetTrigger>
<SheetContent
side={side}
className="data-[side=bottom]:max-h-[50vh] data-[side=top]:max-h-[50vh]"
>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>
Make changes to your profile here. Click save when you&apos;re
done.
</SheetDescription>
</SheetHeader>
<div className="no-scrollbar overflow-y-auto style-vega:px-4 style-nova:px-4 style-lyra:px-4 style-maia:px-6 style-mira:px-6 style-luma:px-6 style-rhea:px-6">
{Array.from({ length: 10 }).map((_, index) => (
<p
key={index}
className="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
))}
</div>
<SheetFooter>
<Button type="submit">Save changes</Button>
<SheetClose render={<Button variant="outline" />}>
Cancel
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
))}
</div>
</Example>
)
}

View File

@@ -0,0 +1,286 @@
"use client"
import * as React from "react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/base/ui/item"
import { Label } from "@/registry/bases/base/ui/label"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/base/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SidebarExample() {
const data = {
versions: ["1.0.1", "1.1.0-alpha", "2.0.0-beta1"],
navMain: [
{
title: "Getting Started",
url: "#",
items: [
{
title: "Installation",
url: "#",
},
{
title: "Project Structure",
url: "#",
},
],
},
{
title: "Build Your Application",
url: "#",
items: [
{
title: "Routing",
url: "#",
},
{
title: "Data Fetching",
url: "#",
isActive: true,
},
{
title: "Rendering",
url: "#",
},
{
title: "Caching",
url: "#",
},
{
title: "Styling",
url: "#",
},
{
title: "Optimizing",
url: "#",
},
{
title: "Configuring",
url: "#",
},
{
title: "Testing",
url: "#",
},
{
title: "Authentication",
url: "#",
},
{
title: "Deploying",
url: "#",
},
{
title: "Upgrading",
url: "#",
},
{
title: "Examples",
url: "#",
},
],
},
{
title: "API Reference",
url: "#",
items: [
{
title: "Components",
url: "#",
},
{
title: "File Conventions",
url: "#",
},
{
title: "Functions",
url: "#",
},
{
title: "next.config.js Options",
url: "#",
},
{
title: "CLI",
url: "#",
},
{
title: "Edge Runtime",
url: "#",
},
],
},
{
title: "Architecture",
url: "#",
items: [
{
title: "Accessibility",
url: "#",
},
{
title: "Fast Refresh",
url: "#",
},
{
title: "Next.js Compiler",
url: "#",
},
{
title: "Supported Browsers",
url: "#",
},
{
title: "Turbopack",
url: "#",
},
],
},
],
}
const [selectedVersion, setSelectedVersion] = React.useState(data.versions[0])
return (
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuButton
size="lg"
className="data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground"
/>
}
>
<Item className="p-0" size="xs">
<ItemContent>
<ItemTitle className="text-sm">Documentation</ItemTitle>
<ItemDescription>v{selectedVersion}</ItemDescription>
</ItemContent>
<ItemActions>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretUpDownIcon"
remixicon="RiArrowUpDownLine"
/>
</ItemActions>
</Item>
</DropdownMenuTrigger>
<DropdownMenuContent>
{data.versions.map((version) => (
<DropdownMenuItem
key={version}
onSelect={() => setSelectedVersion(version)}
>
v{version}{" "}
{version === selectedVersion && (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="ml-auto"
/>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
<form>
<SidebarGroup className="py-0">
<SidebarGroupContent className="relative">
<Label htmlFor="search" className="sr-only">
Search
</Label>
<SidebarInput
id="search"
placeholder="Search the docs..."
className="pl-8"
/>
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none"
/>
</SidebarGroupContent>
</SidebarGroup>
</form>
</SidebarHeader>
<SidebarContent>
{data.navMain.map((item) => (
<SidebarGroup key={item.title}>
<SidebarGroupLabel>{item.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{item.items.map((subItem) => (
<SidebarMenuItem key={subItem.title}>
<SidebarMenuButton
render={<a href={subItem.url} />}
isActive={subItem.isActive}
>
{subItem.title}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,275 @@
"use client"
import { Button } from "@/registry/bases/base/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/base/ui/card"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { Field } from "@/registry/bases/base/ui/field"
import {
Item,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/base/ui/item"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/base/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SidebarFloatingExample() {
const data = {
navMain: [
{
title: "Getting Started",
url: "#",
items: [
{
title: "Installation",
url: "#",
},
{
title: "Project Structure",
url: "#",
},
],
},
{
title: "Build Your Application",
url: "#",
items: [
{
title: "Routing",
url: "#",
},
{
title: "Data Fetching",
url: "#",
isActive: true,
},
{
title: "Rendering",
url: "#",
},
{
title: "Caching",
url: "#",
},
{
title: "Styling",
url: "#",
},
{
title: "Optimizing",
url: "#",
},
{
title: "Configuring",
url: "#",
},
{
title: "Testing",
url: "#",
},
{
title: "Authentication",
url: "#",
},
{
title: "Deploying",
url: "#",
},
{
title: "Upgrading",
url: "#",
},
{
title: "Examples",
url: "#",
},
],
},
{
title: "API Reference",
url: "#",
items: [
{
title: "Components",
url: "#",
},
{
title: "File Conventions",
url: "#",
},
{
title: "Functions",
url: "#",
},
{
title: "next.config.js Options",
url: "#",
},
{
title: "CLI",
url: "#",
},
{
title: "Edge Runtime",
url: "#",
},
],
},
{
title: "Architecture",
url: "#",
items: [
{
title: "Accessibility",
url: "#",
},
{
title: "Fast Refresh",
url: "#",
},
{
title: "Next.js Compiler",
url: "#",
},
{
title: "Supported Browsers",
url: "#",
},
{
title: "Turbopack",
url: "#",
},
],
},
],
}
return (
<SidebarProvider className="bg-background">
<Sidebar variant="floating">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" render={<a href="#" />}>
<Item className="p-0" size="xs">
<ItemContent>
<ItemTitle className="text-sm">Documentation</ItemTitle>
<ItemDescription>v1.0.0</ItemDescription>
</ItemContent>
</Item>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarMenu>
{data.navMain.map((item) => (
<DropdownMenu key={item.title}>
<SidebarMenuItem>
<DropdownMenuTrigger
render={
<SidebarMenuButton className="data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground" />
}
>
{item.title}{" "}
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
className="ml-auto"
/>
</DropdownMenuTrigger>
{item.items?.length ? (
<DropdownMenuContent side="right" align="start">
<DropdownMenuGroup>
{item.items.map((subItem) => (
<DropdownMenuItem
render={<a href={subItem.url} />}
key={subItem.title}
>
{subItem.title}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
) : null}
</SidebarMenuItem>
</DropdownMenu>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarGroup>
<Card size="sm" className="-mx-2">
<CardHeader>
<CardTitle className="text-sm">
Subscribe to our newsletter
</CardTitle>
<CardDescription>
Opt-in to receive updates and news about the sidebar.
</CardDescription>
</CardHeader>
<CardContent>
<form>
<Field>
<SidebarInput type="email" placeholder="Email" />
<Button
className="w-full bg-sidebar-primary text-sidebar-primary-foreground"
size="sm"
>
Subscribe
</Button>
</Field>
</form>
</CardContent>
</Card>
</SidebarGroup>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,461 @@
"use client"
import * as React from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/base/ui/avatar"
import { Button } from "@/registry/bases/base/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/base/ui/collapsible"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/registry/bases/base/ui/item"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/base/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SidebarIconExample() {
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
teams: [
{
name: "Acme Inc",
plan: "Enterprise",
},
{
name: "Acme Corp.",
plan: "Startup",
},
{
name: "Evil Corp.",
plan: "Free",
},
],
navMain: [
{
title: "Playground",
url: "#",
icon: (
<IconPlaceholder
lucide="TerminalSquareIcon"
tabler="IconTerminal2"
hugeicons="ComputerTerminalIcon"
phosphor="TerminalIcon"
remixicon="RiTerminalBoxLine"
/>
),
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
{
title: "Models",
url: "#",
icon: (
<IconPlaceholder
lucide="BotIcon"
tabler="IconRobot"
hugeicons="RoboticIcon"
phosphor="RobotIcon"
remixicon="RiRobotLine"
/>
),
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: (
<IconPlaceholder
lucide="BookOpen"
tabler="IconBook"
hugeicons="BookOpen02Icon"
phosphor="BookOpenIcon"
remixicon="RiBookOpenLine"
/>
),
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: (
<IconPlaceholder
lucide="Settings2Icon"
tabler="IconSettings"
hugeicons="Settings05Icon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
),
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
projects: [
{
name: "Design Engineering",
url: "#",
icon: (
<IconPlaceholder
lucide="FrameIcon"
tabler="IconFrame"
hugeicons="CropIcon"
phosphor="CropIcon"
remixicon="RiCropLine"
/>
),
},
{
name: "Sales & Marketing",
url: "#",
icon: (
<IconPlaceholder
lucide="PieChartIcon"
tabler="IconChartPie"
hugeicons="PieChartIcon"
phosphor="ChartPieIcon"
remixicon="RiPieChartLine"
/>
),
},
{
name: "Travel",
url: "#",
icon: (
<IconPlaceholder
lucide="MapIcon"
tabler="IconMap"
hugeicons="MapsIcon"
phosphor="MapTrifoldIcon"
remixicon="RiMapLine"
/>
),
},
],
}
const [activeTeam, setActiveTeam] = React.useState(data.teams[0])
return (
<SidebarProvider>
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuButton
size="lg"
className="data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground"
/>
}
>
<Button
size="icon-sm"
render={<span />}
nativeButton={false}
className="size-8"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
>
<rect width="256" height="256" fill="none"></rect>
<line
x1="208"
y1="128"
x2="128"
y2="208"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="32"
></line>
<line
x1="192"
y1="40"
x2="40"
y2="192"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="32"
></line>
</svg>
</Button>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">
{activeTeam.name}
</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretUpDownIcon"
remixicon="RiArrowUpDownLine"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>Teams</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuGroup>
{data.teams.map((team) => (
<DropdownMenuItem
key={team.name}
onClick={() => setActiveTeam(team)}
>
{team.name}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{data.navMain.map((item) => (
<Collapsible
key={item.title}
defaultOpen={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger
render={<SidebarMenuButton tooltip={item.title} />}
>
{item.icon}
<span>{item.title}</span>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
className="ml-auto transition-transform duration-100 group-data-open/collapsible:rotate-90"
/>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton
render={<a href={subItem.url} />}
>
{subItem.title}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
{data.projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton render={<a href={item.url} />}>
{item.icon}
{item.name}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuButton
size="lg"
className="data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground"
/>
}
>
<Avatar>
<AvatarImage src={data.user.avatar} alt={data.user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">
{data.user.name}
</span>
<span className="truncate text-xs">{data.user.email}</span>
</div>
<IconPlaceholder
lucide="ChevronsUpDownIcon"
tabler="IconSelector"
hugeicons="UnfoldMoreIcon"
phosphor="CaretUpDownIcon"
remixicon="RiArrowUpDownLine"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel>
<Item size="xs">
<ItemMedia>
<Avatar>
<AvatarImage
src={data.user.avatar}
alt={data.user.name}
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</ItemMedia>
<ItemContent>
<ItemTitle>{data.user.name}</ItemTitle>
<ItemDescription> {data.user.email}</ItemDescription>
</ItemContent>
</Item>
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>Account</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>Log out</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<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" />
</div>
</header>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,287 @@
"use client"
import * as React from "react"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/base/ui/collapsible"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/base/ui/sidebar"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SidebarInsetExample() {
const data = {
navMain: [
{
title: "Dashboard",
url: "#",
icon: (
<IconPlaceholder
lucide="HomeIcon"
tabler="IconHome"
hugeicons="Home01Icon"
phosphor="HouseIcon"
remixicon="RiHomeLine"
/>
),
isActive: true,
items: [
{
title: "Overview",
url: "#",
},
{
title: "Analytics",
url: "#",
},
],
},
{
title: "Analytics",
url: "#",
icon: (
<IconPlaceholder
lucide="ChartLineIcon"
tabler="IconChartLine"
hugeicons="ChartIcon"
phosphor="ChartLineIcon"
remixicon="RiLineChartLine"
/>
),
items: [
{
title: "Reports",
url: "#",
},
{
title: "Metrics",
url: "#",
},
],
},
{
title: "Orders",
url: "#",
icon: (
<IconPlaceholder
lucide="ShoppingBagIcon"
tabler="IconShoppingBag"
hugeicons="ShoppingBag01Icon"
phosphor="BagIcon"
remixicon="RiShoppingBagLine"
/>
),
items: [
{
title: "All Orders",
url: "#",
},
{
title: "Pending",
url: "#",
},
{
title: "Completed",
url: "#",
},
],
},
{
title: "Products",
url: "#",
icon: (
<IconPlaceholder
lucide="ShoppingCartIcon"
tabler="IconShoppingCart"
hugeicons="ShoppingCart01Icon"
phosphor="ShoppingCartIcon"
remixicon="RiShoppingCartLine"
/>
),
items: [
{
title: "All Products",
url: "#",
},
{
title: "Categories",
url: "#",
},
],
},
{
title: "Invoices",
url: "#",
icon: (
<IconPlaceholder
lucide="FileIcon"
tabler="IconFile"
hugeicons="File01Icon"
phosphor="FileIcon"
remixicon="RiFileLine"
/>
),
},
{
title: "Customers",
url: "#",
icon: (
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
),
},
{
title: "Settings",
url: "#",
icon: (
<IconPlaceholder
lucide="Settings2Icon"
tabler="IconSettings"
hugeicons="Settings05Icon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
),
},
],
navSecondary: [
{
title: "Support",
url: "#",
icon: (
<IconPlaceholder
lucide="LifeBuoy"
tabler="IconLifebuoy"
hugeicons="ChartRingIcon"
phosphor="LifebuoyIcon"
remixicon="RiLifebuoyLine"
/>
),
},
{
title: "Feedback",
url: "#",
icon: (
<IconPlaceholder
lucide="Send"
tabler="IconSend"
hugeicons="SentIcon"
phosphor="PaperPlaneTiltIcon"
remixicon="RiSendPlaneLine"
/>
),
},
],
}
return (
<SidebarProvider>
<Sidebar variant="inset">
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
<SidebarMenu>
{data.navMain.map((item) => (
<Collapsible
key={item.title}
defaultOpen={item.isActive}
render={<SidebarMenuItem />}
>
<SidebarMenuButton
render={<a href={item.url} />}
isActive={item.isActive}
>
{item.icon}
<span>{item.title}</span>
</SidebarMenuButton>
{item.items?.length ? (
<>
<CollapsibleTrigger
render={
<SidebarMenuAction className="data-open:rotate-90" />
}
>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
/>
<span className="sr-only">Toggle</span>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.items.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton
render={<a href={subItem.url} />}
>
{subItem.title}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</>
) : null}
</Collapsible>
))}
</SidebarMenu>
</SidebarGroup>
<SidebarGroup className="mt-auto">
<SidebarGroupContent>
<SidebarMenu>
{data.navSecondary.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton render={<a href={item.url} />} size="sm">
{item.icon}
<span>{item.title}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />
</div>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,102 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Card, CardContent, CardHeader } from "@/registry/bases/base/ui/card"
import { Skeleton } from "@/registry/bases/base/ui/skeleton"
export default function SkeletonExample() {
return (
<ExampleWrapper>
<SkeletonAvatar />
<SkeletonCard />
<SkeletonText />
<SkeletonForm />
<SkeletonTable />
</ExampleWrapper>
)
}
function SkeletonAvatar() {
return (
<Example title="Avatar">
<div className="flex w-full items-center gap-4">
<Skeleton className="size-10 shrink-0 rounded-full" />
<div className="grid gap-2">
<Skeleton className="h-4 w-[150px]" />
<Skeleton className="h-4 w-[100px]" />
</div>
</div>
</Example>
)
}
function SkeletonCard() {
return (
<Example title="Card">
<Card className="w-full">
<CardHeader>
<Skeleton className="h-4 w-2/3" />
<Skeleton className="h-4 w-1/2" />
</CardHeader>
<CardContent>
<Skeleton className="aspect-square w-full" />
</CardContent>
</Card>
</Example>
)
}
function SkeletonText() {
return (
<Example title="Text">
<div className="flex w-full flex-col gap-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>
</Example>
)
}
function SkeletonForm() {
return (
<Example title="Form">
<div className="flex w-full flex-col gap-7">
<div className="flex flex-col gap-3">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
<div className="flex flex-col gap-3">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-10 w-full" />
</div>
<Skeleton className="h-9 w-24" />
</div>
</Example>
)
}
function SkeletonTable() {
return (
<Example title="Table">
<div className="flex w-full flex-col gap-2">
<div className="flex gap-4">
<Skeleton className="h-4 flex-1" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-20" />
</div>
<div className="flex gap-4">
<Skeleton className="h-4 flex-1" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-20" />
</div>
<div className="flex gap-4">
<Skeleton className="h-4 flex-1" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-20" />
</div>
</div>
</Example>
)
}

View File

@@ -0,0 +1,103 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Label } from "@/registry/bases/base/ui/label"
import { Slider } from "@/registry/bases/base/ui/slider"
export default function SliderExample() {
return (
<ExampleWrapper>
<SliderBasic />
<SliderRange />
<SliderMultiple />
<SliderVertical />
<SliderControlled />
<SliderDisabled />
</ExampleWrapper>
)
}
function SliderBasic() {
return (
<Example title="Basic">
<Slider defaultValue={50} max={100} step={1} />
</Example>
)
}
function SliderRange() {
return (
<Example title="Range">
<Slider defaultValue={[25, 50]} max={100} step={5} />
</Example>
)
}
function SliderMultiple() {
return (
<Example title="Multiple Thumbs">
<Slider defaultValue={[10, 20, 70]} max={100} step={10} />
</Example>
)
}
function SliderVertical() {
return (
<Example title="Vertical">
<div className="flex items-center gap-6">
<Slider
defaultValue={[50]}
max={100}
step={1}
orientation="vertical"
className="h-40"
/>
<Slider
defaultValue={[25]}
max={100}
step={1}
orientation="vertical"
className="h-40"
/>
</div>
</Example>
)
}
function SliderControlled() {
const [value, setValue] = React.useState([0.3, 0.7])
return (
<Example title="Controlled">
<div className="grid w-full gap-3">
<div className="flex items-center justify-between gap-2">
<Label htmlFor="slider-demo-temperature">Temperature</Label>
<span className="text-sm text-muted-foreground">
{value.join(", ")}
</span>
</div>
<Slider
id="slider-demo-temperature"
value={value}
onValueChange={(value) => setValue(value as number[])}
min={0}
max={1}
step={0.1}
/>
</div>
</Example>
)
}
function SliderDisabled() {
return (
<Example title="Disabled">
<Slider defaultValue={[50]} max={100} step={1} disabled />
</Example>
)
}

View File

@@ -0,0 +1,50 @@
"use client"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
export default function SonnerExample() {
return (
<ExampleWrapper>
<SonnerBasic />
<SonnerWithDescription />
</ExampleWrapper>
)
}
function SonnerBasic() {
return (
<Example title="Basic" className="items-center justify-center">
<Button
onClick={() => toast("Event has been created")}
variant="outline"
className="w-fit"
>
Show Toast
</Button>
</Example>
)
}
function SonnerWithDescription() {
return (
<Example title="With Description" className="items-center justify-center">
<Button
onClick={() =>
toast("Event has been created", {
description: "Monday, January 3rd at 6:00pm",
})
}
variant="outline"
className="w-fit"
>
Show Toast
</Button>
</Example>
)
}

View File

@@ -0,0 +1,150 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Badge } from "@/registry/bases/base/ui/badge"
import { Button } from "@/registry/bases/base/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/base/ui/empty"
import { Field, FieldLabel } from "@/registry/bases/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/base/ui/input-group"
import { Spinner } from "@/registry/bases/base/ui/spinner"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SpinnerExample() {
return (
<ExampleWrapper>
<SpinnerBasic />
<SpinnerInButtons />
<SpinnerInBadges />
<SpinnerInInputGroup />
<SpinnerInEmpty />
</ExampleWrapper>
)
}
function SpinnerBasic() {
return (
<Example title="Basic">
<div className="flex items-center gap-6">
<Spinner />
<Spinner className="size-6" />
</div>
</Example>
)
}
function SpinnerInButtons() {
return (
<Example title="In Buttons">
<div className="flex flex-wrap items-center gap-4">
<Button>
<Spinner data-icon="inline-start" /> Submit
</Button>
<Button disabled>
<Spinner data-icon="inline-start" /> Disabled
</Button>
<Button variant="outline" disabled>
<Spinner data-icon="inline-start" /> Outline
</Button>
<Button variant="outline" size="icon" disabled>
<Spinner data-icon="inline-start" />
<span className="sr-only">Loading...</span>
</Button>
</div>
</Example>
)
}
function SpinnerInBadges() {
return (
<Example title="In Badges" className="items-center justify-center">
<div className="flex flex-wrap items-center justify-center gap-4">
<Badge>
<Spinner data-icon="inline-start" />
Badge
</Badge>
<Badge variant="secondary">
<Spinner data-icon="inline-start" />
Badge
</Badge>
<Badge variant="destructive">
<Spinner data-icon="inline-start" />
Badge
</Badge>
<Badge variant="outline">
<Spinner data-icon="inline-start" />
Badge
</Badge>
</div>
</Example>
)
}
function SpinnerInInputGroup() {
return (
<Example title="In Input Group">
<Field>
<FieldLabel htmlFor="input-group-spinner">Input Group</FieldLabel>
<InputGroup>
<InputGroupInput id="input-group-spinner" />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
</Field>
</Example>
)
}
function SpinnerInEmpty() {
return (
<Example title="In Empty State" containerClassName="lg:col-span-full">
<Empty className="min-h-[300px]">
<EmptyHeader>
<EmptyMedia variant="icon">
<Spinner />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
You haven&apos;t created any projects yet. Get started by creating
your first project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<div className="flex gap-2">
<Button render={<a href="#" />} nativeButton={false}>
Create project
</Button>
<Button variant="outline">Import project</Button>
</div>
<Button
variant="link"
render={<a href="#" />}
nativeButton={false}
className="text-muted-foreground"
>
Learn more{" "}
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</Button>
</EmptyContent>
</Empty>
</Example>
)
}

View File

@@ -0,0 +1,102 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldTitle,
} from "@/registry/bases/base/ui/field"
import { Label } from "@/registry/bases/base/ui/label"
import { Switch } from "@/registry/bases/base/ui/switch"
export default function SwitchExample() {
return (
<ExampleWrapper>
<SwitchBasic />
<SwitchWithDescription />
<SwitchWithLabel />
<SwitchDisabled />
<SwitchSizes />
</ExampleWrapper>
)
}
function SwitchBasic() {
return (
<Example title="Basic">
<Field orientation="horizontal">
<Switch id="switch-basic" />
<FieldLabel htmlFor="switch-basic">Airplane Mode</FieldLabel>
</Field>
</Example>
)
}
function SwitchWithLabel() {
return (
<Example title="With Label">
<div className="flex items-center gap-2">
<Switch id="switch-bluetooth" defaultChecked />
<Label htmlFor="switch-bluetooth">Bluetooth</Label>
</div>
</Example>
)
}
function SwitchWithDescription() {
return (
<Example title="With Description">
<FieldLabel htmlFor="switch-focus-mode">
<Field orientation="horizontal">
<FieldContent>
<FieldTitle>Share across devices</FieldTitle>
<FieldDescription>
Focus is shared across devices, and turns off when you leave the
app.
</FieldDescription>
</FieldContent>
<Switch id="switch-focus-mode" />
</Field>
</FieldLabel>
</Example>
)
}
function SwitchDisabled() {
return (
<Example title="Disabled">
<div className="flex flex-col gap-12">
<div className="flex items-center gap-2">
<Switch id="switch-disabled-unchecked" disabled />
<Label htmlFor="switch-disabled-unchecked">
Disabled (Unchecked)
</Label>
</div>
<div className="flex items-center gap-2">
<Switch id="switch-disabled-checked" defaultChecked disabled />
<Label htmlFor="switch-disabled-checked">Disabled (Checked)</Label>
</div>
</div>
</Example>
)
}
function SwitchSizes() {
return (
<Example title="Sizes">
<div className="flex flex-col gap-12">
<div className="flex items-center gap-2">
<Switch id="switch-size-sm" size="sm" />
<Label htmlFor="switch-size-sm">Small</Label>
</div>
<div className="flex items-center gap-2">
<Switch id="switch-size-default" size="default" />
<Label htmlFor="switch-size-default">Default</Label>
</div>
</div>
</Example>
)
}

View File

@@ -0,0 +1,484 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { Input } from "@/registry/bases/base/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/base/ui/table"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
const invoices = [
{
invoice: "INV001",
paymentStatus: "Paid",
totalAmount: "$250.00",
paymentMethod: "Credit Card",
},
{
invoice: "INV002",
paymentStatus: "Pending",
totalAmount: "$150.00",
paymentMethod: "PayPal",
},
{
invoice: "INV003",
paymentStatus: "Unpaid",
totalAmount: "$350.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "INV004",
paymentStatus: "Paid",
totalAmount: "$450.00",
paymentMethod: "Credit Card",
},
{
invoice: "INV005",
paymentStatus: "Paid",
totalAmount: "$550.00",
paymentMethod: "PayPal",
},
{
invoice: "INV006",
paymentStatus: "Pending",
totalAmount: "$200.00",
paymentMethod: "Bank Transfer",
},
{
invoice: "INV007",
paymentStatus: "Unpaid",
totalAmount: "$300.00",
paymentMethod: "Credit Card",
},
]
export default function TableExample() {
return (
<ExampleWrapper>
<TableBasic />
<TableWithFooter />
<TableSimple />
<TableWithBadges />
<TableWithActions />
<TableWithSelect />
<TableWithInput />
</ExampleWrapper>
)
}
function TableBasic() {
return (
<Example title="Basic">
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.slice(0, 3).map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-right">
{invoice.totalAmount}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Example>
)
}
function TableWithFooter() {
return (
<Example title="With Footer">
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.slice(0, 3).map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-right">
{invoice.totalAmount}
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
</Example>
)
}
function TableSimple() {
return (
<Example title="Simple">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead className="text-right">Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Sarah Chen</TableCell>
<TableCell>sarah.chen@acme.com</TableCell>
<TableCell className="text-right">Admin</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Marc Rodriguez</TableCell>
<TableCell>marcus.rodriguez@acme.com</TableCell>
<TableCell className="text-right">User</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Emily Watson</TableCell>
<TableCell>emily.watson@acme.com</TableCell>
<TableCell className="text-right">User</TableCell>
</TableRow>
</TableBody>
</Table>
</Example>
)
}
function TableWithBadges() {
return (
<Example title="With Badges">
<Table>
<TableHeader>
<TableRow>
<TableHead>Task</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Priority</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Design homepage</TableCell>
<TableCell>
<span className="inline-flex items-center rounded-full bg-green-500/10 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-400">
Completed
</span>
</TableCell>
<TableCell className="text-right">
<span className="inline-flex items-center rounded-full bg-blue-500/10 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-400">
High
</span>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Implement API</TableCell>
<TableCell>
<span className="inline-flex items-center rounded-full bg-yellow-500/10 px-2 py-1 text-xs font-medium text-yellow-700 dark:text-yellow-400">
In Progress
</span>
</TableCell>
<TableCell className="text-right">
<span className="inline-flex items-center rounded-full bg-gray-500/10 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-400">
Medium
</span>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Write tests</TableCell>
<TableCell>
<span className="inline-flex items-center rounded-full bg-gray-500/10 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-400">
Pending
</span>
</TableCell>
<TableCell className="text-right">
<span className="inline-flex items-center rounded-full bg-gray-500/10 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-400">
Low
</span>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Example>
)
}
function TableWithActions() {
return (
<Example title="With Actions">
<Table>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>Price</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Wireless Mouse</TableCell>
<TableCell>$29.99</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="size-8" />
}
>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">Open menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Mechanical Keyboard</TableCell>
<TableCell>$129.99</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="size-8" />
}
>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">Open menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">USB-C Hub</TableCell>
<TableCell>$49.99</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="size-8" />
}
>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">Open menu</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Example>
)
}
const people = [
{ value: "sarah", label: "Sarah Chen" },
{ value: "marcus", label: "Marc Rodriguez" },
{ value: "emily", label: "Emily Watson" },
{ value: "david", label: "David Kim" },
]
const tasks = [
{
task: "Design homepage",
assignee: "sarah",
status: "In Progress",
},
{
task: "Implement API",
assignee: "marcus",
status: "Pending",
},
{
task: "Write tests",
assignee: "emily",
status: "Not Started",
},
]
function TableWithSelect() {
return (
<Example title="With Select">
<Table>
<TableHeader>
<TableRow>
<TableHead>Task</TableHead>
<TableHead>Assignee</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tasks.map((item) => (
<TableRow key={item.task}>
<TableCell className="font-medium">{item.task}</TableCell>
<TableCell>
<Select
items={people}
defaultValue={people.find(
(person) => person.value === item.assignee
)}
itemToStringValue={(item) => {
return item.value
}}
>
<SelectTrigger className="w-40" size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{people.map((person) => (
<SelectItem key={person.value} value={person}>
{person.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</TableCell>
<TableCell>{item.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Example>
)
}
function TableWithInput() {
return (
<Example title="With Input">
<Table>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>Quantity</TableHead>
<TableHead>Price</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Wireless Mouse</TableCell>
<TableCell>
<Input
type="number"
defaultValue="1"
className="h-8 w-20"
min="0"
/>
</TableCell>
<TableCell>$29.99</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Mechanical Keyboard</TableCell>
<TableCell>
<Input
type="number"
defaultValue="2"
className="h-8 w-20"
min="0"
/>
</TableCell>
<TableCell>$129.99</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">USB-C Hub</TableCell>
<TableCell>
<Input
type="number"
defaultValue="1"
className="h-8 w-20"
min="0"
/>
</TableCell>
<TableCell>$49.99</TableCell>
</TableRow>
</TableBody>
</Table>
</Example>
)
}

View File

@@ -0,0 +1,359 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/base/ui/dropdown-menu"
import { Input } from "@/registry/bases/base/ui/input"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/base/ui/tabs"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function TabsExample() {
return (
<ExampleWrapper>
<TabsBasic />
<TabsLine />
<TabsVariantsComparison />
<TabsDisabled />
<TabsWithIcons />
<TabsIconOnly />
<TabsMultiple />
<TabsWithContent />
<TabsLineWithContent />
<TabsLineDisabled />
<TabsWithDropdown />
<TabsVertical />
<TabsWithInputAndButton />
</ExampleWrapper>
)
}
function TabsBasic() {
return (
<Example title="Basic">
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home">Home</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsLine() {
return (
<Example title="Line">
<Tabs defaultValue="overview">
<TabsList variant="line">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsVariantsComparison() {
return (
<Example title="Variants Alignment">
<div className="flex gap-4">
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
</Tabs>
<Tabs defaultValue="overview">
<TabsList variant="line">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
</Tabs>
</div>
</Example>
)
}
function TabsDisabled() {
return (
<Example title="Disabled">
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home">Home</TabsTrigger>
<TabsTrigger value="settings" disabled>
Disabled
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsWithIcons() {
return (
<Example title="With Icons">
<Tabs defaultValue="preview">
<TabsList>
<TabsTrigger value="preview">
<IconPlaceholder
lucide="AppWindowIcon"
tabler="IconAppWindow"
hugeicons="CursorInWindowIcon"
phosphor="AppWindowIcon"
remixicon="RiWindowLine"
/>
Preview
</TabsTrigger>
<TabsTrigger value="code">
<IconPlaceholder
lucide="CodeIcon"
tabler="IconCode"
hugeicons="CodeIcon"
phosphor="CodeIcon"
remixicon="RiCodeLine"
/>
Code
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsIconOnly() {
return (
<Example title="Icon Only">
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home">
<IconPlaceholder
lucide="HomeIcon"
tabler="IconHome"
hugeicons="HomeIcon"
phosphor="HouseIcon"
remixicon="RiHomeLine"
/>
</TabsTrigger>
<TabsTrigger value="search">
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</TabsTrigger>
<TabsTrigger value="settings">
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsMultiple() {
return (
<Example title="Multiple">
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsWithContent() {
return (
<Example title="With Content">
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<div className="border style-vega:rounded-lg style-vega:p-6 style-nova:rounded-lg style-nova:p-4 style-lyra:rounded-none style-lyra:p-4 style-maia:rounded-xl style-maia:p-6 style-mira:rounded-md style-mira:p-4 style-luma:rounded-xl style-luma:p-6 style-rhea:rounded-xl style-rhea:p-6">
<TabsContent value="account">
Manage your account preferences and profile information.
</TabsContent>
<TabsContent value="password">
Update your password to keep your account secure.
</TabsContent>
<TabsContent value="notifications">
Configure how you receive notifications and alerts.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsLineWithContent() {
return (
<Example title="Line With Content">
<Tabs defaultValue="account">
<TabsList variant="line">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<div className="border style-vega:rounded-lg style-vega:p-6 style-nova:rounded-lg style-nova:p-4 style-lyra:rounded-none style-lyra:p-4 style-maia:rounded-xl style-maia:p-6 style-mira:rounded-md style-mira:p-4 style-luma:rounded-xl style-luma:p-6 style-rhea:rounded-xl style-rhea:p-6">
<TabsContent value="account">
Manage your account preferences and profile information.
</TabsContent>
<TabsContent value="password">
Update your password to keep your account secure.
</TabsContent>
<TabsContent value="notifications">
Configure how you receive notifications and alerts.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsLineDisabled() {
return (
<Example title="Line Disabled">
<Tabs defaultValue="overview">
<TabsList variant="line">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports" disabled>
Reports
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsWithDropdown() {
return (
<Example title="With Dropdown">
<Tabs defaultValue="overview">
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
</TabsList>
<DropdownMenu>
<DropdownMenuTrigger
render={<Button variant="ghost" size="icon" className="size-8" />}
>
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
/>
<span className="sr-only">More options</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Export</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Archive</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="border style-vega:rounded-lg style-vega:p-6 style-nova:rounded-lg style-nova:p-4 style-lyra:rounded-none style-lyra:p-4 style-maia:rounded-xl style-maia:p-6 style-mira:rounded-md style-mira:p-4 style-luma:rounded-xl style-luma:p-6 style-rhea:rounded-xl style-rhea:p-6">
<TabsContent value="overview">
View your dashboard metrics and key performance indicators.
</TabsContent>
<TabsContent value="analytics">
Detailed analytics and insights about your data.
</TabsContent>
<TabsContent value="reports">
Generate and view custom reports.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsVertical() {
return (
<Example title="Vertical">
<Tabs defaultValue="account" orientation="vertical">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<div className="border style-vega:rounded-lg style-vega:p-6 style-nova:rounded-lg style-nova:p-4 style-lyra:rounded-none style-lyra:p-4 style-maia:rounded-xl style-maia:p-6 style-mira:rounded-md style-mira:p-4 style-luma:rounded-xl style-luma:p-6 style-rhea:rounded-xl style-rhea:p-6">
<TabsContent value="account">
Manage your account preferences and profile information.
</TabsContent>
<TabsContent value="password">
Update your password to keep your account secure. Use a strong
password with a mix of letters, numbers, and symbols.
</TabsContent>
<TabsContent value="notifications">
Configure how you receive notifications and alerts. Choose which
types of notifications you want to receive and how you want to
receive them.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsWithInputAndButton() {
return (
<Example title="With Input and Button" containerClassName="col-span-full">
<Tabs defaultValue="overview" className="mx-auto w-full max-w-lg">
<div className="flex items-center gap-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
<div className="ml-auto flex items-center gap-2">
<Input placeholder="Search..." className="w-44" />
<Button>Action</Button>
</div>
</div>
<div className="border style-vega:rounded-lg style-vega:p-6 style-nova:rounded-lg style-nova:p-4 style-lyra:rounded-none style-lyra:p-4 style-maia:rounded-xl style-maia:p-6 style-mira:rounded-md style-mira:p-4 style-luma:rounded-xl style-luma:p-6 style-rhea:rounded-xl style-rhea:p-6">
<TabsContent value="overview">
View your dashboard metrics and key performance indicators.
</TabsContent>
<TabsContent value="analytics">
Detailed analytics and insights about your data.
</TabsContent>
<TabsContent value="reports">
Generate and view custom reports.
</TabsContent>
</div>
</Tabs>
</Example>
)
}

View File

@@ -0,0 +1,86 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import { Textarea } from "@/registry/bases/base/ui/textarea"
export default function TextareaExample() {
return (
<ExampleWrapper>
<TextareaBasic />
<TextareaInvalid />
<TextareaWithLabel />
<TextareaWithDescription />
<TextareaDisabled />
</ExampleWrapper>
)
}
function TextareaBasic() {
return (
<Example title="Basic">
<Textarea placeholder="Type your message here." />
</Example>
)
}
function TextareaInvalid() {
return (
<Example title="Invalid">
<Textarea placeholder="Type your message here." aria-invalid="true" />
</Example>
)
}
function TextareaWithLabel() {
return (
<Example title="With Label">
<Field>
<FieldLabel htmlFor="textarea-demo-message">Message</FieldLabel>
<Textarea
id="textarea-demo-message"
placeholder="Type your message here."
rows={6}
/>
</Field>
</Example>
)
}
function TextareaWithDescription() {
return (
<Example title="With Description">
<Field>
<FieldLabel htmlFor="textarea-demo-message-2">Message</FieldLabel>
<Textarea
id="textarea-demo-message-2"
placeholder="Type your message here."
rows={6}
/>
<FieldDescription>
Type your message and press enter to send.
</FieldDescription>
</Field>
</Example>
)
}
function TextareaDisabled() {
return (
<Example title="Disabled">
<Field>
<FieldLabel htmlFor="textarea-demo-disabled">Message</FieldLabel>
<Textarea
id="textarea-demo-disabled"
placeholder="Type your message here."
disabled
/>
</Field>
</Example>
)
}

View File

@@ -0,0 +1,86 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { toast } from "@/registry/bases/base/ui/toast"
export default function ToastExample() {
return (
<ExampleWrapper>
<ToastBasic />
<ToastWithAction />
<ToastPromise />
</ExampleWrapper>
)
}
function ToastBasic() {
return (
<Example title="Basic" className="items-center justify-center">
<Button
variant="outline"
className="w-fit"
onClick={() =>
toast.add({
title: "Event created",
description: "Sunday, December 3 at 9:00 AM",
})
}
>
Show Toast
</Button>
</Example>
)
}
function ToastWithAction() {
function showToast() {
const id = toast.add({
title: "Event created",
description: "You can undo this action.",
actionProps: {
children: "Undo",
onClick() {
toast.close(id)
toast.add({
description: "Event creation undone.",
})
},
},
})
}
return (
<Example title="With Action" className="items-center justify-center">
<Button variant="outline" className="w-fit" onClick={showToast}>
Show Toast
</Button>
</Example>
)
}
function ToastPromise() {
function showToast() {
toast.promise(
new Promise<{ name: string }>((resolve) => {
window.setTimeout(() => resolve({ name: "Event" }), 2000)
}),
{
loading: "Creating event…",
success: (data) => `${data.name} created.`,
error: "Could not create event.",
}
)
}
return (
<Example title="Promise" className="items-center justify-center">
<Button variant="outline" className="w-fit" onClick={showToast}>
Create Event
</Button>
</Example>
)
}

View File

@@ -0,0 +1,338 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { Toggle } from "@/registry/bases/base/ui/toggle"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function ToggleExample() {
return (
<ExampleWrapper>
<ToggleBasic />
<ToggleOutline />
<ToggleSizes />
<ToggleWithButtonText />
<ToggleWithButtonIcon />
<ToggleWithButtonIconText />
<ToggleDisabled />
<ToggleWithIcon />
</ExampleWrapper>
)
}
function ToggleBasic() {
return (
<Example title="Basic">
<div className="flex flex-wrap items-center gap-2">
<Toggle aria-label="Toggle bold" defaultPressed>
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</Toggle>
<Toggle aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</Toggle>
<Toggle aria-label="Toggle underline">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</Toggle>
</div>
</Example>
)
}
function ToggleOutline() {
return (
<Example title="Outline">
<div className="flex flex-wrap items-center gap-2">
<Toggle variant="outline" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
Italic
</Toggle>
<Toggle variant="outline" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
Bold
</Toggle>
</div>
</Example>
)
}
function ToggleSizes() {
return (
<Example title="Sizes">
<div className="flex flex-wrap items-center gap-2">
<Toggle variant="outline" aria-label="Toggle small" size="sm">
Small
</Toggle>
<Toggle variant="outline" aria-label="Toggle default" size="default">
Default
</Toggle>
<Toggle variant="outline" aria-label="Toggle large" size="lg">
Large
</Toggle>
</div>
</Example>
)
}
function ToggleWithButtonText() {
return (
<Example title="With Button Text">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Button size="sm" variant="outline">
Button
</Button>
<Toggle variant="outline" aria-label="Toggle sm" size="sm">
Toggle
</Toggle>
</div>
<div className="flex items-center gap-2">
<Button size="default" variant="outline">
Button
</Button>
<Toggle variant="outline" aria-label="Toggle default" size="default">
Toggle
</Toggle>
</div>
<div className="flex items-center gap-2">
<Button size="lg" variant="outline">
Button
</Button>
<Toggle variant="outline" aria-label="Toggle lg" size="lg">
Toggle
</Toggle>
</div>
</div>
</Example>
)
}
function ToggleWithButtonIcon() {
return (
<Example title="With Button Icon">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Button variant="outline" size="icon-sm">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</Button>
<Toggle variant="outline" aria-label="Toggle sm icon" size="sm">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</Toggle>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</Button>
<Toggle
variant="outline"
aria-label="Toggle default icon"
size="default"
>
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</Toggle>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="icon-lg">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</Button>
<Toggle variant="outline" aria-label="Toggle lg icon" size="lg">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</Toggle>
</div>
</div>
</Example>
)
}
function ToggleWithButtonIconText() {
return (
<Example title="With Button Icon + Text">
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Button size="sm" variant="outline">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
data-icon="inline-start"
/>
Button
</Button>
<Toggle variant="outline" aria-label="Toggle sm icon text" size="sm">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
Toggle
</Toggle>
</div>
<div className="flex items-center gap-2">
<Button size="default" variant="outline">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
data-icon="inline-start"
/>
Button
</Button>
<Toggle
variant="outline"
aria-label="Toggle default icon text"
size="default"
>
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
Toggle
</Toggle>
</div>
<div className="flex items-center gap-2">
<Button size="lg" variant="outline">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
data-icon="inline-start"
/>
Button
</Button>
<Toggle variant="outline" aria-label="Toggle lg icon text" size="lg">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
Toggle
</Toggle>
</div>
</div>
</Example>
)
}
function ToggleDisabled() {
return (
<Example title="Disabled">
<div className="flex flex-wrap items-center gap-2">
<Toggle aria-label="Toggle disabled" disabled>
Disabled
</Toggle>
<Toggle variant="outline" aria-label="Toggle disabled outline" disabled>
Disabled
</Toggle>
</div>
</Example>
)
}
function ToggleWithIcon() {
return (
<Example title="With Icon">
<div className="flex flex-wrap items-center gap-2">
<Toggle aria-label="Toggle bookmark" defaultPressed>
<IconPlaceholder
lucide="BookmarkIcon"
tabler="IconBookmark"
hugeicons="BookmarkIcon"
phosphor="BookmarkIcon"
remixicon="RiBookmarkLine"
className="group-data-[state=on]/toggle:fill-accent-foreground"
/>
</Toggle>
<Toggle variant="outline" aria-label="Toggle bookmark outline">
<IconPlaceholder
lucide="BookmarkIcon"
tabler="IconBookmark"
hugeicons="BookmarkIcon"
phosphor="BookmarkIcon"
remixicon="RiBookmarkLine"
className="group-data-[state=on]/toggle:fill-accent-foreground"
/>
Bookmark
</Toggle>
</div>
</Example>
)
}

View File

@@ -0,0 +1,550 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/base/ui/field"
import { Input } from "@/registry/bases/base/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/base/ui/select"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/base/ui/toggle-group"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function ToggleGroupExample() {
return (
<ExampleWrapper>
<ToggleGroupBasic />
<ToggleGroupOutline />
<ToggleGroupOutlineWithIcons />
<ToggleGroupSizes />
<ToggleGroupSpacing />
<ToggleGroupWithIcons />
<ToggleGroupFilter />
<ToggleGroupDateRange />
<ToggleGroupSort />
<ToggleGroupWithInputAndSelect />
<ToggleGroupVertical />
<ToggleGroupVerticalOutline />
<ToggleGroupVerticalOutlineWithIcons />
<ToggleGroupVerticalWithSpacing />
<ToggleGroupFontWeightSelector />
</ExampleWrapper>
)
}
function ToggleGroupBasic() {
return (
<Example title="Basic">
<ToggleGroup multiple spacing={1}>
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupOutline() {
return (
<Example title="Outline">
<ToggleGroup variant="outline" defaultValue={["all"]}>
<ToggleGroupItem value="all" aria-label="Toggle all">
All
</ToggleGroupItem>
<ToggleGroupItem value="missed" aria-label="Toggle missed">
Missed
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupOutlineWithIcons() {
return (
<Example title="Outline With Icons">
<ToggleGroup variant="outline" multiple size="sm">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupSizes() {
return (
<Example title="Sizes">
<div className="flex flex-col gap-4">
<ToggleGroup size="sm" defaultValue={["top"]} variant="outline">
<ToggleGroupItem value="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem value="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem value="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem value="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
<ToggleGroup defaultValue={["top"]} variant="outline">
<ToggleGroupItem value="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem value="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem value="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem value="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
</div>
</Example>
)
}
function ToggleGroupSpacing() {
return (
<Example title="With Spacing">
<ToggleGroup size="sm" defaultValue={["top"]} spacing={2}>
<ToggleGroupItem value="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem value="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem value="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem value="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupWithIcons() {
return (
<Example title="With Icons">
<ToggleGroup multiple variant="outline" spacing={2} size="sm">
<ToggleGroupItem
value="star"
aria-label="Toggle star"
className="aria-pressed:bg-transparent aria-pressed:*:[svg]:fill-foreground aria-pressed:*:[svg]:stroke-foreground"
>
<IconPlaceholder
lucide="StarIcon"
tabler="IconStar"
hugeicons="StarIcon"
phosphor="StarIcon"
remixicon="RiStarLine"
/>
Star
</ToggleGroupItem>
<ToggleGroupItem
value="heart"
aria-label="Toggle heart"
className="aria-pressed:bg-transparent aria-pressed:*:[svg]:fill-foreground aria-pressed:*:[svg]:stroke-foreground"
>
<IconPlaceholder
lucide="HeartIcon"
tabler="IconHeart"
hugeicons="FavouriteIcon"
phosphor="HeartIcon"
remixicon="RiHeartLine"
/>
Heart
</ToggleGroupItem>
<ToggleGroupItem
value="bookmark"
aria-label="Toggle bookmark"
className="aria-pressed:bg-transparent aria-pressed:*:[svg]:fill-foreground aria-pressed:*:[svg]:stroke-foreground"
>
<IconPlaceholder
lucide="BookmarkIcon"
tabler="IconBookmark"
hugeicons="BookmarkIcon"
phosphor="BookmarkIcon"
remixicon="RiBookmarkLine"
/>
Bookmark
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupFilter() {
return (
<Example title="Filter">
<ToggleGroup defaultValue={["all"]} variant="outline" size="sm">
<ToggleGroupItem value="all" aria-label="All">
All
</ToggleGroupItem>
<ToggleGroupItem value="active" aria-label="Active">
Active
</ToggleGroupItem>
<ToggleGroupItem value="completed" aria-label="Completed">
Completed
</ToggleGroupItem>
<ToggleGroupItem value="archived" aria-label="Archived">
Archived
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupDateRange() {
return (
<Example title="Date Range">
<ToggleGroup
defaultValue={["today"]}
variant="outline"
size="sm"
spacing={2}
>
<ToggleGroupItem value="today" aria-label="Today">
Today
</ToggleGroupItem>
<ToggleGroupItem value="week" aria-label="This Week">
This Week
</ToggleGroupItem>
<ToggleGroupItem value="month" aria-label="This Month">
This Month
</ToggleGroupItem>
<ToggleGroupItem value="year" aria-label="This Year">
This Year
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupSort() {
return (
<Example title="Sort">
<ToggleGroup defaultValue={["newest"]} variant="outline" size="sm">
<ToggleGroupItem value="newest" aria-label="Newest">
<IconPlaceholder
lucide="ArrowDownIcon"
tabler="IconArrowDown"
hugeicons="ArrowDownIcon"
phosphor="ArrowDownIcon"
remixicon="RiArrowDownLine"
/>
Newest
</ToggleGroupItem>
<ToggleGroupItem value="oldest" aria-label="Oldest">
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUpIcon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
Oldest
</ToggleGroupItem>
<ToggleGroupItem value="popular" aria-label="Popular">
<IconPlaceholder
lucide="TrendingUpIcon"
tabler="IconTrendingUp"
hugeicons="TradeUpIcon"
phosphor="TrendUpIcon"
remixicon="RiLineChartLine"
/>
Popular
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupWithInputAndSelect() {
const items = [
{ label: "All", value: "all" },
{ label: "Active", value: "active" },
{ label: "Archived", value: "archived" },
]
return (
<Example title="With Input and Select">
<div className="flex items-center gap-2">
<Input type="search" placeholder="Search..." className="flex-1" />
<Select items={items} defaultValue={items[0]}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<ToggleGroup defaultValue={["grid"]} variant="outline">
<ToggleGroupItem value="grid" aria-label="Grid view">
Grid
</ToggleGroupItem>
<ToggleGroupItem value="list" aria-label="List view">
List
</ToggleGroupItem>
</ToggleGroup>
</div>
</Example>
)
}
function ToggleGroupVertical() {
return (
<Example title="Vertical">
<ToggleGroup multiple orientation="vertical" spacing={1}>
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupVerticalOutline() {
return (
<Example title="Vertical Outline">
<ToggleGroup
variant="outline"
defaultValue={["all"]}
orientation="vertical"
size="sm"
>
<ToggleGroupItem value="all" aria-label="Toggle all">
All
</ToggleGroupItem>
<ToggleGroupItem value="active" aria-label="Toggle active">
Active
</ToggleGroupItem>
<ToggleGroupItem value="completed" aria-label="Toggle completed">
Completed
</ToggleGroupItem>
<ToggleGroupItem value="archived" aria-label="Toggle archived">
Archived
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupVerticalOutlineWithIcons() {
return (
<Example title="Vertical Outline With Icons">
<ToggleGroup variant="outline" multiple orientation="vertical" size="sm">
<ToggleGroupItem value="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="Toggle underline">
<IconPlaceholder
lucide="UnderlineIcon"
tabler="IconUnderline"
hugeicons="TextUnderlineIcon"
phosphor="TextUnderlineIcon"
remixicon="RiUnderline"
/>
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupFontWeightSelector() {
const [fontWeight, setFontWeight] = React.useState("normal")
return (
<Example title="Font Weight Selector">
<Field>
<FieldLabel>Font Weight</FieldLabel>
<ToggleGroup
value={[fontWeight]}
onValueChange={(value) => setFontWeight(value[0])}
variant="outline"
spacing={2}
size="lg"
>
<ToggleGroupItem
value="light"
aria-label="Light"
className="flex size-16 flex-col items-center justify-center rounded-xl"
>
<span className="text-2xl leading-none font-light">Aa</span>
<span className="text-xs text-muted-foreground">Light</span>
</ToggleGroupItem>
<ToggleGroupItem
value="normal"
aria-label="Normal"
className="flex size-16 flex-col items-center justify-center rounded-xl"
>
<span className="text-2xl leading-none font-normal">Aa</span>
<span className="text-xs text-muted-foreground">Normal</span>
</ToggleGroupItem>
<ToggleGroupItem
value="medium"
aria-label="Medium"
className="flex size-16 flex-col items-center justify-center rounded-xl"
>
<span className="text-2xl leading-none font-medium">Aa</span>
<span className="text-xs text-muted-foreground">Medium</span>
</ToggleGroupItem>
<ToggleGroupItem
value="bold"
aria-label="Bold"
className="flex size-16 flex-col items-center justify-center rounded-xl"
>
<span className="text-2xl leading-none font-bold">Aa</span>
<span className="text-xs text-muted-foreground">Bold</span>
</ToggleGroupItem>
</ToggleGroup>
<FieldDescription>
Use{" "}
<code className="rounded-md bg-muted px-1 py-0.5 font-mono">
font-{fontWeight}
</code>{" "}
to set the font weight.
</FieldDescription>
</Field>
</Example>
)
}
function ToggleGroupVerticalWithSpacing() {
return (
<Example title="Vertical With Spacing">
<ToggleGroup
size="sm"
defaultValue={["top"]}
orientation="vertical"
spacing={1}
>
<ToggleGroupItem value="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem value="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem value="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem value="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}

View File

@@ -0,0 +1,191 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/base/components/example"
import { Button } from "@/registry/bases/base/ui/button"
import { Kbd } from "@/registry/bases/base/ui/kbd"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/bases/base/ui/tooltip"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function TooltipExample() {
return (
<ExampleWrapper>
<TooltipBasic />
<TooltipSides />
<TooltipWithIcon />
<TooltipLongContent />
<TooltipDisabled />
<TooltipWithKeyboard />
<TooltipOnLink />
<TooltipFormatted />
</ExampleWrapper>
)
}
function TooltipBasic() {
return (
<Example title="Basic">
<Tooltip>
<TooltipTrigger render={<Button variant="outline" className="w-fit" />}>
Show Tooltip
</TooltipTrigger>
<TooltipContent>
<p>Add to library</p>
</TooltipContent>
</Tooltip>
</Example>
)
}
function TooltipSides() {
return (
<Example title="Sides">
<div className="flex flex-wrap gap-2">
{(
[
"inline-start",
"left",
"top",
"bottom",
"right",
"inline-end",
] as const
).map((side) => (
<Tooltip key={side}>
<TooltipTrigger
render={<Button variant="outline" className="w-fit capitalize" />}
>
{side.replace("-", " ")}
</TooltipTrigger>
<TooltipContent side={side}>
<p>Add to library</p>
</TooltipContent>
</Tooltip>
))}
</div>
</Example>
)
}
function TooltipWithIcon() {
return (
<Example title="With Icon">
<Tooltip>
<TooltipTrigger render={<Button variant="ghost" size="icon" />}>
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
<span className="sr-only">Info</span>
</TooltipTrigger>
<TooltipContent>
<p>Additional information</p>
</TooltipContent>
</Tooltip>
</Example>
)
}
function TooltipLongContent() {
return (
<Example title="Long Content">
<Tooltip>
<TooltipTrigger render={<Button variant="outline" className="w-fit" />}>
Show Tooltip
</TooltipTrigger>
<TooltipContent>
To learn more about how this works, check out the docs. If you have
any questions, please reach out to us.
</TooltipContent>
</Tooltip>
</Example>
)
}
function TooltipDisabled() {
return (
<Example title="Disabled">
<Tooltip>
<TooltipTrigger render={<span className="inline-block w-fit" />}>
<Button variant="outline" disabled>
Disabled
</Button>
</TooltipTrigger>
<TooltipContent>
<p>This feature is currently unavailable</p>
</TooltipContent>
</Tooltip>
</Example>
)
}
function TooltipWithKeyboard() {
return (
<Example title="With Keyboard Shortcut">
<Tooltip>
<TooltipTrigger render={<Button variant="outline" size="icon-sm" />}>
<IconPlaceholder
lucide="SaveIcon"
tabler="IconDeviceFloppy"
hugeicons="FloppyDiskIcon"
phosphor="FloppyDiskIcon"
remixicon="RiSaveLine"
/>
</TooltipTrigger>
<TooltipContent>
Save Changes <Kbd>S</Kbd>
</TooltipContent>
</Tooltip>
</Example>
)
}
function TooltipOnLink() {
return (
<Example title="On Link">
<Tooltip>
<TooltipTrigger
render={
<a
href="#"
className="w-fit text-sm text-primary underline-offset-4 hover:underline"
onClick={(e) => e.preventDefault()}
/>
}
>
Learn more
</TooltipTrigger>
<TooltipContent>
<p>Click to read the documentation</p>
</TooltipContent>
</Tooltip>
</Example>
)
}
function TooltipFormatted() {
return (
<Example title="Formatted Content">
<Tooltip>
<TooltipTrigger render={<Button variant="outline" className="w-fit" />}>
Status
</TooltipTrigger>
<TooltipContent>
<div className="flex flex-col gap-1">
<p className="font-semibold">Active</p>
<p className="text-xs opacity-80">Last updated 2 hours ago</p>
</div>
</TooltipContent>
</Tooltip>
</Example>
)
}