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

View File

@@ -0,0 +1,981 @@
import { type Registry } from "shadcn/schema"
export const examples: Registry["items"] = [
{
name: "accordion-example",
title: "Accordion",
type: "registry:example",
registryDependencies: ["accordion", "button", "card", "example"],
files: [
{
path: "examples/accordion-example.tsx",
type: "registry:example",
},
],
},
{
name: "alert-example",
title: "Alert",
type: "registry:example",
registryDependencies: ["alert", "badge", "example"],
files: [
{
path: "examples/alert-example.tsx",
type: "registry:example",
},
],
},
{
name: "alert-dialog-example",
title: "Alert Dialog",
type: "registry:example",
registryDependencies: ["alert-dialog", "button", "dialog", "example"],
files: [
{
path: "examples/alert-dialog-example.tsx",
type: "registry:example",
},
],
},
{
name: "aspect-ratio-example",
title: "Aspect Ratio",
type: "registry:example",
registryDependencies: ["aspect-ratio", "example"],
files: [
{
path: "examples/aspect-ratio-example.tsx",
type: "registry:example",
},
],
},
{
name: "avatar-example",
title: "Avatar",
type: "registry:example",
registryDependencies: ["avatar", "button", "empty", "example"],
files: [
{
path: "examples/avatar-example.tsx",
type: "registry:example",
},
],
},
{
name: "badge-example",
title: "Badge",
type: "registry:example",
registryDependencies: ["badge", "spinner", "example"],
files: [
{
path: "examples/badge-example.tsx",
type: "registry:example",
},
],
},
{
name: "breadcrumb-example",
title: "Breadcrumb",
type: "registry:example",
registryDependencies: ["breadcrumb", "dropdown-menu", "example"],
files: [
{
path: "examples/breadcrumb-example.tsx",
type: "registry:example",
},
],
},
{
name: "button-example",
title: "Button",
type: "registry:example",
registryDependencies: ["button", "example"],
files: [
{
path: "examples/button-example.tsx",
type: "registry:example",
},
],
},
{
name: "button-group-example",
title: "Button Group",
type: "registry:example",
registryDependencies: [
"button",
"button-group",
"dropdown-menu",
"field",
"input",
"input-group",
"label",
"popover",
"select",
"tooltip",
"example",
],
files: [
{
path: "examples/button-group-example.tsx",
type: "registry:example",
},
],
},
{
name: "calendar-example",
title: "Calendar",
type: "registry:example",
registryDependencies: [
"button",
"calendar",
"card",
"field",
"input",
"label",
"popover",
"example",
],
files: [
{
path: "examples/calendar-example.tsx",
type: "registry:example",
},
],
},
{
name: "card-example",
title: "Card",
type: "registry:example",
registryDependencies: [
"avatar",
"button",
"card",
"field",
"input",
"example",
],
files: [
{
path: "examples/card-example.tsx",
type: "registry:example",
},
],
},
{
name: "carousel-example",
title: "Carousel",
type: "registry:example",
registryDependencies: ["card", "carousel", "example"],
files: [
{
path: "examples/carousel-example.tsx",
type: "registry:example",
},
],
},
{
name: "chart-example",
title: "Chart",
type: "registry:example",
registryDependencies: ["chart", "card", "example"],
files: [
{
path: "examples/chart-example.tsx",
type: "registry:example",
},
],
},
{
name: "checkbox-example",
title: "Checkbox",
type: "registry:example",
registryDependencies: ["checkbox", "field", "table", "example"],
files: [
{
path: "examples/checkbox-example.tsx",
type: "registry:example",
},
],
},
{
name: "collapsible-example",
title: "Collapsible",
type: "registry:example",
registryDependencies: [
"button",
"card",
"collapsible",
"field",
"input",
"tabs",
"example",
],
files: [
{
path: "examples/collapsible-example.tsx",
type: "registry:example",
},
],
},
{
name: "combobox-example",
title: "Combobox",
type: "registry:example",
registryDependencies: [
"button",
"card",
"combobox",
"dialog",
"field",
"input",
"input-group",
"item",
"select",
"example",
],
files: [
{
path: "examples/combobox-example.tsx",
type: "registry:example",
},
],
},
{
name: "command-example",
title: "Command",
type: "registry:example",
registryDependencies: ["button", "command", "example"],
files: [
{
path: "examples/command-example.tsx",
type: "registry:example",
},
],
},
{
name: "context-menu-example",
title: "Context Menu",
type: "registry:example",
registryDependencies: ["button", "context-menu", "dialog", "example"],
files: [
{
path: "examples/context-menu-example.tsx",
type: "registry:example",
},
],
},
{
name: "dialog-example",
title: "Dialog",
type: "registry:example",
registryDependencies: [
"button",
"checkbox",
"dialog",
"field",
"input",
"input-group",
"kbd",
"native-select",
"select",
"switch",
"tabs",
"textarea",
"tooltip",
"example",
],
files: [
{
path: "examples/dialog-example.tsx",
type: "registry:example",
},
],
},
{
name: "drawer-example",
title: "Drawer",
type: "registry:example",
registryDependencies: ["drawer", "example"],
files: [
{
path: "examples/drawer-example.tsx",
type: "registry:example",
},
],
},
{
name: "dropdown-menu-example",
title: "Dropdown Menu",
type: "registry:example",
registryDependencies: [
"avatar",
"button",
"dialog",
"dropdown-menu",
"example",
],
files: [
{
path: "examples/dropdown-menu-example.tsx",
type: "registry:example",
},
],
},
{
name: "empty-example",
title: "Empty",
type: "registry:example",
registryDependencies: ["button", "empty", "input-group", "kbd", "example"],
files: [
{
path: "examples/empty-example.tsx",
type: "registry:example",
},
],
},
{
name: "field-example",
title: "Field",
type: "registry:example",
registryDependencies: [
"badge",
"checkbox",
"field",
"input",
"input-otp",
"native-select",
"radio-group",
"select",
"slider",
"switch",
"textarea",
"example",
],
files: [
{
path: "examples/field-example.tsx",
type: "registry:example",
},
],
},
{
name: "hover-card-example",
title: "Hover Card",
type: "registry:example",
registryDependencies: ["button", "dialog", "hover-card", "example"],
files: [
{
path: "examples/hover-card-example.tsx",
type: "registry:example",
},
],
},
{
name: "input-example",
title: "Input",
type: "registry:example",
registryDependencies: [
"button",
"field",
"input",
"native-select",
"select",
"example",
],
files: [
{
path: "examples/input-example.tsx",
type: "registry:example",
},
],
},
{
name: "input-group-example",
title: "Input Group",
type: "registry:example",
registryDependencies: [
"button",
"button-group",
"card",
"dropdown-menu",
"field",
"input",
"input-group",
"kbd",
"popover",
"spinner",
"textarea",
"tooltip",
"example",
],
files: [
{
path: "examples/input-group-example.tsx",
type: "registry:example",
},
],
},
{
name: "input-otp-example",
title: "Input OTP",
type: "registry:example",
registryDependencies: ["button", "card", "field", "input-otp", "example"],
files: [
{
path: "examples/input-otp-example.tsx",
type: "registry:example",
},
],
},
{
name: "item-example",
title: "Item",
type: "registry:example",
registryDependencies: ["button", "item", "example"],
files: [
{
path: "examples/item-example.tsx",
type: "registry:example",
},
],
},
{
name: "kbd-example",
title: "Kbd",
type: "registry:example",
registryDependencies: [
"button",
"input-group",
"kbd",
"tooltip",
"example",
],
files: [
{
path: "examples/kbd-example.tsx",
type: "registry:example",
},
],
},
{
name: "label-example",
title: "Label",
type: "registry:example",
registryDependencies: [
"checkbox",
"field",
"input",
"label",
"textarea",
"example",
],
files: [
{
path: "examples/label-example.tsx",
type: "registry:example",
},
],
},
{
name: "native-select-example",
title: "Native Select",
type: "registry:example",
registryDependencies: ["field", "native-select", "example"],
files: [
{
path: "examples/native-select-example.tsx",
type: "registry:example",
},
],
},
{
name: "pagination-example",
title: "Pagination",
type: "registry:example",
registryDependencies: ["field", "pagination", "select", "example"],
files: [
{
path: "examples/pagination-example.tsx",
type: "registry:example",
},
],
},
{
name: "popover-example",
title: "Popover",
type: "registry:example",
registryDependencies: [
"button",
"dialog",
"field",
"input",
"popover",
"example",
],
files: [
{
path: "examples/popover-example.tsx",
type: "registry:example",
},
],
},
{
name: "progress-example",
title: "Progress",
type: "registry:example",
registryDependencies: ["field", "item", "progress", "slider", "example"],
files: [
{
path: "examples/progress-example.tsx",
type: "registry:example",
},
],
},
{
name: "radio-group-example",
title: "Radio Group",
type: "registry:example",
registryDependencies: ["field", "radio-group", "example"],
files: [
{
path: "examples/radio-group-example.tsx",
type: "registry:example",
},
],
},
{
name: "resizable-example",
title: "Resizable",
type: "registry:example",
registryDependencies: ["resizable", "example"],
files: [
{
path: "examples/resizable-example.tsx",
type: "registry:example",
},
],
},
{
name: "scroll-area-example",
title: "Scroll Area",
type: "registry:example",
registryDependencies: ["scroll-area", "separator", "example"],
files: [
{
path: "examples/scroll-area-example.tsx",
type: "registry:example",
},
],
},
{
name: "select-example",
title: "Select",
type: "registry:example",
registryDependencies: [
"button",
"dialog",
"field",
"input",
"item",
"native-select",
"select",
"example",
],
files: [
{
path: "examples/select-example.tsx",
type: "registry:example",
},
],
},
{
name: "separator-example",
title: "Separator",
type: "registry:example",
registryDependencies: ["separator", "example"],
files: [
{
path: "examples/separator-example.tsx",
type: "registry:example",
},
],
},
{
name: "sheet-example",
title: "Sheet",
type: "registry:example",
registryDependencies: ["button", "field", "input", "sheet", "example"],
files: [
{
path: "examples/sheet-example.tsx",
type: "registry:example",
},
],
},
{
name: "sidebar-example",
title: "Sidebar",
type: "registry:example",
registryDependencies: [
"button",
"dropdown-menu",
"item",
"label",
"sidebar",
"example",
],
files: [
{
path: "examples/sidebar-example.tsx",
type: "registry:example",
},
],
},
{
name: "sidebar-icon-example",
title: "Sidebar (Icon)",
type: "registry:example",
registryDependencies: [
"avatar",
"button",
"collapsible",
"dropdown-menu",
"item",
"sidebar",
"example",
],
files: [
{
path: "examples/sidebar-icon-example.tsx",
type: "registry:example",
},
],
},
{
name: "sidebar-inset-example",
title: "Sidebar (Inset)",
type: "registry:example",
registryDependencies: ["collapsible", "sidebar", "example"],
files: [
{
path: "examples/sidebar-inset-example.tsx",
type: "registry:example",
},
],
},
{
name: "sidebar-floating-example",
title: "Sidebar (Floating)",
type: "registry:example",
registryDependencies: [
"button",
"card",
"dropdown-menu",
"field",
"item",
"sidebar",
"example",
],
files: [
{
path: "examples/sidebar-floating-example.tsx",
type: "registry:example",
},
],
},
{
name: "skeleton-example",
title: "Skeleton",
type: "registry:example",
registryDependencies: ["skeleton", "example"],
files: [
{
path: "examples/skeleton-example.tsx",
type: "registry:example",
},
],
},
{
name: "slider-example",
title: "Slider",
type: "registry:example",
registryDependencies: ["label", "slider", "example"],
files: [
{
path: "examples/slider-example.tsx",
type: "registry:example",
},
],
},
{
name: "sonner-example",
title: "Sonner",
type: "registry:example",
registryDependencies: ["sonner", "example"],
files: [
{
path: "examples/sonner-example.tsx",
type: "registry:example",
},
],
},
{
name: "spinner-example",
title: "Spinner",
type: "registry:example",
registryDependencies: [
"badge",
"button",
"empty",
"field",
"input-group",
"spinner",
"example",
],
files: [
{
path: "examples/spinner-example.tsx",
type: "registry:example",
},
],
},
{
name: "switch-example",
title: "Switch",
type: "registry:example",
registryDependencies: ["field", "label", "switch", "example"],
files: [
{
path: "examples/switch-example.tsx",
type: "registry:example",
},
],
},
{
name: "table-example",
title: "Table",
type: "registry:example",
registryDependencies: [
"button",
"dropdown-menu",
"input",
"select",
"table",
"example",
],
files: [
{
path: "examples/table-example.tsx",
type: "registry:example",
},
],
},
{
name: "tabs-example",
title: "Tabs",
type: "registry:example",
registryDependencies: ["button", "dropdown-menu", "tabs", "example"],
files: [
{
path: "examples/tabs-example.tsx",
type: "registry:example",
},
],
},
{
name: "textarea-example",
title: "Textarea",
type: "registry:example",
registryDependencies: ["field", "textarea", "example"],
files: [
{
path: "examples/textarea-example.tsx",
type: "registry:example",
},
],
},
{
name: "toggle-example",
title: "Toggle",
type: "registry:example",
registryDependencies: ["toggle", "example"],
files: [
{
path: "examples/toggle-example.tsx",
type: "registry:example",
},
],
},
{
name: "toggle-group-example",
title: "Toggle Group",
type: "registry:example",
registryDependencies: ["input", "select", "toggle-group", "example"],
files: [
{
path: "examples/toggle-group-example.tsx",
type: "registry:example",
},
],
},
{
name: "tooltip-example",
title: "Tooltip",
type: "registry:example",
registryDependencies: ["button", "kbd", "tooltip", "example"],
files: [
{
path: "examples/tooltip-example.tsx",
type: "registry:example",
},
],
},
{
name: "demo",
title: "Demo",
type: "registry:example",
registryDependencies: [
"alert-dialog",
"badge",
"button",
"button-group",
"card",
"checkbox",
"dropdown-menu",
"field",
"input-group",
"item",
"radio-group",
"slider",
"switch",
"textarea",
],
files: [
{
path: "examples/demo.tsx",
type: "registry:example",
},
],
},
{
name: "component-example",
title: "Example",
type: "registry:example",
registryDependencies: [
"alert-dialog",
"badge",
"button",
"card",
"combobox",
"dropdown-menu",
"field",
"input",
"select",
"textarea",
"example",
],
files: [
{
path: "examples/component-example.tsx",
type: "registry:example",
},
],
},
{
name: "attachment-example",
title: "Attachment",
type: "registry:example",
registryDependencies: ["attachment"],
files: [
{
path: "examples/attachment-example.tsx",
type: "registry:example",
},
],
},
{
name: "bubble-example",
title: "Bubble",
type: "registry:example",
registryDependencies: ["bubble", "button", "collapsible", "example"],
files: [
{
path: "examples/bubble-example.tsx",
type: "registry:example",
},
],
},
{
name: "message-scroller-example",
title: "Message Scroller",
type: "registry:example",
registryDependencies: [
"attachment",
"bubble",
"button",
"card",
"example",
"input-group",
"marker",
"message",
"message-scroller",
"spinner",
],
files: [
{
path: "examples/message-scroller-example.tsx",
type: "registry:example",
},
],
},
{
name: "questionnaire-example",
title: "Questionnaire",
type: "registry:example",
registryDependencies: [
"button",
"card",
"dialog",
"example",
"questionnaire",
"sonner",
],
files: [
{
path: "examples/questionnaire-example.tsx",
type: "registry:example",
},
],
},
{
name: "marker-example",
title: "Marker",
type: "registry:example",
registryDependencies: [
"marker",
"button",
"accordion",
"drawer",
"spinner",
"example",
],
files: [
{
path: "examples/marker-example.tsx",
type: "registry:example",
},
],
},
{
name: "message-example",
title: "Message",
type: "registry:example",
registryDependencies: ["bubble", "message", "button", "example"],
files: [
{
path: "examples/message-example.tsx",
type: "registry:example",
},
],
},
]

View File

@@ -0,0 +1,336 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/bases/aria/ui/accordion"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/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} id={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 allowsMultipleExpanded className="mx-auto max-w-lg">
{items.map((item) => (
<AccordionItem key={item.value} id={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}
id={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
allowsMultipleExpanded
defaultExpandedKeys={["plans"]}
className="style-maia:rounded-md style-mira:rounded-md"
>
{items.map((item) => (
<AccordionItem key={item.value} id={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}
id={item.value}
isDisabled={item.disabled}
className="p-1 data-expanded: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,216 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/aria/ui/alert-dialog"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Dialog,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/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">
<AlertDialogTrigger>
<Button variant="outline">Default</Button>
<AlertDialog>
<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>
</AlertDialog>
</AlertDialogTrigger>
</Example>
)
}
function AlertDialogSmall() {
return (
<Example title="Small" className="items-center">
<AlertDialogTrigger>
<Button variant="outline">Small</Button>
<AlertDialog 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>
</AlertDialog>
</AlertDialogTrigger>
</Example>
)
}
function AlertDialogWithMedia() {
return (
<Example title="With Media" className="items-center">
<AlertDialogTrigger>
<Button variant="outline">Default (Media)</Button>
<AlertDialog>
<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>
</AlertDialog>
</AlertDialogTrigger>
</Example>
)
}
function AlertDialogSmallWithMedia() {
return (
<Example title="Small With Media" className="items-center">
<AlertDialogTrigger>
<Button variant="outline">Small (Media)</Button>
<AlertDialog 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>
</AlertDialog>
</AlertDialogTrigger>
</Example>
)
}
function AlertDialogDestructive() {
return (
<Example title="Destructive" className="items-center">
<AlertDialogTrigger>
<Button variant="destructive">Delete Chat</Button>
<AlertDialog 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>
</AlertDialog>
</AlertDialogTrigger>
</Example>
)
}
function AlertDialogInDialog() {
return (
<Example title="In Dialog" className="items-center">
<DialogTrigger>
<Button variant="outline">Open Dialog</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Alert Dialog Example</DialogTitle>
<DialogDescription>
Click the button below to open an alert dialog.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<AlertDialogTrigger>
<Button>Open Alert Dialog</Button>
<AlertDialog 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>
</AlertDialog>
</AlertDialogTrigger>
</DialogFooter>
</Dialog>
</DialogTrigger>
</Example>
)
}

View File

@@ -0,0 +1,227 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Alert,
AlertAction,
AlertDescription,
AlertTitle,
} from "@/registry/bases/aria/ui/alert"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/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/aria/components/example"
import { AspectRatio } from "@/registry/bases/aria/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/aria/components/example"
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/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/aria/components/example"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Spinner } from "@/registry/bases/aria/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={(props) => (
<a {...props} href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRightIcon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
)}
/>
<Badge
variant="secondary"
render={(props) => (
<a {...props} href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRightIcon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
)}
/>
<Badge
variant="destructive"
render={(props) => (
<a {...props} href="#">
Link{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRightIcon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
data-icon="inline-end"
/>
</a>
)}
/>
<Badge
variant="ghost"
render={(props) => (
<a {...props} 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,130 @@
"use client"
import Link from "next/link"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
} from "@/registry/bases/aria/ui/breadcrumb"
import { Button } from "@/registry/bases/aria/ui/button"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/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>
<BreadcrumbItem>
<BreadcrumbLink href="#">Components</BreadcrumbLink>
</BreadcrumbItem>
<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>
<BreadcrumbItem>
<DropdownMenuTrigger>
<Button size="icon-sm" variant="ghost">
<BreadcrumbEllipsis />
<span className="sr-only">Toggle menu</span>
</Button>
<DropdownMenu placement="bottom start">
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Themes</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbLink href="#">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</Example>
)
}
function BreadcrumbWithLink() {
return (
<Example title="With Link" className="items-center justify-center">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink
href="#"
render={(props) =>
"href" in props ? <Link {...props} /> : <span {...props} />
}
>
Home
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbEllipsis />
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbLink
href="#"
render={(props) =>
"href" in props ? <Link {...props} /> : <span {...props} />
}
>
Components
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</Example>
)
}

View File

@@ -0,0 +1,522 @@
"use client"
import * as React from "react"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/registry/bases/aria/ui/bubble"
import { Button } from "@/registry/bases/aria/ui/button"
import { Collapsible } from "@/registry/bases/aria/ui/collapsible"
import { Marker, MarkerContent } from "@/registry/bases/aria/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 isExpanded={open} onExpandedChange={setOpen}>
<Bubble variant="muted" align="end">
<BubbleContent className="whitespace-pre-line">
<div>{open || !isLong ? text : preview}</div>
{isLong ? (
<Button
slot="trigger"
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"
/>
</Button>
) : 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={(props) => <a href="#" {...props} />}>
This bubble is a link.
</BubbleContent>
</Bubble>
<Bubble variant="secondary">
<BubbleContent
render={(props) => <button type="button" {...props} />}
>
This one is a button you can click.
</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent
render={(props) => <button type="button" {...props} />}
>
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={(props) => (
<button
type="button"
onClick={() => toast(reply.message)}
{...props}
/>
)}
>
{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 { Focusable } from "react-aria-components"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button, buttonVariants } from "@/registry/bases/aria/ui/button"
import {
ButtonGroup,
ButtonGroupText,
} from "@/registry/bases/aria/ui/button-group"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Label } from "@/registry/bases/aria/ui/label"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/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={(props) => <Label htmlFor="input-text" {...props} />}
>
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>
<DropdownMenuTrigger>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>Disable</DropdownMenuItem>
<DropdownMenuItem variant="destructive">
Uninstall
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Follow</Button>
<DropdownMenuTrigger>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDown01Icon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</Button>
<DropdownMenu placement="bottom 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>
</DropdownMenu>
</DropdownMenuTrigger>
</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 aria-label="Currency" defaultValue={currencyItems[0].value}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{currencyItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{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>
<span
data-slot="button"
className={buttonVariants({
variant: "outline",
size: "icon",
className: "w-12",
})}
>
1.2K
</span>
</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 aria-label="Duration" defaultValue={durationItems[0].value}>
<SelectTrigger id="duration">
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom start">
<SelectGroup>
{durationItems.map((item) => (
<SelectItem key={item.value} id={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..." />
<TooltipTrigger>
<Focusable>
<InputGroupAddon align="inline-end" role="button">
<IconPlaceholder
lucide="AudioLinesIcon"
tabler="IconHeadphones"
hugeicons="AudioWave01Icon"
phosphor="MicrophoneIcon"
remixicon="RiMicLine"
/>
</InputGroupAddon>
</Focusable>
<Tooltip>Voice Mode</Tooltip>
</TooltipTrigger>
</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,475 @@
"use client"
import * as React from "react"
import {
CalendarDate,
getLocalTimeZone,
isSameDay,
isWeekend,
today,
} from "@internationalized/date"
import { I18nProvider, useLocale, type DateRange } from "react-aria-components"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import { Calendar, RangeCalendar } from "@/registry/bases/aria/ui/calendar"
import { Card, CardContent, CardFooter } from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Popover, PopoverTrigger } from "@/registry/bases/aria/ui/popover"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function CalendarExample() {
return (
<ExampleWrapper>
<CalendarSingle />
<CalendarMultiple />
<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 />
</CardContent>
</Card>
</Example>
)
}
function CalendarInPopover() {
return (
<Example title="In Popover">
<PopoverTrigger>
<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
</Button>
<Popover className="w-auto p-0" placement="bottom start">
<Calendar />
</Popover>
</PopoverTrigger>
</Example>
)
}
function CalendarSingle() {
const [date, setDate] = React.useState<CalendarDate | undefined>(
new CalendarDate(new Date().getFullYear(), new Date().getMonth() + 1, 12)
)
return (
<Example title="Single">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar value={date} onChange={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 selectionMode="multiple" />
</CardContent>
</Card>
</Example>
)
}
function CalendarRange() {
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
start: new CalendarDate(new Date().getFullYear(), 1, 12),
end: new CalendarDate(new Date().getFullYear(), 1, 12).add({ days: 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">
<RangeCalendar
value={dateRange}
onChange={setDateRange}
numberOfMonths={2}
maxValue={today(getLocalTimeZone())}
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarRangeMultipleMonths() {
const [range, setRange] = React.useState<DateRange | undefined>({
start: new CalendarDate(new Date().getFullYear(), 4, 12),
end: new CalendarDate(new Date().getFullYear(), 4, 12).add({ days: 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">
<I18nProvider locale="es">
<RangeCalendar
value={range}
onChange={setRange}
numberOfMonths={3}
weeksInMonth={6}
/>
</I18nProvider>
</CardContent>
</Card>
</Example>
)
}
function CalendarBookedDates() {
const [date, setDate] = React.useState<CalendarDate | undefined>(
new CalendarDate(new Date().getFullYear(), 2, 3)
)
const bookedDates = Array.from(
{ length: 15 },
(_, i) => new CalendarDate(new Date().getFullYear(), 2, 12 + i)
)
return (
<Example title="Booked Dates">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<Calendar
value={date}
onChange={setDate}
isDateUnavailable={(date) =>
bookedDates.some((d) => isSameDay(date, d))
}
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarWithTime() {
const [date, setDate] = React.useState<CalendarDate | undefined>(
new CalendarDate(new Date().getFullYear(), new Date().getMonth() + 1, 12)
)
return (
<Example title="With Time">
<Card size="sm" className="mx-auto w-fit">
<CardContent>
<Calendar value={date} onChange={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 { locale } = useLocale()
const [range, setRange] = React.useState<DateRange | undefined>({
start: new CalendarDate(new Date().getFullYear(), 12, 8),
end: new CalendarDate(new Date().getFullYear(), 12, 8).add({ days: 10 }),
})
return (
<Example title="Custom Days">
<Card className="mx-auto w-fit p-0">
<CardContent className="p-0">
<RangeCalendar
value={range}
onChange={setRange}
numberOfMonths={1}
captionLayout="dropdown"
className="[--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)]"
headerFormat={{ month: "long" }}
renderCell={({ isOutsideMonth, date, defaultChildren }) => (
<>
{defaultChildren}
{!isOutsideMonth && (
<span>{isWeekend(date, locale) ? "$120" : "$100"}</span>
)}
</>
)}
/>
</CardContent>
</Card>
</Example>
)
}
function CalendarWithPresets() {
const [date, setDate] = React.useState<CalendarDate | undefined>(
new CalendarDate(new Date().getFullYear(), 2, 12)
)
const [currentMonth, setCurrentMonth] = React.useState<CalendarDate>(
new CalendarDate(new Date().getFullYear(), new Date().getMonth() + 1, 1)
)
return (
<Example title="With Presets">
<Card className="mx-auto w-fit max-w-[300px]" size="sm">
<CardContent>
<Calendar
value={date}
onChange={setDate}
focusedValue={currentMonth}
onFocusChange={setCurrentMonth}
weeksInMonth={6}
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"
onPress={() => {
const newDate = today(getLocalTimeZone()).add({
days: preset.value,
})
setDate(newDate)
setCurrentMonth(newDate)
}}
>
{preset.label}
</Button>
))}
</CardFooter>
</Card>
</Example>
)
}
function DatePickerSimple() {
const [date, setDate] = React.useState<CalendarDate>()
return (
<Example title="Date Picker Simple">
<Field className="mx-auto w-72">
<FieldLabel htmlFor="date-picker-simple">Date</FieldLabel>
<PopoverTrigger>
<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 ? (
date
.toDate(getLocalTimeZone())
.toLocaleDateString(undefined, { dateStyle: "long" })
) : (
<span>Pick a date</span>
)}
</Button>
<Popover className="w-auto p-0" placement="bottom start">
<Calendar value={date} onChange={setDate} />
</Popover>
</PopoverTrigger>
</Field>
</Example>
)
}
function DatePickerWithRange() {
const [date, setDate] = React.useState<DateRange | undefined>({
start: new CalendarDate(new Date().getFullYear(), 1, 20),
end: new CalendarDate(new Date().getFullYear(), 1, 20).add({ days: 20 }),
})
return (
<Example title="Date Picker Range">
<Field className="mx-auto w-72">
<FieldLabel htmlFor="date-picker-range">Date Picker Range</FieldLabel>
<PopoverTrigger>
<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?.start && date.end ? (
new Intl.DateTimeFormat(undefined, {
dateStyle: "long",
}).formatRange(
date.start.toDate(getLocalTimeZone()),
date.end.toDate(getLocalTimeZone())
)
) : (
<span>Pick a date</span>
)}
</Button>
<Popover className="w-auto p-0" placement="bottom start">
<RangeCalendar value={date} onChange={setDate} numberOfMonths={2} />
</Popover>
</PopoverTrigger>
</Field>
</Example>
)
}
function DataPickerWithDropdowns() {
const [date, setDate] = React.useState<CalendarDate>()
const [open, setOpen] = React.useState(false)
return (
<Example title="Date Picker with Dropdowns">
<Field className="mx-auto w-72">
<FieldLabel htmlFor="date-picker-with-dropdowns-desktop">
Date
</FieldLabel>
<PopoverTrigger isOpen={open} onOpenChange={setOpen}>
<Button
variant="outline"
id="date-picker-with-dropdowns-desktop"
className="justify-start px-2.5 font-normal"
>
{date ? (
date
.toDate(getLocalTimeZone())
.toLocaleDateString(undefined, { dateStyle: "long" })
) : (
<span>Pick a date</span>
)}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDownIcon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
data-icon="inline-start"
className="ml-auto"
/>
</Button>
<Popover className="w-auto p-0" placement="bottom start">
<Calendar
value={date}
onChange={setDate}
captionLayout="dropdown"
/>
<div className="flex gap-2 border-t p-2">
<Button
variant="outline"
size="sm"
className="w-full"
onPress={() => setOpen(false)}
>
Done
</Button>
</div>
</Popover>
</PopoverTrigger>
</Field>
</Example>
)
}

View File

@@ -0,0 +1,490 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/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
selectedKeys={[spacing]}
onSelectionChange={(keys) => {
const key = Array.from(keys)[0]
if (key) {
setSpacing(String(key))
}
}}
variant="outline"
size="sm"
className="justify-center"
>
{spacingOptions.map((option) => (
<ToggleGroupItem key={option.value} id={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/aria/components/example"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/registry/bases/aria/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/aria/components/example"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/registry/bases/aria/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,221 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldTitle,
} from "@/registry/bases/aria/ui/field"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/aria/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" defaultSelected />
<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" isInvalid />
<FieldLabel htmlFor="terms-3">Accept terms and conditions</FieldLabel>
</Field>
</Example>
)
}
function CheckboxDisabled() {
return (
<Example title="Disabled">
<Field orientation="horizontal">
<Checkbox id="toggle" isDisabled />
<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" defaultSelected />
<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" isDisabled />
<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() {
return (
<Example title="In Table">
<Table>
<TableHeader>
<TableHead className="w-8">
<Checkbox slot="selection" />
</TableHead>
<TableHead isRowHeader>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
</TableHeader>
<TableBody>
{tableData.map((row) => (
<TableRow key={row.id}>
<TableCell>
<Checkbox id={`row-${row.id}`} slot="selection" />
</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,240 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button, buttonVariants } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/aria/ui/collapsible"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import { Tabs, TabsList, TabsTrigger } from "@/registry/bases/aria/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
className={buttonVariants({
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-aria-expanded: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 defaultSelectedKey="explorer">
<TabsList className="w-full">
<TabsTrigger id="explorer">Explorer</TabsTrigger>
<TabsTrigger id="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
isExpanded={isOpen}
onExpandedChange={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
className={buttonVariants({ 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,605 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import { Card, CardContent } from "@/registry/bases/aria/ui/card"
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
CommandShortcut,
} from "@/registry/bases/aria/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
renderEmptyState={() => (
<CommandEmpty>No results found.</CommandEmpty>
)}
>
<CommandGroup heading="Suggestions">
<CommandItem textValue="Calendar">
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
/>
<span>Calendar</span>
</CommandItem>
<CommandItem textValue="Search Emoji">
<IconPlaceholder
lucide="SmileIcon"
tabler="IconMoodSmile"
hugeicons="SmileIcon"
phosphor="SmileyIcon"
remixicon="RiEmotionLine"
/>
<span>Search Emoji</span>
</CommandItem>
<CommandItem textValue="Calculator">
<IconPlaceholder
lucide="CalculatorIcon"
tabler="IconCalculator"
hugeicons="CalculatorIcon"
phosphor="CalculatorIcon"
remixicon="RiCalculatorLine"
/>
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem textValue="Profile">
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem textValue="Billing">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem textValue="Settings">
<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
onPress={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList
renderEmptyState={() => (
<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
onPress={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList
renderEmptyState={() => (
<CommandEmpty>No results found.</CommandEmpty>
)}
>
<CommandGroup heading="Settings">
<CommandItem textValue="Profile">
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem textValue="Billing">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem textValue="Settings">
<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
onPress={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList
renderEmptyState={() => (
<CommandEmpty>No results found.</CommandEmpty>
)}
>
<CommandGroup heading="Suggestions">
<CommandItem textValue="Calendar">
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
/>
<span>Calendar</span>
</CommandItem>
<CommandItem textValue="Search Emoji">
<IconPlaceholder
lucide="SmileIcon"
tabler="IconMoodSmile"
hugeicons="SmileIcon"
phosphor="SmileyIcon"
remixicon="RiEmotionLine"
/>
<span>Search Emoji</span>
</CommandItem>
<CommandItem textValue="Calculator">
<IconPlaceholder
lucide="CalculatorIcon"
tabler="IconCalculator"
hugeicons="CalculatorIcon"
phosphor="CalculatorIcon"
remixicon="RiCalculatorLine"
/>
<span>Calculator</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem textValue="Profile">
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem textValue="Billing">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem textValue="Settings">
<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
onPress={() => setOpen(true)}
variant="outline"
className="w-fit"
>
Open Menu
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList
renderEmptyState={() => (
<CommandEmpty>No results found.</CommandEmpty>
)}
>
<CommandGroup heading="Navigation">
<CommandItem textValue="Home">
<IconPlaceholder
lucide="HomeIcon"
tabler="IconHome"
hugeicons="HomeIcon"
phosphor="HouseIcon"
remixicon="RiHomeLine"
/>
<span>Home</span>
<CommandShortcut>⌘H</CommandShortcut>
</CommandItem>
<CommandItem textValue="Inbox">
<IconPlaceholder
lucide="InboxIcon"
tabler="IconInbox"
hugeicons="InboxIcon"
phosphor="TrayIcon"
remixicon="RiInboxLine"
/>
<span>Inbox</span>
<CommandShortcut>⌘I</CommandShortcut>
</CommandItem>
<CommandItem textValue="Documents">
<IconPlaceholder
lucide="FileTextIcon"
tabler="IconFileText"
hugeicons="File02Icon"
phosphor="FileTextIcon"
remixicon="RiFileTextLine"
/>
<span>Documents</span>
<CommandShortcut>⌘D</CommandShortcut>
</CommandItem>
<CommandItem textValue="Folders">
<IconPlaceholder
lucide="FolderIcon"
tabler="IconFolder"
hugeicons="FolderIcon"
phosphor="FolderIcon"
remixicon="RiFolderLine"
/>
<span>Folders</span>
<CommandShortcut>⌘F</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem textValue="New File">
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
/>
<span>New File</span>
<CommandShortcut>⌘N</CommandShortcut>
</CommandItem>
<CommandItem textValue="New Folder">
<IconPlaceholder
lucide="FolderPlusIcon"
tabler="IconFolderPlus"
hugeicons="FolderAddIcon"
phosphor="FolderPlusIcon"
remixicon="RiFolderAddLine"
/>
<span>New Folder</span>
<CommandShortcut>⇧⌘N</CommandShortcut>
</CommandItem>
<CommandItem textValue="Copy">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="CopyIcon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
<span>Copy</span>
<CommandShortcut>⌘C</CommandShortcut>
</CommandItem>
<CommandItem textValue="Cut">
<IconPlaceholder
lucide="ScissorsIcon"
tabler="IconCut"
hugeicons="ScissorIcon"
phosphor="ScissorsIcon"
remixicon="RiScissorsLine"
/>
<span>Cut</span>
<CommandShortcut>⌘X</CommandShortcut>
</CommandItem>
<CommandItem textValue="Paste">
<IconPlaceholder
lucide="ClipboardPasteIcon"
tabler="IconClipboard"
hugeicons="ClipboardIcon"
phosphor="ClipboardIcon"
remixicon="RiClipboardLine"
/>
<span>Paste</span>
<CommandShortcut>⌘V</CommandShortcut>
</CommandItem>
<CommandItem textValue="Delete">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
<span>Delete</span>
<CommandShortcut>⌫</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="View">
<CommandItem textValue="Grid View">
<IconPlaceholder
lucide="LayoutGridIcon"
tabler="IconLayoutGrid"
hugeicons="GridIcon"
phosphor="GridFourIcon"
remixicon="RiGridLine"
/>
<span>Grid View</span>
</CommandItem>
<CommandItem textValue="List View">
<IconPlaceholder
lucide="ListIcon"
tabler="IconList"
hugeicons="Menu05Icon"
phosphor="ListIcon"
remixicon="RiListUnordered"
/>
<span>List View</span>
</CommandItem>
<CommandItem textValue="Zoom In">
<IconPlaceholder
lucide="ZoomInIcon"
tabler="IconZoomIn"
hugeicons="ZoomInAreaIcon"
phosphor="MagnifyingGlassPlusIcon"
remixicon="RiZoomInLine"
/>
<span>Zoom In</span>
<CommandShortcut>⌘+</CommandShortcut>
</CommandItem>
<CommandItem textValue="Zoom Out">
<IconPlaceholder
lucide="ZoomOutIcon"
tabler="IconZoomOut"
hugeicons="ZoomOutAreaIcon"
phosphor="MagnifyingGlassMinusIcon"
remixicon="RiSearchEyeLine"
/>
<span>Zoom Out</span>
<CommandShortcut>⌘-</CommandShortcut>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Account">
<CommandItem textValue="Profile">
<IconPlaceholder
lucide="UserIcon"
tabler="IconUser"
hugeicons="UserIcon"
phosphor="UserIcon"
remixicon="RiUserLine"
/>
<span>Profile</span>
<CommandShortcut>⌘P</CommandShortcut>
</CommandItem>
<CommandItem textValue="Billing">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
<span>Billing</span>
<CommandShortcut>⌘B</CommandShortcut>
</CommandItem>
<CommandItem textValue="Settings">
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
<span>Settings</span>
<CommandShortcut>⌘S</CommandShortcut>
</CommandItem>
<CommandItem textValue="Notifications">
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
<span>Notifications</span>
</CommandItem>
<CommandItem textValue="Help & Support">
<IconPlaceholder
lucide="HelpCircleIcon"
tabler="IconHelpCircle"
hugeicons="HelpCircleIcon"
phosphor="QuestionIcon"
remixicon="RiQuestionLine"
/>
<span>Help & Support</span>
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Tools">
<CommandItem textValue="Calculator">
<IconPlaceholder
lucide="CalculatorIcon"
tabler="IconCalculator"
hugeicons="CalculatorIcon"
phosphor="CalculatorIcon"
remixicon="RiCalculatorLine"
/>
<span>Calculator</span>
</CommandItem>
<CommandItem textValue="Calendar">
<IconPlaceholder
lucide="CalendarIcon"
tabler="IconCalendar"
hugeicons="CalendarIcon"
phosphor="CalendarBlankIcon"
remixicon="RiCalendarLine"
/>
<span>Calendar</span>
</CommandItem>
<CommandItem textValue="Image Editor">
<IconPlaceholder
lucide="ImageIcon"
tabler="IconPhoto"
hugeicons="ImageIcon"
phosphor="ImageIcon"
remixicon="RiImageLine"
/>
<span>Image Editor</span>
</CommandItem>
<CommandItem textValue="Code Editor">
<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,634 @@
"use client"
import * as React from "react"
import type { Selection } from "react-aria-components"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/aria/ui/alert-dialog"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "@/registry/bases/aria/ui/combobox"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Textarea } from "@/registry/bases/aria/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>
<AlertDialogTrigger>
<Button>
<IconPlaceholder
lucide="PlusIcon"
tabler="IconPlus"
hugeicons="PlusSignIcon"
phosphor="PlusIcon"
remixicon="RiAddLine"
data-icon="inline-start"
/>
Show Dialog
</Button>
<AlertDialog 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>
</AlertDialog>
</AlertDialogTrigger>
<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<Selection>(
new Set(["email", "push"])
)
const [view, setView] = React.useState<Selection>(new Set(["sidebar"]))
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>
<DropdownMenuTrigger>
<Button variant="ghost" size="icon">
<IconPlaceholder
lucide="MoreVerticalIcon"
tabler="IconDotsVertical"
hugeicons="MoreVerticalCircle01Icon"
phosphor="DotsThreeVerticalIcon"
remixicon="RiMore2Line"
/>
<span className="sr-only">More options</span>
</Button>
<DropdownMenu placement="bottom 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>
<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>
<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>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="FolderSearchIcon"
tabler="IconFolderSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
Browse...
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</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
selectionMode="multiple"
selectedKeys={notifications}
onSelectionChange={setNotifications}
>
<DropdownMenuLabel>View</DropdownMenuLabel>
<DropdownMenuItem id="sidebar">
<IconPlaceholder
lucide="EyeIcon"
tabler="IconEye"
hugeicons="EyeIcon"
phosphor="EyeIcon"
remixicon="RiEyeLine"
/>
Show Sidebar
</DropdownMenuItem>
<DropdownMenuItem id="status-bar">
<IconPlaceholder
lucide="LayoutIcon"
tabler="IconLayout"
hugeicons="LayoutIcon"
phosphor="LayoutIcon"
remixicon="RiLayoutLine"
/>
Show Status Bar
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="PaletteIcon"
tabler="IconPalette"
hugeicons="PaintBoardIcon"
phosphor="PaletteIcon"
remixicon="RiPaletteLine"
/>
Theme
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuGroup
selectionMode="single"
selectedKeys={[theme]}
onSelectionChange={(keys) =>
setTheme(
keys === "all"
? "system"
: (keys.values().next().value as string)
)
}
>
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuItem id="light">
<IconPlaceholder
lucide="SunIcon"
tabler="IconSun"
hugeicons="SunIcon"
phosphor="SunIcon"
remixicon="RiSunLine"
/>
Light
</DropdownMenuItem>
<DropdownMenuItem id="dark">
<IconPlaceholder
lucide="MoonIcon"
tabler="IconMoon"
hugeicons="MoonIcon"
phosphor="MoonIcon"
remixicon="RiMoonLine"
/>
Dark
</DropdownMenuItem>
<DropdownMenuItem id="system">
<IconPlaceholder
lucide="MonitorIcon"
tabler="IconDeviceDesktop"
hugeicons="ComputerIcon"
phosphor="MonitorIcon"
remixicon="RiComputerLine"
/>
System
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</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>
<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>
<DropdownMenuSubContent>
<DropdownMenuGroup
selectionMode="multiple"
selectedKeys={notifications}
onSelectionChange={setNotifications}
>
<DropdownMenuLabel>
Notification Types
</DropdownMenuLabel>
<DropdownMenuItem id="push">
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
Push Notifications
</DropdownMenuItem>
<DropdownMenuItem id="email">
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="MailIcon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
Email Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<IconPlaceholder
lucide="ShieldIcon"
tabler="IconShield"
hugeicons="ShieldIcon"
phosphor="ShieldIcon"
remixicon="RiShieldLine"
/>
Privacy & Security
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</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>
</DropdownMenu>
</DropdownMenuTrigger>
</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 defaultValue={null}>
<SelectTrigger id="small-form-role">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{roleItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
</div>
<Field>
<FieldLabel htmlFor="small-form-framework">
Framework
</FieldLabel>
<Combobox allowsEmptyCollection>
<ComboboxInput
id="small-form-framework"
placeholder="Select a framework"
required
/>
<ComboboxContent>
<ComboboxList
renderEmptyState={() => (
<ComboboxEmpty>No frameworks found.</ComboboxEmpty>
)}
>
{frameworks.map((item) => (
<ComboboxItem key={item} id={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,675 @@
"use client"
import * as React from "react"
import { Pressable, type Selection } from "react-aria-components"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
ContextMenu,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/registry/bases/aria/ui/context-menu"
import {
Dialog,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/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">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem isDisabled>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithIcons() {
return (
<Example title="With Icons">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<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>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithShortcuts() {
return (
<Example title="With Shortcuts">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<ContextMenuGroup>
<ContextMenuItem>
Back
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem isDisabled>
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>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithSubmenu() {
return (
<Example title="With Submenu">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<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>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithGroups() {
return (
<Example title="With Groups, Labels & Separators">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<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>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithCheckboxes() {
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set(["bookmarks-bar", "developer-tools"])
)
return (
<Example title="With Checkboxes">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<ContextMenuGroup
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<ContextMenuItem id="bookmarks-bar">
Show Bookmarks Bar
</ContextMenuItem>
<ContextMenuItem>Show Full URLs</ContextMenuItem>
<ContextMenuItem id="developer-tools">
Show Developer Tools
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithRadio() {
const [user, setUser] = React.useState("pedro")
const [theme, setTheme] = React.useState("light")
return (
<Example title="With Radio Group">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<ContextMenuGroup>
<ContextMenuLabel>People</ContextMenuLabel>
<ContextMenuGroup
selectionMode="single"
selectedKeys={[user]}
onSelectionChange={(keys) =>
setUser(
keys === "all"
? "pedro"
: (keys.values().next().value as string)
)
}
>
<ContextMenuItem id="pedro">Pedro Duarte</ContextMenuItem>
<ContextMenuItem id="colm">Colm Tuite</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuLabel>Theme</ContextMenuLabel>
<ContextMenuGroup
selectionMode="single"
selectedKeys={theme}
onSelectionChange={(keys) =>
setTheme(
keys === "all"
? "light"
: (keys.values().next().value as string)
)
}
>
<ContextMenuItem id="light">Light</ContextMenuItem>
<ContextMenuItem id="dark">Dark</ContextMenuItem>
<ContextMenuItem id="system">System</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuGroup>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithDestructive() {
return (
<Example title="With Destructive Items">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<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>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}
function ContextMenuWithSides() {
return (
<Example title="With Sides" containerClassName="col-span-2">
<div className="flex flex-wrap justify-center gap-2">
{(
[
{ label: "start", placement: "start top" },
{ label: "left", placement: "left top" },
{ label: "top", placement: "top start" },
{ label: "bottom", placement: "bottom start" },
{ label: "right", placement: "right top" },
{ label: "end", placement: "end top" },
] as const
).map(({ label, placement }) => (
<ContextMenuTrigger key={placement}>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] items-center justify-center rounded-lg border p-4 text-sm capitalize"
>
{label}
</div>
</Pressable>
<ContextMenu placement={placement}>
<ContextMenuGroup>
<ContextMenuItem>Back</ContextMenuItem>
<ContextMenuItem>Forward</ContextMenuItem>
<ContextMenuItem>Reload</ContextMenuItem>
</ContextMenuGroup>
</ContextMenu>
</ContextMenuTrigger>
))}
</div>
</Example>
)
}
function ContextMenuInDialog() {
return (
<Example title="In Dialog">
<DialogTrigger>
<Button variant="outline">Open Dialog</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Context Menu Example</DialogTitle>
<DialogDescription>
Right click on the area below to see the context menu.
</DialogDescription>
</DialogHeader>
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu>
<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>
</ContextMenu>
</ContextMenuTrigger>
</Dialog>
</DialogTrigger>
</Example>
)
}
function ContextMenuWithInset() {
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set(["bookmarks"])
)
const [theme, setTheme] = React.useState("system")
return (
<Example title="With Inset">
<ContextMenuTrigger>
<Pressable>
<div
role="button"
className="flex aspect-[2/0.5] w-full items-center justify-center rounded-lg border text-sm"
>
Right click here
</div>
</Pressable>
<ContextMenu 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
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<ContextMenuLabel inset>Appearance</ContextMenuLabel>
<ContextMenuItem inset id="bookmarks">
Bookmarks
</ContextMenuItem>
<ContextMenuItem inset id="urls">
Full URLs
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup
selectionMode="single"
selectedKeys={[theme]}
onSelectionChange={(keys) =>
setTheme(
keys === "all"
? "system"
: (keys.values().next().value as string)
)
}
>
<ContextMenuLabel inset>Theme</ContextMenuLabel>
<ContextMenuItem inset id="light">
Light
</ContextMenuItem>
<ContextMenuItem inset id="dark">
Dark
</ContextMenuItem>
<ContextMenuItem inset id="system">
System
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuSub>
<ContextMenuSubTrigger inset>More Options</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuGroup>
<ContextMenuItem>Save Page...</ContextMenuItem>
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenu>
</ContextMenuTrigger>
</Example>
)
}

View File

@@ -0,0 +1,406 @@
"use client"
import * as React from "react"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/registry/bases/aria/ui/alert-dialog"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button } from "@/registry/bases/aria/ui/button"
import { ButtonGroup } from "@/registry/bases/aria/ui/button-group"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Field, FieldGroup } from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/registry/bases/aria/ui/input-group"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/aria/ui/radio-group"
import { Slider } from "@/registry/bases/aria/ui/slider"
import { Switch } from "@/registry/bases/aria/ui/switch"
import { Textarea } from "@/registry/bases/aria/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}
onChange={handleSliderValueChange}
maxValue={1000}
minValue={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 defaultSelected />
<Checkbox />
</div>
</div>
<div className="flex items-center gap-4">
<AlertDialogTrigger>
<Button variant="outline">
<span className="hidden md:block">Alert Dialog</span>
<span className="block md:hidden">Dialog</span>
</Button>
<AlertDialog 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>
</AlertDialog>
</AlertDialogTrigger>
<ButtonGroup>
<Button variant="outline">Button Group</Button>
<DropdownMenuTrigger>
<Button variant="outline" size="icon">
<IconPlaceholder
lucide="ChevronUpIcon"
tabler="IconChevronUp"
hugeicons="ArrowUp01Icon"
phosphor="CaretUpIcon"
remixicon="RiArrowUpSLine"
/>
</Button>
<DropdownMenu placement="top end" 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>
</DropdownMenu>
</DropdownMenuTrigger>
</ButtonGroup>
<Switch defaultSelected className="ml-auto" />
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,565 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Kbd } from "@/registry/bases/aria/ui/kbd"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Switch } from "@/registry/bases/aria/ui/switch"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/aria/ui/tabs"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/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">
<DialogTrigger>
<form>
<Button variant="outline">Edit Profile</Button>
<Dialog>
<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 variant="outline">Cancel</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</Dialog>
</form>
</DialogTrigger>
</Example>
)
}
function DialogScrollableContent() {
return (
<Example title="Scrollable Content" className="items-center justify-center">
<DialogTrigger>
<Button variant="outline">Scrollable Content</Button>
<Dialog>
<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>
</Dialog>
</DialogTrigger>
</Example>
)
}
function DialogWithStickyFooter() {
return (
<Example title="With Sticky Footer" className="items-center justify-center">
<DialogTrigger>
<Button variant="outline">Sticky Footer</Button>
<Dialog>
<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 variant="outline">Close</DialogClose>
</DialogFooter>
</Dialog>
</DialogTrigger>
</Example>
)
}
function DialogNoCloseButton() {
return (
<Example title="No Close Button" className="items-center justify-center">
<DialogTrigger>
<Button variant="outline">No Close Button</Button>
<Dialog 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 variant="outline">Close</DialogClose>
</DialogFooter>
</Dialog>
</DialogTrigger>
</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">
<DialogTrigger>
<Button variant="outline">Chat Settings</Button>
<Dialog 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
selectedKey={tab}
onSelectionChange={(key) => setTab(String(key))}
>
<TabsList className="hidden w-full md:flex">
<TabsTrigger id="general">General</TabsTrigger>
<TabsTrigger id="notifications">Notifications</TabsTrigger>
<TabsTrigger id="personalization">Personalization</TabsTrigger>
<TabsTrigger id="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 id="general">
<FieldSet>
<FieldGroup>
<Field orientation="horizontal">
<FieldLabel htmlFor="theme">Theme</FieldLabel>
<Select
value={theme}
onChange={(value) => setTheme(String(value))}
>
<SelectTrigger id="theme">
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom end">
<SelectGroup>
{themes.map((theme) => (
<SelectItem key={theme.value} id={theme.value}>
{theme.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldLabel htmlFor="accent-color">
Accent Color
</FieldLabel>
<Select
value={accentColor}
onChange={(value) => setAccentColor(String(value))}
>
<SelectTrigger id="accent-color">
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom end">
<SelectGroup>
{accents.map((accent) => (
<SelectItem
key={accent.value}
id={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
value={spokenLanguage}
onChange={(value) => setSpokenLanguage(String(value))}
>
<SelectTrigger id="spoken-language">
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom end">
<SelectGroup>
{spokenLanguages.map((language) => (
<SelectItem
key={language.value}
id={language.value}
>
{language.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<FieldSeparator />
<Field orientation="horizontal">
<FieldLabel htmlFor="voice">Voice</FieldLabel>
<Select
value={voice}
onChange={(value) => setVoice(String(value))}
>
<SelectTrigger id="voice">
<SelectValue />
</SelectTrigger>
<SelectContent placement="bottom end">
<SelectGroup>
{voices.map((voice) => (
<SelectItem key={voice.value} id={voice.value}>
{voice.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
</FieldGroup>
</FieldSet>
</TabsContent>
<TabsContent id="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" defaultSelected isDisabled />
<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 id="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">
<TooltipTrigger>
<InputGroupButton size="icon-xs">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupButton>
<Tooltip className="flex items-center gap-2">
Used to identify you in the chat. <Kbd>N</Kbd>
</Tooltip>
</TooltipTrigger>
</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" defaultSelected />
</Field>
</FieldLabel>
</FieldGroup>
</TabsContent>
<TabsContent id="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>
</Dialog>
</DialogTrigger>
</Example>
)
}

View File

@@ -0,0 +1,484 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/bases/aria/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,941 @@
"use client"
import * as React from "react"
import type { Selection } from "react-aria-components"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Dialog,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/registry/bases/aria/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">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Open
</Button>
<DropdownMenu>
<DropdownMenuGroup>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>GitHub</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuItem isDisabled>API</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuSides() {
return (
<Example title="Sides" containerClassName="col-span-2">
<div className="flex flex-wrap justify-center gap-2">
{(
[
{ label: "start", placement: "start top" },
{ label: "left", placement: "left top" },
{ label: "top", placement: "top start" },
{ label: "bottom", placement: "bottom start" },
{ label: "right", placement: "right top" },
{ label: "end", placement: "end top" },
] as const
).map(({ label, placement }) => (
<DropdownMenuTrigger key={placement}>
<Button variant="outline" className="w-fit capitalize">
{label}
</Button>
<DropdownMenu placement={placement}>
<DropdownMenuGroup>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
))}
</div>
</Example>
)
}
function DropdownMenuWithIcons() {
return (
<Example title="With Icons">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Open
</Button>
<DropdownMenu>
<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>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithShortcuts() {
return (
<Example title="With Shortcuts">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Open
</Button>
<DropdownMenu>
<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>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithSubmenu() {
return (
<Example title="With Submenu">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Open
</Button>
<DropdownMenu>
<DropdownMenuGroup>
<DropdownMenuItem>Team</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Message</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>More...</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuItem>
New Team
<DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithCheckboxes() {
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set(["status-bar"])
)
return (
<Example title="With Checkboxes">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Checkboxes
</Button>
<DropdownMenu className="min-w-40">
<DropdownMenuGroup
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
<DropdownMenuItem id="status-bar">
<IconPlaceholder
lucide="LayoutIcon"
tabler="IconLayout"
hugeicons="LayoutIcon"
phosphor="LayoutIcon"
remixicon="RiLayoutLine"
/>
Status Bar
</DropdownMenuItem>
<DropdownMenuItem id="activity-bar" isDisabled>
<IconPlaceholder
lucide="ActivityIcon"
tabler="IconActivity"
hugeicons="ActivityIcon"
phosphor="ActivityIcon"
remixicon="RiPulseLine"
/>
Activity Bar
</DropdownMenuItem>
<DropdownMenuItem id="panel">
<IconPlaceholder
lucide="PanelLeftIcon"
tabler="IconLayoutSidebar"
hugeicons="LayoutLeftIcon"
phosphor="SidebarIcon"
remixicon="RiSideBarLine"
/>
Panel
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithRadio() {
const [position, setPosition] = React.useState("bottom")
return (
<Example title="With Radio Group">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Radio Group
</Button>
<DropdownMenu>
<DropdownMenuGroup
selectionMode="single"
selectedKeys={[position]}
onSelectionChange={(keys) =>
setPosition(
keys === "all"
? "bottom"
: (keys.values().next().value as string)
)
}
>
<DropdownMenuLabel>Panel Position</DropdownMenuLabel>
<DropdownMenuItem id="top">
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUp01Icon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
Top
</DropdownMenuItem>
<DropdownMenuItem id="bottom">
<IconPlaceholder
lucide="ArrowDownIcon"
tabler="IconArrowDown"
hugeicons="ArrowDown01Icon"
phosphor="ArrowDownIcon"
remixicon="RiArrowDownLine"
/>
Bottom
</DropdownMenuItem>
<DropdownMenuItem id="right" isDisabled>
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight01Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
Right
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithCheckboxesIcons() {
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set(["email", "push"])
)
return (
<Example title="Checkboxes with Icons">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Notifications
</Button>
<DropdownMenu className="min-w-56">
<DropdownMenuGroup
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<DropdownMenuLabel>Notification Preferences</DropdownMenuLabel>
<DropdownMenuItem id="email">
<IconPlaceholder
lucide="MailIcon"
tabler="IconMail"
hugeicons="MailIcon"
phosphor="EnvelopeIcon"
remixicon="RiMailLine"
/>
Email notifications
</DropdownMenuItem>
<DropdownMenuItem id="sms">
<IconPlaceholder
lucide="MessageSquareIcon"
tabler="IconMessage"
hugeicons="MessageIcon"
phosphor="ChatCircleIcon"
remixicon="RiChat1Line"
/>
SMS notifications
</DropdownMenuItem>
<DropdownMenuItem id="push">
<IconPlaceholder
lucide="BellIcon"
tabler="IconBell"
hugeicons="NotificationIcon"
phosphor="BellIcon"
remixicon="RiNotificationLine"
/>
Push notifications
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithRadioIcons() {
const [paymentMethod, setPaymentMethod] = React.useState("card")
return (
<Example title="Radio with Icons">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Payment Method
</Button>
<DropdownMenu className="min-w-56">
<DropdownMenuGroup
selectionMode="single"
selectedKeys={[paymentMethod]}
onSelectionChange={(keys) =>
setPaymentMethod(
keys === "all" ? "card" : (keys.values().next().value as string)
)
}
>
<DropdownMenuLabel>Select Payment Method</DropdownMenuLabel>
<DropdownMenuItem id="card">
<IconPlaceholder
lucide="CreditCardIcon"
tabler="IconCreditCard"
hugeicons="CreditCardIcon"
phosphor="CreditCardIcon"
remixicon="RiBankCardLine"
/>
Credit Card
</DropdownMenuItem>
<DropdownMenuItem id="paypal">
<IconPlaceholder
lucide="WalletIcon"
tabler="IconWallet"
hugeicons="WalletIcon"
phosphor="WalletIcon"
remixicon="RiWalletLine"
/>
PayPal
</DropdownMenuItem>
<DropdownMenuItem id="bank">
<IconPlaceholder
lucide="Building2Icon"
tabler="IconBuildingBank"
hugeicons="BankIcon"
phosphor="BankIcon"
remixicon="RiBankLine"
/>
Bank Transfer
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuWithDestructive() {
return (
<Example title="With Destructive Items">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Actions
</Button>
<DropdownMenu>
<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>
</DropdownMenu>
</DropdownMenuTrigger>
</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">
<DropdownMenuTrigger>
<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"
/>
</Button>
<DropdownMenu className="w-(--anchor-width) min-w-56">
{menuContent}
</DropdownMenu>
</DropdownMenuTrigger>
<DropdownMenuTrigger>
<Button variant="ghost" size="icon" className="rounded-full">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="shadcn" />
<AvatarFallback>LR</AvatarFallback>
</Avatar>
</Button>
<DropdownMenu placement="top end">{menuContent}</DropdownMenu>
</DropdownMenuTrigger>
</div>
</Example>
)
}
function DropdownMenuInDialog() {
return (
<Example title="In Dialog">
<DialogTrigger>
<Button variant="outline">Open Dialog</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Dropdown Menu Example</DialogTitle>
<DialogDescription>
Click the button below to see the dropdown menu.
</DialogDescription>
</DialogHeader>
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Open Menu
</Button>
<DropdownMenu>
<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>
<DropdownMenuSubContent>
<DropdownMenuItem>Save Page...</DropdownMenuItem>
<DropdownMenuItem>Create Shortcut...</DropdownMenuItem>
<DropdownMenuItem>Name Window...</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Developer Tools</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<IconPlaceholder
lucide="TrashIcon"
tabler="IconTrash"
hugeicons="DeleteIcon"
phosphor="TrashIcon"
remixicon="RiDeleteBinLine"
/>
Delete
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</Dialog>
</DialogTrigger>
</Example>
)
}
function DropdownMenuWithInset() {
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set(["bookmarks"])
)
const [theme, setTheme] = React.useState("system")
return (
<Example title="With Inset">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Open
</Button>
<DropdownMenu 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
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<DropdownMenuLabel inset>Appearance</DropdownMenuLabel>
<DropdownMenuItem inset id="bookmarks">
Bookmarks
</DropdownMenuItem>
<DropdownMenuItem inset id="urls">
Full URLs
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup
selectionMode="single"
selectedKeys={[theme]}
onSelectionChange={(keys) =>
setTheme(
keys === "all"
? "system"
: (keys.values().next().value as string)
)
}
>
<DropdownMenuLabel inset>Theme</DropdownMenuLabel>
<DropdownMenuItem inset id="light">
Light
</DropdownMenuItem>
<DropdownMenuItem inset id="dark">
Dark
</DropdownMenuItem>
<DropdownMenuItem inset id="system">
System
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger inset>More Options</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuGroup>
<DropdownMenuItem>Save Page...</DropdownMenuItem>
<DropdownMenuItem>Create Shortcut...</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}
function DropdownMenuComplex() {
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(
new Set(["sidebar"])
)
return (
<Example title="Complex">
<DropdownMenuTrigger>
<Button variant="outline" className="w-fit">
Complex Menu
</Button>
<DropdownMenu 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
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<DropdownMenuLabel>View</DropdownMenuLabel>
<DropdownMenuItem id="sidebar">
<IconPlaceholder
lucide="PanelLeftIcon"
tabler="IconLayoutSidebar"
hugeicons="LayoutLeftIcon"
phosphor="SidebarIcon"
remixicon="RiSideBarLine"
/>
Sidebar
</DropdownMenuItem>
<DropdownMenuItem id="status-bar">
<IconPlaceholder
lucide="LayoutIcon"
tabler="IconLayout"
hugeicons="LayoutIcon"
phosphor="LayoutIcon"
remixicon="RiLayoutLine"
/>
Status Bar
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<IconPlaceholder
lucide="UsersIcon"
tabler="IconUsers"
hugeicons="UserGroupIcon"
phosphor="UsersIcon"
remixicon="RiTeamLine"
/>
Invite Users
</DropdownMenuSubTrigger>
<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>
</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>
</DropdownMenu>
</DropdownMenuTrigger>
</Example>
)
}

View File

@@ -0,0 +1,244 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button, LinkButton } from "@/registry/bases/aria/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Kbd } from "@/registry/bases/aria/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">
<LinkButton href="#">Create project</LinkButton>
<Button variant="outline">Import project</Button>
</div>
<LinkButton variant="link" href="#" className="text-muted-foreground">
Learn more{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</LinkButton>
</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>
<LinkButton variant="link" href="#" className="text-muted-foreground">
Learn more{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</LinkButton>
</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">
<LinkButton href="#">Create project</LinkButton>
<Button variant="outline">Import project</Button>
</div>
<LinkButton variant="link" href="#" className="text-muted-foreground">
Learn more{" "}
<IconPlaceholder
lucide="ArrowUpRightIcon"
tabler="IconArrowUpRight"
hugeicons="ArrowUpRight01Icon"
phosphor="ArrowUpRightIcon"
remixicon="RiArrowRightUpLine"
/>
</LinkButton>
</EmptyContent>
</Empty>
</Example>
)
}

View File

@@ -0,0 +1,997 @@
"use client"
import { useState } from "react"
import { REGEXP_ONLY_DIGITS } from "input-otp"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/registry/bases/aria/ui/input-otp"
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/aria/ui/radio-group"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { Slider } from "@/registry/bases/aria/ui/slider"
import { Switch } from "@/registry/bases/aria/ui/switch"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
export default function FieldExample() {
return (
<ExampleWrapper>
<InputFields />
<TextareaFields />
<SelectFields />
<CheckboxFields />
<RadioFields />
<SwitchFields />
<SliderFields />
<NativeSelectFields />
<InputOTPFields />
<HorizontalFields />
</ExampleWrapper>
)
}
function InputFields() {
return (
<Example title="Input Fields">
<FieldGroup>
<Field>
<FieldLabel htmlFor="input-basic">Basic Input</FieldLabel>
<Input id="input-basic" placeholder="Enter text" />
</Field>
<Field>
<FieldLabel htmlFor="input-with-desc">
Input with Description
</FieldLabel>
<Input id="input-with-desc" placeholder="Enter your username" />
<FieldDescription>
Choose a unique username for your account.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-desc-first">Email Address</FieldLabel>
<FieldDescription>
We&apos;ll never share your email with anyone.
</FieldDescription>
<Input
id="input-desc-first"
type="email"
placeholder="email@example.com"
/>
</Field>
<Field>
<FieldLabel htmlFor="input-required">
Required Field <span className="text-destructive">*</span>
</FieldLabel>
<Input
id="input-required"
placeholder="This field is required"
required
/>
<FieldDescription>This field must be filled out.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-disabled">Disabled Input</FieldLabel>
<Input id="input-disabled" placeholder="Cannot edit" disabled />
<FieldDescription>This field is currently disabled.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-badge">
Input with Badge{" "}
<Badge variant="secondary" className="ml-auto">
Recommended
</Badge>
</FieldLabel>
<Input id="input-badge" placeholder="Enter value" />
</Field>
<Field data-invalid>
<FieldLabel htmlFor="input-invalid">Invalid Input</FieldLabel>
<Input
id="input-invalid"
placeholder="This field has an error"
aria-invalid
/>
<FieldDescription>
This field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="input-disabled-field">Disabled Field</FieldLabel>
<Input id="input-disabled-field" placeholder="Cannot edit" disabled />
<FieldDescription>This field is currently disabled.</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function TextareaFields() {
return (
<Example title="Textarea Fields">
<FieldGroup>
<Field>
<FieldLabel htmlFor="textarea-basic">Basic Textarea</FieldLabel>
<Textarea id="textarea-basic" placeholder="Enter your message" />
</Field>
<Field>
<FieldLabel htmlFor="textarea-comments">Comments</FieldLabel>
<Textarea
id="textarea-comments"
placeholder="Share your thoughts..."
className="min-h-[100px]"
/>
<FieldDescription>Maximum 500 characters allowed.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="textarea-bio">Bio</FieldLabel>
<FieldDescription>
Tell us about yourself in a few sentences.
</FieldDescription>
<Textarea
id="textarea-bio"
placeholder="I am a..."
className="min-h-[120px]"
/>
</Field>
<Field>
<FieldLabel htmlFor="textarea-desc-after">Message</FieldLabel>
<Textarea id="textarea-desc-after" placeholder="Enter your message" />
<FieldDescription>
Enter your message so it is long enough to test the layout.
</FieldDescription>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="textarea-invalid">Invalid Textarea</FieldLabel>
<Textarea
id="textarea-invalid"
placeholder="This field has an error"
aria-invalid
/>
<FieldDescription>
This field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="textarea-disabled-field">
Disabled Field
</FieldLabel>
<Textarea
id="textarea-disabled-field"
placeholder="Cannot edit"
disabled
/>
<FieldDescription>This field is currently disabled.</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function SelectFields() {
const basicItems = [
{ label: "Option 1", value: "option1" },
{ label: "Option 2", value: "option2" },
{ label: "Option 3", value: "option3" },
]
const countryItems = [
{ label: "United States", value: "us" },
{ label: "United Kingdom", value: "uk" },
{ label: "Canada", value: "ca" },
]
const timezoneItems = [
{ label: "UTC", value: "utc" },
{ label: "Eastern Time", value: "est" },
{ label: "Pacific Time", value: "pst" },
]
const invalidItems = [
{ label: "Option 1", value: "option1" },
{ label: "Option 2", value: "option2" },
{ label: "Option 3", value: "option3" },
]
const disabledItems = [
{ label: "Option 1", value: "option1" },
{ label: "Option 2", value: "option2" },
{ label: "Option 3", value: "option3" },
]
return (
<Example title="Select Fields">
<FieldGroup>
<Field>
<FieldLabel htmlFor="select-basic">Basic Select</FieldLabel>
<Select placeholder="Choose an option">
<SelectTrigger id="select-basic">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{basicItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="select-country">Country</FieldLabel>
<Select placeholder="Select your country">
<SelectTrigger id="select-country">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{countryItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>
Select the country where you currently reside.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="select-timezone">Timezone</FieldLabel>
<FieldDescription>
Choose your local timezone for accurate scheduling.
</FieldDescription>
<Select placeholder="Select timezone">
<SelectTrigger id="select-timezone">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{timezoneItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="select-invalid">Invalid Select</FieldLabel>
<Select placeholder="This field has an error" isInvalid>
<SelectTrigger id="select-invalid">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{invalidItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>
This field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="select-disabled-field">
Disabled Field
</FieldLabel>
<Select placeholder="Cannot select" isDisabled>
<SelectTrigger id="select-disabled-field">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{disabledItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>This field is currently disabled.</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function NativeSelectFields() {
return (
<Example title="Native Select Fields">
<FieldGroup>
<Field>
<FieldLabel htmlFor="native-select-basic">
Basic Native Select
</FieldLabel>
<NativeSelect id="native-select-basic">
<NativeSelectOption value="">Choose an option</NativeSelectOption>
<NativeSelectOption value="option1">Option 1</NativeSelectOption>
<NativeSelectOption value="option2">Option 2</NativeSelectOption>
<NativeSelectOption value="option3">Option 3</NativeSelectOption>
</NativeSelect>
</Field>
<Field>
<FieldLabel htmlFor="native-select-country">Country</FieldLabel>
<NativeSelect id="native-select-country">
<NativeSelectOption value="">
Select your country
</NativeSelectOption>
<NativeSelectOption value="us">United States</NativeSelectOption>
<NativeSelectOption value="uk">United Kingdom</NativeSelectOption>
<NativeSelectOption value="ca">Canada</NativeSelectOption>
</NativeSelect>
<FieldDescription>
Select the country where you currently reside.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="native-select-timezone">Timezone</FieldLabel>
<FieldDescription>
Choose your local timezone for accurate scheduling.
</FieldDescription>
<NativeSelect id="native-select-timezone">
<NativeSelectOption value="">Select timezone</NativeSelectOption>
<NativeSelectOption value="utc">UTC</NativeSelectOption>
<NativeSelectOption value="est">Eastern Time</NativeSelectOption>
<NativeSelectOption value="pst">Pacific Time</NativeSelectOption>
</NativeSelect>
</Field>
<Field>
<FieldLabel htmlFor="native-select-grouped">
Grouped Options
</FieldLabel>
<NativeSelect id="native-select-grouped">
<NativeSelectOption value="">Select a region</NativeSelectOption>
<NativeSelectOptGroup label="North America">
<NativeSelectOption value="us">United States</NativeSelectOption>
<NativeSelectOption value="ca">Canada</NativeSelectOption>
<NativeSelectOption value="mx">Mexico</NativeSelectOption>
</NativeSelectOptGroup>
<NativeSelectOptGroup label="Europe">
<NativeSelectOption value="uk">United Kingdom</NativeSelectOption>
<NativeSelectOption value="fr">France</NativeSelectOption>
<NativeSelectOption value="de">Germany</NativeSelectOption>
</NativeSelectOptGroup>
</NativeSelect>
<FieldDescription>
Native select with grouped options using optgroup.
</FieldDescription>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="native-select-invalid">
Invalid Native Select
</FieldLabel>
<NativeSelect id="native-select-invalid" aria-invalid>
<NativeSelectOption value="">
This field has an error
</NativeSelectOption>
<NativeSelectOption value="option1">Option 1</NativeSelectOption>
<NativeSelectOption value="option2">Option 2</NativeSelectOption>
<NativeSelectOption value="option3">Option 3</NativeSelectOption>
</NativeSelect>
<FieldDescription>
This field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="native-select-disabled-field">
Disabled Field
</FieldLabel>
<NativeSelect id="native-select-disabled-field" disabled>
<NativeSelectOption value="">Cannot select</NativeSelectOption>
<NativeSelectOption value="option1">Option 1</NativeSelectOption>
<NativeSelectOption value="option2">Option 2</NativeSelectOption>
<NativeSelectOption value="option3">Option 3</NativeSelectOption>
</NativeSelect>
<FieldDescription>This field is currently disabled.</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function CheckboxFields() {
return (
<Example title="Checkbox Fields">
<FieldGroup>
<Field orientation="horizontal">
<Checkbox id="checkbox-basic" defaultSelected />
<FieldLabel htmlFor="checkbox-basic">
I agree to the terms and conditions
</FieldLabel>
</Field>
<Field orientation="horizontal">
<FieldLabel htmlFor="checkbox-right">
Accept terms and conditions
</FieldLabel>
<Checkbox id="checkbox-right" />
</Field>
<Field orientation="horizontal">
<Checkbox id="checkbox-with-desc" />
<FieldContent>
<FieldLabel htmlFor="checkbox-with-desc">
Subscribe to newsletter
</FieldLabel>
<FieldDescription>
Receive weekly updates about new features and promotions.
</FieldDescription>
</FieldContent>
</Field>
<FieldLabel htmlFor="checkbox-with-title">
<Field orientation="horizontal">
<Checkbox id="checkbox-with-title" />
<FieldContent>
<FieldTitle>Enable Touch ID</FieldTitle>
<FieldDescription>
Enable Touch ID to quickly unlock your device.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
<FieldSet>
<FieldLegend variant="label">Preferences</FieldLegend>
<FieldDescription>
Select all that apply to customize your experience.
</FieldDescription>
<FieldGroup className="gap-3">
<Field orientation="horizontal">
<Checkbox id="pref-dark" />
<FieldLabel htmlFor="pref-dark">Dark mode</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="pref-compact" />
<FieldLabel htmlFor="pref-compact">Compact view</FieldLabel>
</Field>
<Field orientation="horizontal">
<Checkbox id="pref-notifications" />
<FieldLabel htmlFor="pref-notifications">
Enable notifications
</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
<Field data-invalid orientation="horizontal">
<Checkbox id="checkbox-invalid" isInvalid />
<FieldLabel htmlFor="checkbox-invalid">Invalid checkbox</FieldLabel>
</Field>
<Field data-disabled orientation="horizontal">
<Checkbox id="checkbox-disabled-field" isDisabled />
<FieldLabel htmlFor="checkbox-disabled-field">
Disabled checkbox
</FieldLabel>
</Field>
</FieldGroup>
</Example>
)
}
function RadioFields() {
return (
<Example title="Radio Fields">
<FieldGroup>
<FieldSet>
<FieldLegend variant="label">Subscription Plan</FieldLegend>
<RadioGroup defaultValue="free">
<Field orientation="horizontal">
<RadioGroupItem value="free" id="radio-free" />
<FieldLabel htmlFor="radio-free">Free Plan</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="pro" id="radio-pro" />
<FieldLabel htmlFor="radio-pro">Pro Plan</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="enterprise" id="radio-enterprise" />
<FieldLabel htmlFor="radio-enterprise">Enterprise</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
<FieldSet>
<FieldLegend variant="label">Battery Level</FieldLegend>
<FieldDescription>
Choose your preferred battery level.
</FieldDescription>
<RadioGroup>
<Field orientation="horizontal">
<RadioGroupItem value="high" id="battery-high" />
<FieldLabel htmlFor="battery-high">High</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="medium" id="battery-medium" />
<FieldLabel htmlFor="battery-medium">Medium</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="low" id="battery-low" />
<FieldLabel htmlFor="battery-low">Low</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
<RadioGroup className="gap-6">
<Field orientation="horizontal">
<RadioGroupItem value="option1" id="radio-content-1" />
<FieldContent>
<FieldLabel htmlFor="radio-content-1">Enable Touch ID</FieldLabel>
<FieldDescription>
Enable Touch ID to quickly unlock your device.
</FieldDescription>
</FieldContent>
</Field>
</RadioGroup>
<RadioGroup className="gap-3">
<FieldLabel htmlFor="radio-title-1">
<Field orientation="horizontal">
<RadioGroupItem value="title1" id="radio-title-1" />
<FieldContent>
<FieldTitle>Enable Touch ID</FieldTitle>
<FieldDescription>
Enable Touch ID to quickly unlock your device.
</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
</RadioGroup>
<FieldSet>
<FieldLegend variant="label">Invalid Radio Group</FieldLegend>
<RadioGroup isInvalid>
<Field data-invalid orientation="horizontal">
<RadioGroupItem value="invalid1" id="radio-invalid-1" />
<FieldLabel htmlFor="radio-invalid-1">
Invalid Option 1
</FieldLabel>
</Field>
<Field data-invalid orientation="horizontal">
<RadioGroupItem value="invalid2" id="radio-invalid-2" />
<FieldLabel htmlFor="radio-invalid-2">
Invalid Option 2
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
<FieldSet>
<FieldLegend variant="label">Disabled Radio Group</FieldLegend>
<RadioGroup isDisabled>
<Field data-disabled orientation="horizontal">
<RadioGroupItem value="disabled1" id="radio-disabled-1" />
<FieldLabel htmlFor="radio-disabled-1">
Disabled Option 1
</FieldLabel>
</Field>
<Field data-disabled orientation="horizontal">
<RadioGroupItem value="disabled2" id="radio-disabled-2" />
<FieldLabel htmlFor="radio-disabled-2">
Disabled Option 2
</FieldLabel>
</Field>
</RadioGroup>
</FieldSet>
</FieldGroup>
</Example>
)
}
function SwitchFields() {
return (
<Example title="Switch Fields">
<FieldGroup>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="switch-airplane">Airplane Mode</FieldLabel>
<FieldDescription>
Turn on airplane mode to disable all connections.
</FieldDescription>
</FieldContent>
<Switch id="switch-airplane" />
</Field>
<Field orientation="horizontal">
<FieldLabel htmlFor="switch-dark">Dark Mode</FieldLabel>
<Switch id="switch-dark" />
</Field>
<Field orientation="horizontal">
<Switch id="switch-marketing" />
<FieldContent>
<FieldLabel htmlFor="switch-marketing">Marketing Emails</FieldLabel>
<FieldDescription>
Receive emails about new products, features, and more.
</FieldDescription>
</FieldContent>
</Field>
<Field>
<FieldLabel>Privacy Settings</FieldLabel>
<FieldDescription>Manage your privacy preferences.</FieldDescription>
<Field orientation="horizontal">
<Switch id="switch-profile" defaultSelected />
<FieldLabel htmlFor="switch-profile">
Make profile visible to others
</FieldLabel>
</Field>
<Field orientation="horizontal">
<Switch id="switch-email" />
<FieldLabel htmlFor="switch-email">
Show email on profile
</FieldLabel>
</Field>
</Field>
<Field data-invalid orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="switch-invalid">Invalid Switch</FieldLabel>
<FieldDescription>
This switch has validation errors.
</FieldDescription>
</FieldContent>
<Switch id="switch-invalid" />
</Field>
<Field data-disabled orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="switch-disabled-field">
Disabled Switch
</FieldLabel>
<FieldDescription>
This switch is currently disabled.
</FieldDescription>
</FieldContent>
<Switch id="switch-disabled-field" isDisabled />
</Field>
</FieldGroup>
</Example>
)
}
function SliderFields() {
const [volume, setVolume] = useState([50])
const [brightness, setBrightness] = useState([75])
const [temperature, setTemperature] = useState([0.3, 0.7])
const [priceRange, setPriceRange] = useState([25, 75])
const [colorBalance, setColorBalance] = useState([10, 20, 70])
return (
<Example title="Slider Fields">
<FieldGroup>
<Field>
<FieldLabel htmlFor="slider-volume">Volume</FieldLabel>
<Slider
id="slider-volume"
value={volume}
onChange={setVolume}
maxValue={100}
step={1}
/>
</Field>
<Field>
<FieldLabel htmlFor="slider-brightness">Screen Brightness</FieldLabel>
<Slider
id="slider-brightness"
value={brightness}
onChange={setBrightness}
maxValue={100}
step={5}
/>
<FieldDescription>
Current brightness: {brightness[0]}%
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="slider-quality">Video Quality</FieldLabel>
<FieldDescription>
Higher quality uses more bandwidth.
</FieldDescription>
<Slider
id="slider-quality"
defaultValue={[720]}
maxValue={1080}
minValue={360}
step={360}
/>
</Field>
<Field>
<FieldLabel htmlFor="slider-temperature">
Temperature Range
</FieldLabel>
<Slider
id="slider-temperature"
value={temperature}
onChange={setTemperature}
minValue={0}
maxValue={1}
step={0.1}
/>
<FieldDescription>
Range: {temperature[0].toFixed(1)} - {temperature[1].toFixed(1)}
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="slider-price-range">Price Range</FieldLabel>
<Slider
id="slider-price-range"
value={priceRange}
onChange={setPriceRange}
maxValue={100}
step={5}
/>
<FieldDescription>
${priceRange[0]} - ${priceRange[1]}
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="slider-color-balance">Color Balance</FieldLabel>
<Slider
id="slider-color-balance"
value={colorBalance}
onChange={setColorBalance}
maxValue={100}
step={10}
/>
<FieldDescription>
Red: {colorBalance[0]}%, Green: {colorBalance[1]}%, Blue:{" "}
{colorBalance[2]}%
</FieldDescription>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="slider-invalid">Invalid Slider</FieldLabel>
<Slider
id="slider-invalid"
defaultValue={[30]}
maxValue={100}
aria-invalid
/>
<FieldDescription>
This slider has validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="slider-disabled-field">
Disabled Slider
</FieldLabel>
<Slider
id="slider-disabled-field"
defaultValue={[50]}
maxValue={100}
isDisabled
/>
<FieldDescription>
This slider is currently disabled.
</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function InputOTPFields() {
const [value, setValue] = useState("")
const [pinValue, setPinValue] = useState("")
return (
<Example title="OTP Input Fields">
<FieldGroup>
<Field>
<FieldLabel htmlFor="otp-basic">Verification Code</FieldLabel>
<InputOTP id="otp-basic" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</Field>
<Field>
<FieldLabel htmlFor="otp-with-desc">Enter OTP</FieldLabel>
<InputOTP
id="otp-with-desc"
maxLength={6}
value={value}
onChange={setValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter the 6-digit code sent to your email.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="otp-separator">
Two-Factor Authentication
</FieldLabel>
<InputOTP id="otp-separator" 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>
<FieldDescription>
Enter the code from your authenticator app.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="otp-pin">PIN Code</FieldLabel>
<InputOTP
id="otp-pin"
maxLength={4}
pattern={REGEXP_ONLY_DIGITS}
value={pinValue}
onChange={setPinValue}
>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
Enter your 4-digit PIN (numbers only).
</FieldDescription>
</Field>
<Field data-invalid>
<FieldLabel htmlFor="otp-invalid">Invalid OTP</FieldLabel>
<InputOTP id="otp-invalid" maxLength={6}>
<InputOTPGroup>
<InputOTPSlot index={0} aria-invalid />
<InputOTPSlot index={1} aria-invalid />
<InputOTPSlot index={2} aria-invalid />
<InputOTPSlot index={3} aria-invalid />
<InputOTPSlot index={4} aria-invalid />
<InputOTPSlot index={5} aria-invalid />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
This OTP field contains validation errors.
</FieldDescription>
</Field>
<Field data-disabled>
<FieldLabel htmlFor="otp-disabled-field">Disabled OTP</FieldLabel>
<InputOTP id="otp-disabled-field" maxLength={6} disabled>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
<FieldDescription>
This OTP field is currently disabled.
</FieldDescription>
</Field>
</FieldGroup>
</Example>
)
}
function HorizontalFields() {
const basicItems = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Orange", value: "orange" },
]
return (
<Example title="Horizontal Fields">
<FieldGroup className="**:data-[slot=field-content]:min-w-48">
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="horizontal-input">Username</FieldLabel>
<FieldDescription>Enter your preferred username.</FieldDescription>
</FieldContent>
<Input id="horizontal-input" placeholder="johndoe" />
</Field>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="horizontal-textarea">Bio</FieldLabel>
<FieldDescription>
Write a short description about yourself.
</FieldDescription>
</FieldContent>
<Textarea
id="horizontal-textarea"
placeholder="Tell us about yourself..."
/>
</Field>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="horizontal-switch">
Email Notifications
</FieldLabel>
<FieldDescription>
Receive email updates about your account.
</FieldDescription>
</FieldContent>
<Switch id="horizontal-switch" />
</Field>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="horizontal-select">Favorite Fruit</FieldLabel>
<FieldDescription>Choose your favorite fruit.</FieldDescription>
</FieldContent>
<Select placeholder="Select a fruit">
<SelectTrigger id="horizontal-select">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{basicItems.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Field>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="horizontal-native-select">Country</FieldLabel>
<FieldDescription>Select your country.</FieldDescription>
</FieldContent>
<NativeSelect id="horizontal-native-select">
<NativeSelectOption value="">Select a country</NativeSelectOption>
<NativeSelectOption value="us">United States</NativeSelectOption>
<NativeSelectOption value="uk">United Kingdom</NativeSelectOption>
<NativeSelectOption value="ca">Canada</NativeSelectOption>
</NativeSelect>
</Field>
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="horizontal-slider">Volume</FieldLabel>
<FieldDescription>Adjust the volume level.</FieldDescription>
</FieldContent>
<Slider id="horizontal-slider" defaultValue={[50]} maxValue={100} />
</Field>
</FieldGroup>
</Example>
)
}

View File

@@ -0,0 +1,93 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Dialog,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import {
HoverCard,
HoverCardTrigger,
} from "@/registry/bases/aria/ui/hover-card"
export default function HoverCardExample() {
return (
<ExampleWrapper>
<HoverCardSides />
<HoverCardInDialog />
</ExampleWrapper>
)
}
const HOVER_CARD_PLACEMENTS = [
"start",
"left",
"top",
"bottom",
"right",
"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_PLACEMENTS.map((placement) => (
<HoverCardTrigger key={placement} delay={100} closeDelay={100}>
<Button variant="outline" className="capitalize">
{placement}
</Button>
<HoverCard placement={placement}>
<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 {placement} side of the
trigger.
</p>
</div>
</HoverCard>
</HoverCardTrigger>
))}
</div>
</Example>
)
}
function HoverCardInDialog() {
return (
<Example title="In Dialog">
<DialogTrigger>
<Button variant="outline">Open Dialog</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Hover Card Example</DialogTitle>
<DialogDescription>
Hover over the button below to see the hover card.
</DialogDescription>
</DialogHeader>
<HoverCardTrigger delay={100} closeDelay={100}>
<Button variant="outline" className="w-fit">
Hover me
</Button>
<HoverCard>
<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>
</HoverCard>
</HoverCardTrigger>
</Dialog>
</DialogTrigger>
</Example>
)
}

View File

@@ -0,0 +1,268 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/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 id="usd">USD</SelectItem>
<SelectItem id="eur">EUR</SelectItem>
<SelectItem id="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 id="us">United States</SelectItem>
<SelectItem id="uk">United Kingdom</SelectItem>
<SelectItem id="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,897 @@
"use client"
import { useState } from "react"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
ButtonGroup,
ButtonGroupText,
} from "@/registry/bases/aria/ui/button-group"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/registry/bases/aria/ui/input-group"
import { Kbd, KbdGroup } from "@/registry/bases/aria/ui/kbd"
import {
Popover,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/registry/bases/aria/ui/popover"
import { Spinner } from "@/registry/bases/aria/ui/spinner"
import { Textarea } from "@/registry/bases/aria/ui/textarea"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/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"
onPress={() => 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">
<TooltipTrigger>
<InputGroupButton className="rounded-full" size="icon-xs">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupButton>
<Tooltip>This is content in a tooltip.</Tooltip>
</TooltipTrigger>
</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>
<DropdownMenuTrigger>
<InputGroupButton className="text-muted-foreground tabular-nums">
{country}{" "}
<IconPlaceholder
lucide="ChevronDownIcon"
tabler="IconChevronDown"
hugeicons="ArrowDownIcon"
phosphor="CaretDownIcon"
remixicon="RiArrowDownSLine"
/>
</InputGroupButton>
<DropdownMenu
placement="bottom start"
className="min-w-16"
offset={10}
crossOffset={-8}
>
<DropdownMenuItem onAction={() => setCountry("+1")}>
+1
</DropdownMenuItem>
<DropdownMenuItem onAction={() => setCountry("+44")}>
+44
</DropdownMenuItem>
<DropdownMenuItem onAction={() => setCountry("+46")}>
+46
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
This is a description of the input group.
</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="input-secure-19">Popover</FieldLabel>
<InputGroup>
<PopoverTrigger>
<InputGroupAddon>
<InputGroupButton variant="secondary" size="icon-xs">
<IconPlaceholder
lucide="InfoIcon"
tabler="IconInfoCircle"
hugeicons="AlertCircleIcon"
phosphor="InfoIcon"
remixicon="RiInformationLine"
/>
</InputGroupButton>
</InputGroupAddon>
<Popover placement="bottom start">
<PopoverHeader>
<PopoverTitle>Your connection is not secure.</PopoverTitle>
<PopoverDescription>
You should not enter any sensitive information on this site.
</PopoverDescription>
</PopoverHeader>
</Popover>
</PopoverTrigger>
<InputGroupAddon className="pl-1 text-muted-foreground">
https://
</InputGroupAddon>
<InputGroupInput id="input-secure-19" />
<InputGroupAddon align="inline-end">
<InputGroupButton
size="icon-xs"
onPress={() => 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/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/registry/bases/aria/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,188 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Kbd, KbdGroup } from "@/registry/bases/aria/ui/kbd"
import { Tooltip, TooltipTrigger } from "@/registry/bases/aria/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">
<TooltipTrigger>
<Button size="icon-sm" variant="outline">
<IconPlaceholder
lucide="SaveIcon"
tabler="IconDeviceFloppy"
hugeicons="FloppyDiskIcon"
phosphor="FloppyDiskIcon"
remixicon="RiSaveLine"
/>
</Button>
<Tooltip className="pr-1.5">
<div className="flex items-center gap-2">
Save Changes <Kbd>S</Kbd>
</div>
</Tooltip>
</TooltipTrigger>
</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/aria/components/example"
import { Checkbox } from "@/registry/bases/aria/ui/checkbox"
import { Field } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import { Label } from "@/registry/bases/aria/ui/label"
import { Textarea } from "@/registry/bases/aria/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,326 @@
"use client"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/registry/bases/aria/ui/accordion"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/registry/bases/aria/ui/drawer"
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/registry/bases/aria/ui/marker"
import { Spinner } from "@/registry/bases/aria/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={(props) => <a href="#" {...props} />}>
<MarkerIcon>
<IconPlaceholder
lucide="GitBranchIcon"
tabler="IconGitBranch"
hugeicons="GitBranchIcon"
phosphor="GitBranchIcon"
remixicon="RiGitBranchLine"
/>
</MarkerIcon>
<MarkerContent>Marker as a link</MarkerContent>
</Marker>
<Marker
className="transition-colors hover:text-foreground"
render={(props) => (
<button {...props} onClick={() => toast("You clicked the button")} />
)}
>
<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 id="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,923 @@
import type { UIMessage } from "ai"
import { createChat } from "@/lib/ai"
import { type MessagePartRenderProps } from "@/components/message-parts"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Attachment,
AttachmentAction,
AttachmentActions,
AttachmentContent,
AttachmentDescription,
AttachmentGroup,
AttachmentMedia,
AttachmentTitle,
} from "@/registry/bases/aria/ui/attachment"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/registry/bases/aria/ui/bubble"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/registry/bases/aria/ui/marker"
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageGroup,
MessageHeader,
} from "@/registry/bases/aria/ui/message"
import { Spinner } from "@/registry/bases/aria/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"
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"
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"
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"
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">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</Button>
<Button variant="ghost" size="icon" aria-label="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">
<IconPlaceholder
lucide="CopyIcon"
tabler="IconCopy"
hugeicons="Copy01Icon"
phosphor="CopyIcon"
remixicon="RiFileCopyLine"
/>
</Button>
<Button variant="ghost" size="icon" aria-label="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"
>
<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/aria/components/example"
import { Bubble, BubbleContent } from "@/registry/bases/aria/ui/bubble"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/registry/bases/aria/ui/input-group"
import {
Marker,
MarkerContent,
MarkerIcon,
} from "@/registry/bases/aria/ui/marker"
import { Message, MessageContent } from "@/registry/bases/aria/ui/message"
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/registry/bases/aria/ui/message-scroller"
import { Spinner } from "@/registry/bases/aria/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"
isDisabled={!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/aria/components/example"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/registry/bases/aria/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,128 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Field, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/registry/bases/aria/ui/pagination"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/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 placement="bottom start">
<SelectGroup>
<SelectItem id="10">10</SelectItem>
<SelectItem id="25">25</SelectItem>
<SelectItem id="50">50</SelectItem>
<SelectItem id="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,187 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Dialog,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Popover,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@/registry/bases/aria/ui/popover"
export default function PopoverExample() {
return (
<ExampleWrapper>
<PopoverBasic />
<PopoverSides />
<PopoverWithForm />
<PopoverAlignments />
<PopoverInDialog />
</ExampleWrapper>
)
}
function PopoverBasic() {
return (
<Example title="Basic">
<PopoverTrigger>
<Button variant="outline" className="w-fit">
Open Popover
</Button>
<Popover placement="bottom start">
<PopoverHeader>
<PopoverTitle>Dimensions</PopoverTitle>
<PopoverDescription>
Set the dimensions for the layer.
</PopoverDescription>
</PopoverHeader>
</Popover>
</PopoverTrigger>
</Example>
)
}
function PopoverSides() {
return (
<Example title="Sides">
<div className="flex flex-col gap-2">
<div className="flex flex-wrap gap-2">
{(["start", "left", "top"] as const).map((placement) => {
return (
<PopoverTrigger key={placement}>
<Button variant="outline" className="w-fit capitalize">
{placement}
</Button>
<Popover placement={placement} className="w-40">
<p>Popover on {placement}</p>
</Popover>
</PopoverTrigger>
)
})}
</div>
<div className="flex flex-wrap gap-2">
{(["bottom", "right", "end"] as const).map((placement) => {
return (
<PopoverTrigger key={placement}>
<Button variant="outline" className="w-fit capitalize">
{placement}
</Button>
<Popover placement={placement} className="w-40">
<p>Popover on {placement}</p>
</Popover>
</PopoverTrigger>
)
})}
</div>
</div>
</Example>
)
}
function PopoverWithForm() {
return (
<Example title="With Form">
<PopoverTrigger>
<Button variant="outline">Open Popover</Button>
<Popover className="w-64" placement="bottom 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>
</Popover>
</PopoverTrigger>
</Example>
)
}
function PopoverAlignments() {
return (
<Example title="Alignments">
<div className="flex gap-6">
<PopoverTrigger>
<Button variant="outline" size="sm">
Start
</Button>
<Popover placement="bottom start" className="w-40">
Aligned to start
</Popover>
</PopoverTrigger>
<PopoverTrigger>
<Button variant="outline" size="sm">
Center
</Button>
<Popover placement="bottom" className="w-40">
Aligned to center
</Popover>
</PopoverTrigger>
<PopoverTrigger>
<Button variant="outline" size="sm">
End
</Button>
<Popover placement="bottom end" className="w-40">
Aligned to end
</Popover>
</PopoverTrigger>
</div>
</Example>
)
}
function PopoverInDialog() {
return (
<Example title="In Dialog">
<DialogTrigger>
<Button variant="outline">Open Dialog</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Popover Example</DialogTitle>
<DialogDescription>
Click the button below to see the popover.
</DialogDescription>
</DialogHeader>
<PopoverTrigger>
<Button variant="outline" className="w-fit">
Open Popover
</Button>
<Popover placement="bottom start">
<PopoverHeader>
<PopoverTitle>Popover in Dialog</PopoverTitle>
<PopoverDescription>
This popover appears inside a dialog. Click the button to open
it.
</PopoverDescription>
</PopoverHeader>
</Popover>
</PopoverTrigger>
</Dialog>
</DialogTrigger>
</Example>
)
}

View File

@@ -0,0 +1,142 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Item,
ItemActions,
ItemContent,
ItemGroup,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/registry/bases/aria/ui/progress"
import { Slider } from "@/registry/bases/aria/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}
onChange={setValue}
minValue={0}
maxValue={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,470 @@
"use client"
import * as React from "react"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
Dialog,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import {
Questionnaire,
QuestionnaireActions,
QuestionnaireChoice,
QuestionnaireChoiceDescription,
QuestionnaireChoices,
QuestionnaireDescription,
QuestionnaireError,
QuestionnaireInput,
QuestionnaireItem,
QuestionnaireNext,
QuestionnairePrevious,
QuestionnaireProgress,
QuestionnaireSkip,
QuestionnaireSubmit,
QuestionnaireTitle,
} from "@/registry/bases/aria/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"
>
<DialogTrigger>
<Button variant="outline">Open questionnaire</Button>
<Dialog>
<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>
</Dialog>
</DialogTrigger>
</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/aria/components/example"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/registry/bases/aria/ui/field"
import {
RadioGroup,
RadioGroupItem,
} from "@/registry/bases/aria/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" isDisabled>
<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" isInvalid>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="email" id="invalid-email" />
<FieldLabel htmlFor="invalid-email" className="font-normal">
Email only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="sms" id="invalid-sms" />
<FieldLabel htmlFor="invalid-sms" className="font-normal">
SMS only
</FieldLabel>
</Field>
<Field orientation="horizontal" data-invalid>
<RadioGroupItem value="both" id="invalid-both" />
<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/aria/components/example"
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/registry/bases/aria/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,85 @@
import * as React from "react"
import Image from "next/image"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { ScrollArea } from "@/registry/bases/aria/ui/scroll-area"
import { Separator } from "@/registry/bases/aria/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>
</ScrollArea>
</Example>
)
}

View File

@@ -0,0 +1,793 @@
"use client"
import { Autocomplete, useFilter } from "react-aria-components"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Dialog,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/registry/bases/aria/ui/dialog"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Item,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
NativeSelect,
NativeSelectOption,
} from "@/registry/bases/aria/ui/native-select"
import {
Select,
SelectContent,
SelectEmpty,
SelectGroup,
SelectInput,
SelectItem,
SelectLabel,
SelectList,
SelectPopover,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
export default function SelectExample() {
return (
<ExampleWrapper>
<SelectBasic />
<SelectAutocomplete />
<SelectSides />
<SelectWithIcons />
<SelectWithGroups />
<SelectLargeList />
<SelectMultiple />
<SelectSizes />
<SelectPlan />
<SelectWithButton />
<SelectItemAligned />
<SelectWithField />
<SelectInvalid />
<SelectInline />
<SelectDisabled />
<SelectInDialog />
</ExampleWrapper>
)
}
function SelectBasic() {
const items = [
{ 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 aria-label="Fruits" placeholder="Select a fruit">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectSides() {
const items = [
{ 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">
{(["start", "left", "top", "bottom", "right", "end"] as const).map(
(placement) => (
<Select key={placement} aria-label="Fruits" placeholder="Select">
<SelectTrigger className="w-28 capitalize">
<SelectValue />
</SelectTrigger>
<SelectContent placement={placement}>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={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"
/>
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 aria-label="Chart Type">
<SelectTrigger size="sm">
<SelectValue>
{({ isPlaceholder, defaultChildren }) =>
isPlaceholder ? (
<>
<IconPlaceholder
lucide="ChartLineIcon"
tabler="IconChartLine"
hugeicons="Chart03Icon"
phosphor="ChartLineIcon"
remixicon="RiLineChartLine"
/>
Chart Type
</>
) : (
defaultChildren
)
}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Select aria-label="Chart Type">
<SelectTrigger size="default">
<SelectValue>
{({ isPlaceholder, defaultChildren }) =>
isPlaceholder ? (
<>
<IconPlaceholder
lucide="ChartLineIcon"
tabler="IconChartLine"
hugeicons="Chart03Icon"
phosphor="ChartLineIcon"
remixicon="RiLineChartLine"
/>
Chart Type
</>
) : (
defaultChildren
)
}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={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" },
]
return (
<Example title="With Groups & Labels">
<Select aria-label="Fruits and Vegetables">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{fruits.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
{vegetables.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectLargeList() {
const items = Array.from({ length: 100 }).map((_, i) => ({
label: `Item ${i}`,
value: `item-${i}`,
}))
return (
<Example title="Large List">
<Select aria-label="Items">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectSizes() {
const items = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
]
return (
<Example title="Sizes">
<div className="flex flex-col gap-4">
<Select aria-label="Fruits" placeholder="Select a fruit">
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Select aria-label="Fruits">
<SelectTrigger size="default">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</Example>
)
}
function SelectWithButton() {
const items = [
{ 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 aria-label="Fruits" placeholder="Select a fruit">
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Button variant="outline" size="sm">
Submit
</Button>
</div>
<div className="flex items-center gap-2">
<Select aria-label="Fruits">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Button variant="outline">Submit</Button>
</div>
</div>
</Example>
)
}
function SelectItemAligned() {
const items = [
{ 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 aria-label="Fruits" placeholder="Select a fruit">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem
key={item.value}
id={item.value}
isDisabled={item.disabled}
>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectWithField() {
const items = [
{ 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 aria-label="Fruits" placeholder="Select a fruit">
<SelectTrigger id="select-fruit">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldDescription>
Choose your favorite fruit from the list.
</FieldDescription>
</Field>
</Example>
)
}
function SelectInvalid() {
const items = [
{ 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 aria-label="Fruits" placeholder="Select a fruit" isInvalid>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Field data-invalid>
<FieldLabel htmlFor="select-fruit-invalid">Favorite Fruit</FieldLabel>
<Select aria-label="Fruits" isInvalid>
<SelectTrigger id="select-fruit-invalid">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<FieldError errors={[{ message: "Please select a valid fruit." }]} />
</Field>
</div>
</Example>
)
}
function SelectInline() {
const items = [
{ 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 aria-label="Filter" placeholder="Filter">
<SelectTrigger className="w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={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: "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 aria-label="Fruits" placeholder="Select a fruit" isDisabled>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem
key={item.value}
id={item.value}
isDisabled={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 aria-label="Subscription Plan" defaultValue={plans[0].name}>
<SelectTrigger className="h-auto! w-72">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{plans.map((plan) => (
<SelectItem key={plan.name} id={plan.name} textValue={plan.name}>
<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 selectionMode="multiple" aria-label="Fruits">
<SelectTrigger className="w-72">
<SelectValue<(typeof items)[0]>>
{({ selectedItems }) =>
selectedItems.length === 0
? "Select fruits"
: selectedItems.length === 1
? (selectedItems[0]?.label ?? "")
: `${selectedItems.length} fruits selected`
}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup items={items}>
{(item) => <SelectItem id={item.value}>{item.label}</SelectItem>}
</SelectGroup>
</SelectContent>
</Select>
</Example>
)
}
function SelectInDialog() {
const items = [
{ 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">
<DialogTrigger>
<Button variant="outline">Open Dialog</Button>
<Dialog>
<DialogHeader>
<DialogTitle>Select Example</DialogTitle>
<DialogDescription>
Use the select below to choose a fruit.
</DialogDescription>
</DialogHeader>
<Select aria-label="Fruits" placeholder="Select a fruit">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</Dialog>
</DialogTrigger>
</Example>
)
}
const countries = [
{
code: "ar",
value: "argentina",
label: "Argentina",
continent: "South America",
},
{ code: "au", value: "australia", label: "Australia", continent: "Oceania" },
{ code: "br", value: "brazil", label: "Brazil", continent: "South America" },
{ code: "ca", value: "canada", label: "Canada", continent: "North America" },
{ code: "cn", value: "china", label: "China", continent: "Asia" },
{
code: "co",
value: "colombia",
label: "Colombia",
continent: "South America",
},
{ code: "eg", value: "egypt", label: "Egypt", continent: "Africa" },
{ code: "fr", value: "france", label: "France", continent: "Europe" },
{ code: "de", value: "germany", label: "Germany", continent: "Europe" },
{ code: "it", value: "italy", label: "Italy", continent: "Europe" },
{ code: "jp", value: "japan", label: "Japan", continent: "Asia" },
{ code: "ke", value: "kenya", label: "Kenya", continent: "Africa" },
{ code: "mx", value: "mexico", label: "Mexico", continent: "North America" },
{
code: "nz",
value: "new-zealand",
label: "New Zealand",
continent: "Oceania",
},
{ code: "ng", value: "nigeria", label: "Nigeria", continent: "Africa" },
{
code: "za",
value: "south-africa",
label: "South Africa",
continent: "Africa",
},
{ code: "kr", value: "south-korea", label: "South Korea", continent: "Asia" },
{
code: "gb",
value: "united-kingdom",
label: "United Kingdom",
continent: "Europe",
},
{
code: "us",
value: "united-states",
label: "United States",
continent: "North America",
},
]
function SelectAutocomplete() {
const { contains } = useFilter({ sensitivity: "base" })
return (
<Example title="With Autocomplete">
<Select placeholder="Select country">
<SelectTrigger className="w-full max-w-48">
<SelectValue />
</SelectTrigger>
<Autocomplete filter={contains}>
<SelectPopover>
<SelectInput />
<SelectList
renderEmptyState={() => (
<SelectEmpty>No items found.</SelectEmpty>
)}
>
<SelectGroup items={countries}>
{(item) => (
<SelectItem id={item.value}>{item.label}</SelectItem>
)}
</SelectGroup>
</SelectList>
</SelectPopover>
</Autocomplete>
</Select>
</Example>
)
}

View File

@@ -0,0 +1,100 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Separator } from "@/registry/bases/aria/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,131 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import { Field, FieldGroup, FieldLabel } from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Sheet,
SheetClose,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/registry/bases/aria/ui/sheet"
export default function SheetExample() {
return (
<ExampleWrapper>
<SheetWithForm />
<SheetNoCloseButton />
<SheetWithSides />
</ExampleWrapper>
)
}
function SheetWithForm() {
return (
<Example title="With Form">
<SheetTrigger>
<Button variant="outline">Open</Button>
<Sheet>
<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 variant="outline">Close</SheetClose>
</SheetFooter>
</Sheet>
</SheetTrigger>
</Example>
)
}
function SheetNoCloseButton() {
return (
<Example title="No Close Button">
<SheetTrigger>
<Button variant="outline">No Close Button</Button>
<Sheet 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>
</Sheet>
</SheetTrigger>
</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) => (
<SheetTrigger key={side}>
<Button variant="outline" className="capitalize">
{side}
</Button>
<Sheet
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 variant="outline">Cancel</SheetClose>
</SheetFooter>
</Sheet>
</SheetTrigger>
))}
</div>
</Example>
)
}

View File

@@ -0,0 +1,281 @@
"use client"
import * as React from "react"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import { Label } from "@/registry/bases/aria/ui/label"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/aria/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>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
className="aria-expanded:bg-sidebar-accent aria-expanded: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>
</SidebarMenuButton>
<DropdownMenu>
{data.versions.map((version) => (
<DropdownMenuItem
key={version}
onAction={() => setSelectedVersion(version)}
>
v{version}{" "}
{version === selectedVersion && (
<IconPlaceholder
lucide="CheckIcon"
tabler="IconCheck"
hugeicons="Tick02Icon"
phosphor="CheckIcon"
remixicon="RiCheckLine"
className="ml-auto"
/>
)}
</DropdownMenuItem>
))}
</DropdownMenu>
</DropdownMenuTrigger>
</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
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,270 @@
"use client"
import { Button } from "@/registry/bases/aria/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/bases/aria/ui/card"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Field } from "@/registry/bases/aria/ui/field"
import {
Item,
ItemContent,
ItemDescription,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/aria/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 href="#" size="lg">
<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) => (
<SidebarMenuItem key={item.title}>
<DropdownMenuTrigger>
<SidebarMenuButton className="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground">
{item.title}{" "}
<IconPlaceholder
lucide="MoreHorizontalIcon"
tabler="IconDots"
hugeicons="MoreHorizontalCircle01Icon"
phosphor="DotsThreeOutlineIcon"
remixicon="RiMoreLine"
className="ml-auto"
/>
</SidebarMenuButton>
{item.items?.length ? (
<DropdownMenu placement="right top">
<DropdownMenuGroup>
{item.items.map((subItem) => (
<DropdownMenuItem
href={subItem.url}
key={subItem.title}
>
{subItem.title}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenu>
) : null}
</DropdownMenuTrigger>
</SidebarMenuItem>
))}
</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,449 @@
"use client"
import * as React from "react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/bases/aria/ui/avatar"
import { buttonVariants } from "@/registry/bases/aria/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/aria/ui/collapsible"
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import {
Item,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/registry/bases/aria/ui/item"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/aria/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>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
className="aria-expanded:bg-sidebar-accent aria-expanded:text-sidebar-accent-foreground"
>
<span
data-slot="button"
className={buttonVariants({
size: "icon-sm",
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>
</span>
<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"
/>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuGroup>
<DropdownMenuLabel>Teams</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuGroup>
{data.teams.map((team) => (
<DropdownMenuItem
key={team.name}
onAction={() => setActiveTeam(team)}
>
{team.name}
</DropdownMenuItem>
))}
</DropdownMenuGroup>
</DropdownMenu>
</DropdownMenuTrigger>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{data.navMain.map((item) => (
<Collapsible
key={item.title}
defaultExpanded={item.isActive}
className="group/collapsible"
>
<SidebarMenuItem>
<SidebarMenuButton slot="trigger" 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-expanded/collapsible:rotate-90"
/>
</SidebarMenuButton>
<CollapsibleContent>
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton 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 href={item.url}>
{item.icon}
{item.name}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
className="aria-expanded:bg-sidebar-accent aria-expanded: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"
/>
</SidebarMenuButton>
<DropdownMenu>
<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>
</DropdownMenu>
</DropdownMenuTrigger>
</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,281 @@
"use client"
import * as React from "react"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/registry/bases/aria/ui/collapsible"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/registry/bases/aria/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}
defaultExpanded={item.isActive}
render={(props) => <SidebarMenuItem {...props} />}
>
<SidebarMenuButton href={item.url} isActive={item.isActive}>
{item.icon}
<span>{item.title}</span>
</SidebarMenuButton>
{item.items?.length ? (
<>
<SidebarMenuAction
slot="trigger"
className="group-data-open/menu-item:rotate-90"
>
<IconPlaceholder
lucide="ChevronRightIcon"
tabler="IconChevronRight"
hugeicons="ArrowRight01Icon"
phosphor="CaretRightIcon"
remixicon="RiArrowRightSLine"
/>
<span className="sr-only">Toggle</span>
</SidebarMenuAction>
<CollapsibleContent>
<SidebarMenuSub>
{item.items.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton 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 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/aria/components/example"
import { Card, CardContent, CardHeader } from "@/registry/bases/aria/ui/card"
import { Skeleton } from "@/registry/bases/aria/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,127 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Label } from "@/registry/bases/aria/ui/label"
import { Slider } from "@/registry/bases/aria/ui/slider"
export default function SliderExample() {
return (
<ExampleWrapper>
<SliderBasic />
<SliderRange />
<SliderMultiple />
<SliderVertical />
<SliderControlled />
<SliderDisabled />
</ExampleWrapper>
)
}
function SliderBasic() {
return (
<Example title="Basic">
<Slider
aria-label="Basic slider"
defaultValue={50}
maxValue={100}
step={1}
/>
</Example>
)
}
function SliderRange() {
return (
<Example title="Range">
<Slider
aria-label="Range slider"
defaultValue={[25, 50]}
maxValue={100}
step={5}
/>
</Example>
)
}
function SliderMultiple() {
return (
<Example title="Multiple Thumbs">
<Slider
aria-label="Multiple thumbs slider"
defaultValue={[10, 20, 70]}
maxValue={100}
step={10}
/>
</Example>
)
}
function SliderVertical() {
return (
<Example title="Vertical">
<div className="flex items-center gap-6">
<Slider
aria-label="Vertical slider"
defaultValue={[50]}
maxValue={100}
step={1}
orientation="vertical"
className="h-40"
/>
<Slider
aria-label="Vertical slider"
defaultValue={[25]}
maxValue={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
aria-label="Controlled slider"
id="slider-demo-temperature"
value={value}
onChange={setValue}
minValue={0}
maxValue={1}
step={0.1}
/>
</div>
</Example>
)
}
function SliderDisabled() {
return (
<Example title="Disabled">
<Slider
aria-label="Disabled slider"
defaultValue={[50]}
maxValue={100}
step={1}
isDisabled
/>
</Example>
)
}

View File

@@ -0,0 +1,50 @@
"use client"
import { toast } from "sonner"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
export default function SonnerExample() {
return (
<ExampleWrapper>
<SonnerBasic />
<SonnerWithDescription />
</ExampleWrapper>
)
}
function SonnerBasic() {
return (
<Example title="Basic" className="items-center justify-center">
<Button
onPress={() => 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
onPress={() =>
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,143 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Badge } from "@/registry/bases/aria/ui/badge"
import { Button, LinkButton } from "@/registry/bases/aria/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/registry/bases/aria/ui/empty"
import { Field, FieldLabel } from "@/registry/bases/aria/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/bases/aria/ui/input-group"
import { Spinner } from "@/registry/bases/aria/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 isDisabled>
<Spinner data-icon="inline-start" /> Disabled
</Button>
<Button variant="outline" isDisabled>
<Spinner data-icon="inline-start" /> Outline
</Button>
<Button variant="outline" size="icon" isDisabled>
<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">
<LinkButton href="#">Create project</LinkButton>
<Button variant="outline">Import project</Button>
</div>
<LinkButton variant="link" href="#" className="text-muted-foreground">
Learn more{" "}
<IconPlaceholder
lucide="ArrowRightIcon"
tabler="IconArrowRight"
hugeicons="ArrowRight02Icon"
phosphor="ArrowRightIcon"
remixicon="RiArrowRightLine"
/>
</LinkButton>
</EmptyContent>
</Empty>
</Example>
)
}

View File

@@ -0,0 +1,102 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldTitle,
} from "@/registry/bases/aria/ui/field"
import { Label } from "@/registry/bases/aria/ui/label"
import { Switch } from "@/registry/bases/aria/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" defaultSelected />
<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" isDisabled />
<Label htmlFor="switch-disabled-unchecked">
Disabled (Unchecked)
</Label>
</div>
<div className="flex items-center gap-2">
<Switch id="switch-disabled-checked" defaultSelected isDisabled />
<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,457 @@
"use client"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/registry/bases/aria/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">
<figure>
<Table>
<TableHeader>
<TableHead isRowHeader className="w-[100px]">
Invoice
</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</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>
<TableCaption>A list of your recent invoices.</TableCaption>
</figure>
</Example>
)
}
function TableWithFooter() {
return (
<Example title="With Footer">
<figure>
<Table>
<TableHeader>
<TableHead isRowHeader className="w-[100px]">
Invoice
</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</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>
<TableCaption>A list of your recent invoices.</TableCaption>
</figure>
</Example>
)
}
function TableSimple() {
return (
<Example title="Simple">
<Table>
<TableHeader>
<TableHead isRowHeader>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead className="text-right">Role</TableHead>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Alex Morgan</TableCell>
<TableCell>alex@example.com</TableCell>
<TableCell className="text-right">Admin</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Jordan Lee</TableCell>
<TableCell>jordan@example.com</TableCell>
<TableCell className="text-right">User</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Taylor Kim</TableCell>
<TableCell>taylor@example.com</TableCell>
<TableCell className="text-right">User</TableCell>
</TableRow>
</TableBody>
</Table>
</Example>
)
}
function TableWithBadges() {
return (
<Example title="With Badges">
<Table>
<TableHeader>
<TableHead isRowHeader>Task</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Priority</TableHead>
</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>
<TableHead isRowHeader>Product</TableHead>
<TableHead>Price</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Wireless Mouse</TableCell>
<TableCell>$29.99</TableCell>
<TableCell className="text-right">
<DropdownMenuTrigger>
<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>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Mechanical Keyboard</TableCell>
<TableCell>$129.99</TableCell>
<TableCell className="text-right">
<DropdownMenuTrigger>
<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>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">USB-C Hub</TableCell>
<TableCell>$49.99</TableCell>
<TableCell className="text-right">
<DropdownMenuTrigger>
<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>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</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>
<TableHead isRowHeader>Task</TableHead>
<TableHead>Assignee</TableHead>
<TableHead>Status</TableHead>
</TableHeader>
<TableBody>
{tasks.map((item) => (
<TableRow key={item.task}>
<TableCell className="font-medium">{item.task}</TableCell>
<TableCell>
<Select defaultValue={item.assignee}>
<SelectTrigger className="w-40" size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{people.map((person) => (
<SelectItem key={person.value} id={person.value}>
{person.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</TableCell>
<TableCell>{item.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Example>
)
}
function TableWithInput() {
return (
<Example title="With Input">
<Table>
<TableHeader>
<TableHead isRowHeader>Product</TableHead>
<TableHead>Quantity</TableHead>
<TableHead>Price</TableHead>
</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,357 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import {
DropdownMenu,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/registry/bases/aria/ui/dropdown-menu"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/bases/aria/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 defaultSelectedKey="home">
<TabsList>
<TabsTrigger id="home">Home</TabsTrigger>
<TabsTrigger id="settings">Settings</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsLine() {
return (
<Example title="Line">
<Tabs defaultSelectedKey="overview">
<TabsList variant="line">
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="analytics">Analytics</TabsTrigger>
<TabsTrigger id="reports">Reports</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsVariantsComparison() {
return (
<Example title="Variants Alignment">
<div className="flex gap-4">
<Tabs defaultSelectedKey="overview">
<TabsList>
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="analytics">Analytics</TabsTrigger>
</TabsList>
</Tabs>
<Tabs defaultSelectedKey="overview">
<TabsList variant="line">
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="analytics">Analytics</TabsTrigger>
</TabsList>
</Tabs>
</div>
</Example>
)
}
function TabsDisabled() {
return (
<Example title="Disabled">
<Tabs defaultSelectedKey="home">
<TabsList>
<TabsTrigger id="home">Home</TabsTrigger>
<TabsTrigger id="settings" isDisabled>
Disabled
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsWithIcons() {
return (
<Example title="With Icons">
<Tabs defaultSelectedKey="preview">
<TabsList>
<TabsTrigger id="preview">
<IconPlaceholder
lucide="AppWindowIcon"
tabler="IconAppWindow"
hugeicons="CursorInWindowIcon"
phosphor="AppWindowIcon"
remixicon="RiWindowLine"
/>
Preview
</TabsTrigger>
<TabsTrigger id="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 defaultSelectedKey="home">
<TabsList>
<TabsTrigger id="home">
<IconPlaceholder
lucide="HomeIcon"
tabler="IconHome"
hugeicons="HomeIcon"
phosphor="HouseIcon"
remixicon="RiHomeLine"
/>
</TabsTrigger>
<TabsTrigger id="search">
<IconPlaceholder
lucide="SearchIcon"
tabler="IconSearch"
hugeicons="SearchIcon"
phosphor="MagnifyingGlassIcon"
remixicon="RiSearchLine"
/>
</TabsTrigger>
<TabsTrigger id="settings">
<IconPlaceholder
lucide="SettingsIcon"
tabler="IconSettings"
hugeicons="SettingsIcon"
phosphor="GearIcon"
remixicon="RiSettingsLine"
/>
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsMultiple() {
return (
<Example title="Multiple">
<Tabs defaultSelectedKey="overview">
<TabsList>
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="analytics">Analytics</TabsTrigger>
<TabsTrigger id="reports">Reports</TabsTrigger>
<TabsTrigger id="settings">Settings</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsWithContent() {
return (
<Example title="With Content">
<Tabs defaultSelectedKey="account">
<TabsList>
<TabsTrigger id="account">Account</TabsTrigger>
<TabsTrigger id="password">Password</TabsTrigger>
<TabsTrigger id="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 id="account">
Manage your account preferences and profile information.
</TabsContent>
<TabsContent id="password">
Update your password to keep your account secure.
</TabsContent>
<TabsContent id="notifications">
Configure how you receive notifications and alerts.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsLineWithContent() {
return (
<Example title="Line With Content">
<Tabs defaultSelectedKey="account">
<TabsList variant="line">
<TabsTrigger id="account">Account</TabsTrigger>
<TabsTrigger id="password">Password</TabsTrigger>
<TabsTrigger id="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 id="account">
Manage your account preferences and profile information.
</TabsContent>
<TabsContent id="password">
Update your password to keep your account secure.
</TabsContent>
<TabsContent id="notifications">
Configure how you receive notifications and alerts.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsLineDisabled() {
return (
<Example title="Line Disabled">
<Tabs defaultSelectedKey="overview">
<TabsList variant="line">
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="analytics">Analytics</TabsTrigger>
<TabsTrigger id="reports" isDisabled>
Reports
</TabsTrigger>
</TabsList>
</Tabs>
</Example>
)
}
function TabsWithDropdown() {
return (
<Example title="With Dropdown">
<Tabs defaultSelectedKey="overview">
<div className="flex items-center justify-between">
<TabsList>
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="analytics">Analytics</TabsTrigger>
<TabsTrigger id="reports">Reports</TabsTrigger>
</TabsList>
<DropdownMenuTrigger>
<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>
</Button>
<DropdownMenu placement="bottom end">
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Export</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Archive</DropdownMenuItem>
</DropdownMenu>
</DropdownMenuTrigger>
</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 id="overview">
View your dashboard metrics and key performance indicators.
</TabsContent>
<TabsContent id="analytics">
Detailed analytics and insights about your data.
</TabsContent>
<TabsContent id="reports">
Generate and view custom reports.
</TabsContent>
</div>
</Tabs>
</Example>
)
}
function TabsVertical() {
return (
<Example title="Vertical">
<Tabs defaultSelectedKey="account" orientation="vertical">
<TabsList>
<TabsTrigger id="account">Account</TabsTrigger>
<TabsTrigger id="password">Password</TabsTrigger>
<TabsTrigger id="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 id="account">
Manage your account preferences and profile information.
</TabsContent>
<TabsContent id="password">
Update your password to keep your account secure. Use a strong
password with a mix of letters, numbers, and symbols.
</TabsContent>
<TabsContent id="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 defaultSelectedKey="overview" className="mx-auto w-full max-w-lg">
<div className="flex items-center gap-4">
<TabsList>
<TabsTrigger id="overview">Overview</TabsTrigger>
<TabsTrigger id="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 id="overview">
View your dashboard metrics and key performance indicators.
</TabsContent>
<TabsContent id="analytics">
Detailed analytics and insights about your data.
</TabsContent>
<TabsContent id="reports">
Generate and view custom reports.
</TabsContent>
</div>
</Tabs>
</Example>
)
}

View File

@@ -0,0 +1,86 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Textarea } from "@/registry/bases/aria/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,342 @@
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import { Button } from "@/registry/bases/aria/ui/button"
import { Toggle } from "@/registry/bases/aria/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" defaultSelected>
<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" isDisabled>
Disabled
</Toggle>
<Toggle
variant="outline"
aria-label="Toggle disabled outline"
isDisabled
>
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" defaultSelected>
<IconPlaceholder
lucide="BookmarkIcon"
tabler="IconBookmark"
hugeicons="BookmarkIcon"
phosphor="BookmarkIcon"
remixicon="RiBookmarkLine"
className="group-data-[selected]/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-[selected]/toggle:fill-accent-foreground"
/>
Bookmark
</Toggle>
</div>
</Example>
)
}

View File

@@ -0,0 +1,562 @@
"use client"
import * as React from "react"
import {
Example,
ExampleWrapper,
} from "@/registry/bases/aria/components/example"
import {
Field,
FieldDescription,
FieldLabel,
} from "@/registry/bases/aria/ui/field"
import { Input } from "@/registry/bases/aria/ui/input"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/bases/aria/ui/select"
import {
ToggleGroup,
ToggleGroupItem,
} from "@/registry/bases/aria/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 selectionMode="multiple" spacing={1}>
<ToggleGroupItem id="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem id="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem id="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" defaultSelectedKeys={["all"]}>
<ToggleGroupItem id="all" aria-label="Toggle all">
All
</ToggleGroupItem>
<ToggleGroupItem id="missed" aria-label="Toggle missed">
Missed
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupOutlineWithIcons() {
return (
<Example title="Outline With Icons">
<ToggleGroup variant="outline" selectionMode="multiple" size="sm">
<ToggleGroupItem id="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem id="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem id="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" defaultSelectedKeys={["top"]} variant="outline">
<ToggleGroupItem id="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem id="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem id="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem id="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
<ToggleGroup defaultSelectedKeys={["top"]} variant="outline">
<ToggleGroupItem id="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem id="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem id="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem id="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
</div>
</Example>
)
}
function ToggleGroupSpacing() {
return (
<Example title="With Spacing">
<ToggleGroup size="sm" defaultSelectedKeys={["top"]} spacing={2}>
<ToggleGroupItem id="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem id="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem id="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem id="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupWithIcons() {
return (
<Example title="With Icons">
<ToggleGroup
selectionMode="multiple"
variant="outline"
spacing={2}
size="sm"
>
<ToggleGroupItem
id="star"
aria-label="Toggle star"
className="data-selected:bg-transparent data-selected:*:[svg]:fill-foreground data-selected:*:[svg]:stroke-foreground"
>
<IconPlaceholder
lucide="StarIcon"
tabler="IconStar"
hugeicons="StarIcon"
phosphor="StarIcon"
remixicon="RiStarLine"
/>
Star
</ToggleGroupItem>
<ToggleGroupItem
id="heart"
aria-label="Toggle heart"
className="data-selected:bg-transparent data-selected:*:[svg]:fill-foreground data-selected:*:[svg]:stroke-foreground"
>
<IconPlaceholder
lucide="HeartIcon"
tabler="IconHeart"
hugeicons="FavouriteIcon"
phosphor="HeartIcon"
remixicon="RiHeartLine"
/>
Heart
</ToggleGroupItem>
<ToggleGroupItem
id="bookmark"
aria-label="Toggle bookmark"
className="data-selected:bg-transparent data-selected:*:[svg]:fill-foreground data-selected:*:[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 defaultSelectedKeys={["all"]} variant="outline" size="sm">
<ToggleGroupItem id="all" aria-label="All">
All
</ToggleGroupItem>
<ToggleGroupItem id="active" aria-label="Active">
Active
</ToggleGroupItem>
<ToggleGroupItem id="completed" aria-label="Completed">
Completed
</ToggleGroupItem>
<ToggleGroupItem id="archived" aria-label="Archived">
Archived
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupDateRange() {
return (
<Example title="Date Range">
<ToggleGroup
defaultSelectedKeys={["today"]}
variant="outline"
size="sm"
spacing={2}
>
<ToggleGroupItem id="today" aria-label="Today">
Today
</ToggleGroupItem>
<ToggleGroupItem id="week" aria-label="This Week">
This Week
</ToggleGroupItem>
<ToggleGroupItem id="month" aria-label="This Month">
This Month
</ToggleGroupItem>
<ToggleGroupItem id="year" aria-label="This Year">
This Year
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupSort() {
return (
<Example title="Sort">
<ToggleGroup defaultSelectedKeys={["newest"]} variant="outline" size="sm">
<ToggleGroupItem id="newest" aria-label="Newest">
<IconPlaceholder
lucide="ArrowDownIcon"
tabler="IconArrowDown"
hugeicons="ArrowDownIcon"
phosphor="ArrowDownIcon"
remixicon="RiArrowDownLine"
/>
Newest
</ToggleGroupItem>
<ToggleGroupItem id="oldest" aria-label="Oldest">
<IconPlaceholder
lucide="ArrowUpIcon"
tabler="IconArrowUp"
hugeicons="ArrowUpIcon"
phosphor="ArrowUpIcon"
remixicon="RiArrowUpLine"
/>
Oldest
</ToggleGroupItem>
<ToggleGroupItem id="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 defaultValue={items[0].value}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} id={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<ToggleGroup defaultSelectedKeys={["grid"]} variant="outline">
<ToggleGroupItem id="grid" aria-label="Grid view">
Grid
</ToggleGroupItem>
<ToggleGroupItem id="list" aria-label="List view">
List
</ToggleGroupItem>
</ToggleGroup>
</div>
</Example>
)
}
function ToggleGroupVertical() {
return (
<Example title="Vertical">
<ToggleGroup selectionMode="multiple" orientation="vertical" spacing={1}>
<ToggleGroupItem id="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem id="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem id="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"
defaultSelectedKeys={["all"]}
orientation="vertical"
size="sm"
>
<ToggleGroupItem id="all" aria-label="Toggle all">
All
</ToggleGroupItem>
<ToggleGroupItem id="active" aria-label="Toggle active">
Active
</ToggleGroupItem>
<ToggleGroupItem id="completed" aria-label="Toggle completed">
Completed
</ToggleGroupItem>
<ToggleGroupItem id="archived" aria-label="Toggle archived">
Archived
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}
function ToggleGroupVerticalOutlineWithIcons() {
return (
<Example title="Vertical Outline With Icons">
<ToggleGroup
variant="outline"
selectionMode="multiple"
orientation="vertical"
size="sm"
>
<ToggleGroupItem id="bold" aria-label="Toggle bold">
<IconPlaceholder
lucide="BoldIcon"
tabler="IconBold"
hugeicons="TextBoldIcon"
phosphor="TextBIcon"
remixicon="RiBold"
/>
</ToggleGroupItem>
<ToggleGroupItem id="italic" aria-label="Toggle italic">
<IconPlaceholder
lucide="ItalicIcon"
tabler="IconItalic"
hugeicons="TextItalicIcon"
phosphor="TextItalicIcon"
remixicon="RiItalic"
/>
</ToggleGroupItem>
<ToggleGroupItem id="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
selectedKeys={[fontWeight]}
onSelectionChange={(value) => {
setFontWeight((Array.from(value)[0] as string) ?? "normal")
}}
variant="outline"
spacing={2}
size="lg"
>
<ToggleGroupItem
id="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
id="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
id="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
id="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"
defaultSelectedKeys={["top"]}
orientation="vertical"
spacing={1}
>
<ToggleGroupItem id="top" aria-label="Toggle top">
Top
</ToggleGroupItem>
<ToggleGroupItem id="bottom" aria-label="Toggle bottom">
Bottom
</ToggleGroupItem>
<ToggleGroupItem id="left" aria-label="Toggle left">
Left
</ToggleGroupItem>
<ToggleGroupItem id="right" aria-label="Toggle right">
Right
</ToggleGroupItem>
</ToggleGroup>
</Example>
)
}

View File

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