snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
40
apps/v4/examples/base/accordion-basic.tsx
Normal file
40
apps/v4/examples/base/accordion-basic.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui/accordion"
|
||||
|
||||
const items = [
|
||||
{
|
||||
value: "item-1",
|
||||
trigger: "How do I reset my password?",
|
||||
content:
|
||||
"Click on 'Forgot Password' on the login page, enter your email address, and we'll send you a link to reset your password. The link will expire in 24 hours.",
|
||||
},
|
||||
{
|
||||
value: "item-2",
|
||||
trigger: "Can I change my subscription plan?",
|
||||
content:
|
||||
"Yes, you can upgrade or downgrade your plan at any time from your account settings. Changes will be reflected in your next billing cycle.",
|
||||
},
|
||||
{
|
||||
value: "item-3",
|
||||
trigger: "What payment methods do you accept?",
|
||||
content:
|
||||
"We accept all major credit cards, PayPal, and bank transfers. All payments are processed securely through our payment partners.",
|
||||
},
|
||||
]
|
||||
|
||||
export function AccordionBasic() {
|
||||
return (
|
||||
<Accordion defaultValue={["item-1"]} className="max-w-lg">
|
||||
{items.map((item) => (
|
||||
<AccordionItem key={item.value} value={item.value}>
|
||||
<AccordionTrigger>{item.trigger}</AccordionTrigger>
|
||||
<AccordionContent>{item.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
47
apps/v4/examples/base/accordion-borders.tsx
Normal file
47
apps/v4/examples/base/accordion-borders.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui/accordion"
|
||||
|
||||
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.",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
},
|
||||
{
|
||||
value: "integration",
|
||||
trigger: "What integrations do you support?",
|
||||
content:
|
||||
"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.",
|
||||
},
|
||||
]
|
||||
|
||||
export default function AccordionBorders() {
|
||||
return (
|
||||
<Accordion
|
||||
className="max-w-lg rounded-lg border"
|
||||
defaultValue={["billing"]}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<AccordionItem
|
||||
key={item.value}
|
||||
value={item.value}
|
||||
className="border-b px-4 last:border-b-0"
|
||||
>
|
||||
<AccordionTrigger>{item.trigger}</AccordionTrigger>
|
||||
<AccordionContent>{item.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
58
apps/v4/examples/base/accordion-card.tsx
Normal file
58
apps/v4/examples/base/accordion-card.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui/accordion"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/styles/base-nova/ui/card"
|
||||
|
||||
const items = [
|
||||
{
|
||||
value: "plans",
|
||||
trigger: "What subscription plans do you offer?",
|
||||
content:
|
||||
"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.",
|
||||
},
|
||||
{
|
||||
value: "billing",
|
||||
trigger: "How does billing work?",
|
||||
content:
|
||||
"Billing occurs automatically at the start of each billing cycle. We accept all major credit cards, PayPal, and ACH transfers for enterprise customers. You'll receive an invoice via email after each payment.",
|
||||
},
|
||||
{
|
||||
value: "cancel",
|
||||
trigger: "How do I cancel my subscription?",
|
||||
content:
|
||||
"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.",
|
||||
},
|
||||
]
|
||||
|
||||
export default function AccordionCard() {
|
||||
return (
|
||||
<Card className="w-full max-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle>Subscription & Billing</CardTitle>
|
||||
<CardDescription>
|
||||
Common questions about your account, plans, payments and
|
||||
cancellations.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Accordion defaultValue={["plans"]}>
|
||||
{items.map((item) => (
|
||||
<AccordionItem key={item.value} value={item.value}>
|
||||
<AccordionTrigger>{item.trigger}</AccordionTrigger>
|
||||
<AccordionContent>{item.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
34
apps/v4/examples/base/accordion-demo.tsx
Normal file
34
apps/v4/examples/base/accordion-demo.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui/accordion"
|
||||
|
||||
export default function AccordionDemo() {
|
||||
return (
|
||||
<Accordion defaultValue={["shipping"]} className="max-w-lg">
|
||||
<AccordionItem value="shipping">
|
||||
<AccordionTrigger>What are your shipping options?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
We offer standard (5-7 days), express (2-3 days), and overnight
|
||||
shipping. Free shipping on international orders.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="returns">
|
||||
<AccordionTrigger>What is your return policy?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Returns accepted within 30 days. Items must be unused and in original
|
||||
packaging. Refunds processed within 5-7 business days.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="support">
|
||||
<AccordionTrigger>How can I contact customer support?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Reach us via email, live chat, or phone. We respond within 24 hours
|
||||
during business days.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
36
apps/v4/examples/base/accordion-disabled.tsx
Normal file
36
apps/v4/examples/base/accordion-disabled.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui/accordion"
|
||||
|
||||
export default function AccordionDisabled() {
|
||||
return (
|
||||
<Accordion className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Can I access my account history?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes, you can view your complete account history including all
|
||||
transactions, plan changes, and support tickets in the Account History
|
||||
section of your dashboard.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2" disabled>
|
||||
<AccordionTrigger>Premium feature information</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
This section contains information about premium features. Upgrade your
|
||||
plan to access this content.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>How do I update my email address?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
You can update your email address in your account settings.
|
||||
You'll receive a verification email at your new address to
|
||||
confirm the change.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
40
apps/v4/examples/base/accordion-multiple.tsx
Normal file
40
apps/v4/examples/base/accordion-multiple.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui/accordion"
|
||||
|
||||
const items = [
|
||||
{
|
||||
value: "notifications",
|
||||
trigger: "Notification Settings",
|
||||
content:
|
||||
"Manage how you receive notifications. You can enable email alerts for updates or push notifications for mobile devices.",
|
||||
},
|
||||
{
|
||||
value: "privacy",
|
||||
trigger: "Privacy & Security",
|
||||
content:
|
||||
"Control your privacy settings and security preferences. Enable two-factor authentication, manage connected devices, review active sessions, and configure data sharing preferences. You can also download your data or delete your account.",
|
||||
},
|
||||
{
|
||||
value: "billing",
|
||||
trigger: "Billing & Subscription",
|
||||
content:
|
||||
"View your current plan, payment history, and upcoming invoices. Update your payment method, change your subscription tier, or cancel your subscription.",
|
||||
},
|
||||
]
|
||||
|
||||
export function AccordionMultiple() {
|
||||
return (
|
||||
<Accordion multiple className="max-w-lg" defaultValue={["notifications"]}>
|
||||
{items.map((item) => (
|
||||
<AccordionItem key={item.value} value={item.value}>
|
||||
<AccordionTrigger>{item.trigger}</AccordionTrigger>
|
||||
<AccordionContent>{item.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
91
apps/v4/examples/base/accordion-rtl.tsx
Normal file
91
apps/v4/examples/base/accordion-rtl.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/styles/base-nova/ui-rtl/accordion"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
question1: "How do I reset my password?",
|
||||
answer1:
|
||||
"Click on 'Forgot Password' on the login page, enter your email address, and we'll send you a link to reset your password.",
|
||||
question2: "Can I change my subscription plan?",
|
||||
answer2:
|
||||
"Yes, you can upgrade or downgrade your plan at any time from your account settings. Changes will be reflected in your next billing cycle.",
|
||||
question3: "What payment methods do you accept?",
|
||||
answer3:
|
||||
"We accept all major credit cards, PayPal, and bank transfers. All payments are processed securely through our payment partners.",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
question1: "كيف يمكنني إعادة تعيين كلمة المرور؟",
|
||||
answer1:
|
||||
"انقر على 'نسيت كلمة المرور' في صفحة تسجيل الدخول، أدخل عنوان بريدك الإلكتروني، وسنرسل لك رابطًا لإعادة تعيين كلمة المرور. سينتهي صلاحية الرابط خلال 24 ساعة.",
|
||||
question2: "هل يمكنني تغيير خطة الاشتراك الخاصة بي؟",
|
||||
answer2:
|
||||
"نعم، يمكنك ترقية أو تخفيض خطتك في أي وقت من إعدادات حسابك. ستظهر التغييرات في دورة الفوترة التالية.",
|
||||
question3: "ما هي طرق الدفع التي تقبلونها؟",
|
||||
answer3:
|
||||
"نقبل جميع بطاقات الائتمان الرئيسية و PayPal والتحويلات المصرفية. تتم معالجة جميع المدفوعات بأمان من خلال شركاء الدفع لدينا.",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
question1: "איך אני מאפס את הסיסמה שלי?",
|
||||
answer1:
|
||||
"לחץ על 'שכחתי סיסמה' בעמוד ההתחברות, הזן את כתובת האימייל שלך, ונשלח לך קישור לאיפוס הסיסמה. הקישור יפוג תוך 24 שעות.",
|
||||
question2: "האם אני יכול לשנות את תוכנית המנוי שלי?",
|
||||
answer2:
|
||||
"כן, אתה יכול לשדרג או להוריד את התוכנית שלך בכל עת מההגדרות של החשבון שלך. השינויים יבואו לידי ביטוי במחזור החיוב הבא.",
|
||||
question3: "אילו אמצעי תשלום אתם מקבלים?",
|
||||
answer3: "אנו מקבלים כרטיסי אשראי, PayPal והעברות בנקאיות.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const items = [
|
||||
{
|
||||
value: "item-1",
|
||||
questionKey: "question1" as const,
|
||||
answerKey: "answer1" as const,
|
||||
},
|
||||
{
|
||||
value: "item-2",
|
||||
questionKey: "question2" as const,
|
||||
answerKey: "answer2" as const,
|
||||
},
|
||||
{
|
||||
value: "item-3",
|
||||
questionKey: "question3" as const,
|
||||
answerKey: "answer3" as const,
|
||||
},
|
||||
] as const
|
||||
|
||||
export function AccordionRtl() {
|
||||
const { t } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<Accordion defaultValue={["item-1"]} className="max-w-md">
|
||||
{items.map((item) => (
|
||||
<AccordionItem key={item.value} value={item.value}>
|
||||
<AccordionTrigger>{t[item.questionKey]}</AccordionTrigger>
|
||||
<AccordionContent>{t[item.answerKey]}</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
246
apps/v4/examples/base/ai-sdk-helper-demo.tsx
Normal file
246
apps/v4/examples/base/ai-sdk-helper-demo.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
"use client"
|
||||
|
||||
import { useChat } from "@ai-sdk/react"
|
||||
import { createChat } from "@shadcn/helpers/ai-sdk"
|
||||
import {
|
||||
ArrowUpIcon,
|
||||
GlobeIcon,
|
||||
ImageIcon,
|
||||
MessageCircleDashedIcon,
|
||||
PaperclipIcon,
|
||||
PlusIcon,
|
||||
RotateCwIcon,
|
||||
TelescopeIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { getMessageText } from "@/lib/ai"
|
||||
import { MessageAnimated } from "@/components/message-animated"
|
||||
import { Button } from "@/styles/base-rhea/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/styles/base-rhea/ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-rhea/ui/dropdown-menu"
|
||||
import {
|
||||
Empty,
|
||||
EmptyDescription,
|
||||
EmptyHeader,
|
||||
EmptyMedia,
|
||||
EmptyTitle,
|
||||
} from "@/styles/base-rhea/ui/empty"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupTextarea,
|
||||
} from "@/styles/base-rhea/ui/input-group"
|
||||
import {
|
||||
MessageScroller,
|
||||
MessageScrollerButton,
|
||||
MessageScrollerContent,
|
||||
MessageScrollerProvider,
|
||||
MessageScrollerViewport,
|
||||
} from "@/styles/base-rhea/ui/message-scroller"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/styles/base-rhea/ui/tooltip"
|
||||
|
||||
const chat = createChat()
|
||||
.user(
|
||||
"I'm building a chat for our app and the scroll behavior is driving me nuts. Every time the AI streams a reply, the whole thread jumps around."
|
||||
)
|
||||
.sleep(1000)
|
||||
.assistant(({ writer }) => {
|
||||
writer.reasoning(
|
||||
"They are describing a streaming transcript that keeps taking control of the viewport. I should explain when auto-scroll follows and when it stops."
|
||||
)
|
||||
writer.sleep(1000)
|
||||
writer.text(
|
||||
"That's the classic streaming scroll problem. Wrap your message list in `MessageScroller` and turn on `autoScroll` — the viewport pins to the bottom as tokens arrive, so users always see the latest text land in place.\n\nThe important part: it only auto-scrolls while the reader is already at the bottom. The moment they scroll up to read something earlier, auto-scroll backs off and their position is preserved. You get smooth streaming without fighting the user's intent."
|
||||
)
|
||||
})
|
||||
.user(
|
||||
"Okay, but when someone sends a new message the view still feels jarring — like the whole conversation reloads from the top."
|
||||
)
|
||||
.sleep(1000)
|
||||
.assistant(
|
||||
"MessageScrollerItem fixes that with turn anchoring. Set `scrollAnchor` on the turn that should settle near the top instead of blindly snapping to the document bottom.\n\nIt also leaves a small peek of the previous exchange visible above the anchor, so context isn't lost. The reply starts in view without that disorienting jump you get from a plain overflow container."
|
||||
)
|
||||
.user(
|
||||
"And if they've scrolled up to re-read an older answer? I don't want to yank them back down."
|
||||
)
|
||||
.sleep(1000)
|
||||
.assistant(
|
||||
"You won't. Auto-scroll only runs when the viewport is already pinned to the bottom, so scrolling up is a deliberate opt-out — their place in the thread stays put even as new tokens keep arriving below.\n\nWhen there is content they haven't seen yet, `MessageScrollerButton` appears at the bottom of the viewport. One tap jumps them back to the newest message and re-engages auto-scroll. Same pattern as Slack or iMessage: quiet when you're caught up, helpful when you're not."
|
||||
)
|
||||
.user("Last one — does this work with assistive tech?")
|
||||
.sleep(1000)
|
||||
.assistant(
|
||||
'`MessageScrollerContent` sets `role="log"` and `aria-relevant="additions"` by default, so screen readers announce new messages as they stream in.\n\nThe scroll button is a real `<button>` with an sr-only label, and it\'s removed from the tab order when you\'re already at the bottom — no ghost focus stops.'
|
||||
)
|
||||
const initialMessages = chat.get(0)
|
||||
const transport = chat.transport({ delayMs: 20 })
|
||||
|
||||
export function AiSdkHelperDemo() {
|
||||
const { messages, sendMessage, status, setMessages } = useChat({
|
||||
messages: initialMessages,
|
||||
transport,
|
||||
})
|
||||
const nextMessage = chat.next(messages)
|
||||
const isBusy = status === "submitted" || status === "streaming"
|
||||
|
||||
return (
|
||||
<MessageScrollerProvider>
|
||||
<div className="relative flex flex-col gap-4">
|
||||
<Card className="mx-auto h-140 w-full max-w-sm gap-0">
|
||||
<CardHeader className="gap-1 border-b">
|
||||
<CardTitle>New Chat</CardTitle>
|
||||
<CardDescription>How can I help you today?</CardDescription>
|
||||
<CardAction>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Reset conversation"
|
||||
onClick={() => setMessages(initialMessages)}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RotateCwIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Reset</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 overflow-hidden p-0">
|
||||
{messages.length === 0 ? (
|
||||
<Empty className="h-full">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<MessageCircleDashedIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Morning, shadcn!</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
What are we working on today? Press send to start a new
|
||||
conversation
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
) : (
|
||||
<MessageScroller>
|
||||
<MessageScrollerViewport>
|
||||
<MessageScrollerContent
|
||||
aria-busy={isBusy}
|
||||
className="p-(--card-spacing)"
|
||||
>
|
||||
{messages.map((message) => (
|
||||
<MessageAnimated
|
||||
key={message.id}
|
||||
message={message}
|
||||
scrollAnchor={message.role === "user"}
|
||||
/>
|
||||
))}
|
||||
</MessageScrollerContent>
|
||||
</MessageScrollerViewport>
|
||||
<MessageScrollerButton />
|
||||
</MessageScroller>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2">
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
if (!nextMessage || isBusy) {
|
||||
return
|
||||
}
|
||||
void sendMessage(nextMessage)
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea
|
||||
aria-label="Next predefined message"
|
||||
className="h-14 min-h-14 overflow-hidden px-3 py-2.5 opacity-60 data-[status=ready]:opacity-100"
|
||||
data-status={status}
|
||||
placeholder="No messages queued. Reset the conversation."
|
||||
value={nextMessage ? getMessageText(nextMessage) : ""}
|
||||
readOnly
|
||||
/>
|
||||
<InputGroupAddon align="block-end" className="pt-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
aria-label="Add files"
|
||||
type="button"
|
||||
size="icon-sm"
|
||||
variant="outline"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<PlusIcon />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
side="top"
|
||||
className="w-44"
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
<PaperclipIcon />
|
||||
Add Photos & Files
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<ImageIcon />
|
||||
Create Image
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<TelescopeIcon />
|
||||
Deep Research
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<GlobeIcon />
|
||||
Web Search
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<InputGroupButton
|
||||
type="submit"
|
||||
variant="default"
|
||||
size="icon-sm"
|
||||
disabled={!nextMessage || isBusy}
|
||||
className="ml-auto"
|
||||
>
|
||||
<ArrowUpIcon />
|
||||
<span className="sr-only">Send</span>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</form>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<div className="px-0.5 text-center text-xs text-muted-foreground">
|
||||
Demo is read only. Press send to send messages.
|
||||
</div>
|
||||
</div>
|
||||
</MessageScrollerProvider>
|
||||
)
|
||||
}
|
||||
23
apps/v4/examples/base/alert-action.tsx
Normal file
23
apps/v4/examples/base/alert-action.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertAction,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/styles/base-nova/ui/alert"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function AlertActionExample() {
|
||||
return (
|
||||
<Alert className="max-w-md">
|
||||
<AlertTitle>Dark mode is now available</AlertTitle>
|
||||
<AlertDescription>
|
||||
Enable it under your profile settings to get started.
|
||||
</AlertDescription>
|
||||
<AlertAction>
|
||||
<Button size="xs" variant="default">
|
||||
Enable
|
||||
</Button>
|
||||
</AlertAction>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
20
apps/v4/examples/base/alert-basic.tsx
Normal file
20
apps/v4/examples/base/alert-basic.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { CheckCircle2Icon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/styles/base-nova/ui/alert"
|
||||
|
||||
export default function AlertBasic() {
|
||||
return (
|
||||
<Alert className="max-w-md">
|
||||
<CheckCircle2Icon />
|
||||
<AlertTitle>Account updated successfully</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your profile information has been saved. Changes will be reflected
|
||||
immediately.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
20
apps/v4/examples/base/alert-colors.tsx
Normal file
20
apps/v4/examples/base/alert-colors.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { AlertTriangleIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/styles/base-nova/ui/alert"
|
||||
|
||||
export default function AlertColors() {
|
||||
return (
|
||||
<Alert className="max-w-md border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-50">
|
||||
<AlertTriangleIcon />
|
||||
<AlertTitle>Your subscription will expire in 3 days.</AlertTitle>
|
||||
<AlertDescription>
|
||||
Renew now to avoid service interruption or upgrade to a paid plan to
|
||||
continue using the service.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
30
apps/v4/examples/base/alert-demo.tsx
Normal file
30
apps/v4/examples/base/alert-demo.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { CheckCircle2Icon, InfoIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/styles/base-nova/ui/alert"
|
||||
|
||||
export default function AlertDemo() {
|
||||
return (
|
||||
<div className="grid w-full max-w-md items-start gap-4">
|
||||
<Alert>
|
||||
<CheckCircle2Icon />
|
||||
<AlertTitle>Payment successful</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your payment of $29.99 has been processed. A receipt has been sent to
|
||||
your email address.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<InfoIcon />
|
||||
<AlertTitle>New feature available</AlertTitle>
|
||||
<AlertDescription>
|
||||
We've added dark mode support. You can enable it in your account
|
||||
settings.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
20
apps/v4/examples/base/alert-destructive.tsx
Normal file
20
apps/v4/examples/base/alert-destructive.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { AlertCircleIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/styles/base-nova/ui/alert"
|
||||
|
||||
export default function AlertDestructive() {
|
||||
return (
|
||||
<Alert variant="destructive" className="max-w-md">
|
||||
<AlertCircleIcon />
|
||||
<AlertTitle>Payment failed</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your payment could not be processed. Please check your payment method
|
||||
and try again.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
35
apps/v4/examples/base/alert-dialog-basic.tsx
Normal file
35
apps/v4/examples/base/alert-dialog-basic.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export function AlertDialogBasic() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger
|
||||
render={<Button variant="outline">Show Dialog</Button>}
|
||||
/>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
35
apps/v4/examples/base/alert-dialog-demo.tsx
Normal file
35
apps/v4/examples/base/alert-dialog-demo.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function AlertDialogDemo() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger render={<Button variant="outline" />}>
|
||||
Show Dialog
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
account from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
41
apps/v4/examples/base/alert-dialog-destructive.tsx
Normal file
41
apps/v4/examples/base/alert-dialog-destructive.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Trash2Icon } from "lucide-react"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogMedia,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export function AlertDialogDestructive() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger
|
||||
render={<Button variant="destructive">Delete Chat</Button>}
|
||||
/>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
|
||||
<Trash2Icon />
|
||||
</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="outline">Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction variant="destructive">Delete</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
40
apps/v4/examples/base/alert-dialog-media.tsx
Normal file
40
apps/v4/examples/base/alert-dialog-media.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { CircleFadingPlusIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogMedia,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export function AlertDialogWithMedia() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger
|
||||
render={<Button variant="outline">Share Project</Button>}
|
||||
/>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogMedia>
|
||||
<CircleFadingPlusIcon />
|
||||
</AlertDialogMedia>
|
||||
<AlertDialogTitle>Share this project?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Anyone with the link will be able to view and edit this project.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Share</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
124
apps/v4/examples/base/alert-dialog-rtl.tsx
Normal file
124
apps/v4/examples/base/alert-dialog-rtl.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
"use client"
|
||||
|
||||
import { BluetoothIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogMedia,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui-rtl/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui-rtl/button"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
showDialog: "Show Dialog",
|
||||
showDialogSm: "Show Dialog (sm)",
|
||||
title: "Are you absolutely sure?",
|
||||
description:
|
||||
"This action cannot be undone. This will permanently delete your account from our servers.",
|
||||
cancel: "Cancel",
|
||||
continue: "Continue",
|
||||
smallTitle: "Allow accessory to connect?",
|
||||
smallDescription:
|
||||
"Do you want to allow the USB accessory to connect to this device?",
|
||||
dontAllow: "Don't allow",
|
||||
allow: "Allow",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
showDialog: "إظهار الحوار",
|
||||
showDialogSm: "إظهار الحوار (صغير)",
|
||||
title: "هل أنت متأكد تمامًا؟",
|
||||
description:
|
||||
"لا يمكن التراجع عن هذا الإجراء. سيؤدي هذا إلى حذف حسابك نهائيًا من خوادمنا.",
|
||||
cancel: "إلغاء",
|
||||
continue: "متابعة",
|
||||
smallTitle: "السماح للملحق بالاتصال؟",
|
||||
smallDescription: "هل تريد السماح لملحق USB بالاتصال بهذا الجهاز؟",
|
||||
dontAllow: "عدم السماح",
|
||||
allow: "السماح",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
showDialog: "הצג דיאלוג",
|
||||
showDialogSm: "הצג דיאלוג (קטן)",
|
||||
title: "האם אתה בטוח לחלוטין?",
|
||||
description:
|
||||
"פעולה זו לא ניתנת לביטול. זה ימחק לצמיתות את החשבון שלך מהשרתים שלנו.",
|
||||
cancel: "ביטול",
|
||||
continue: "המשך",
|
||||
smallTitle: "לאפשר להתקן להתחבר?",
|
||||
smallDescription: "האם אתה רוצה לאפשר להתקן USB להתחבר למכשיר זה?",
|
||||
dontAllow: "אל תאפשר",
|
||||
allow: "אפשר",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function AlertDialogRtl() {
|
||||
const { dir, t, language } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<div className="flex gap-4" dir={dir}>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger render={<Button variant="outline" />}>
|
||||
{t.showDialog}
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent
|
||||
dir={dir}
|
||||
data-lang={dir === "rtl" ? language : undefined}
|
||||
>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t.title}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{t.description}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t.cancel}</AlertDialogCancel>
|
||||
<AlertDialogAction>{t.continue}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger render={<Button variant="outline" />}>
|
||||
{t.showDialogSm}
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent
|
||||
size="sm"
|
||||
dir={dir}
|
||||
data-lang={dir === "rtl" ? language : undefined}
|
||||
>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogMedia>
|
||||
<BluetoothIcon />
|
||||
</AlertDialogMedia>
|
||||
<AlertDialogTitle>{t.smallTitle}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t.smallDescription}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t.dontAllow}</AlertDialogCancel>
|
||||
<AlertDialogAction>{t.allow}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
41
apps/v4/examples/base/alert-dialog-small-media.tsx
Normal file
41
apps/v4/examples/base/alert-dialog-small-media.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { BluetoothIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogMedia,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export function AlertDialogSmallWithMedia() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger
|
||||
render={<Button variant="outline">Show Dialog</Button>}
|
||||
/>
|
||||
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogMedia>
|
||||
<BluetoothIcon />
|
||||
</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't allow</AlertDialogCancel>
|
||||
<AlertDialogAction>Allow</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
34
apps/v4/examples/base/alert-dialog-small.tsx
Normal file
34
apps/v4/examples/base/alert-dialog-small.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/styles/base-nova/ui/alert-dialog"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export function AlertDialogSmall() {
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger
|
||||
render={<Button variant="outline">Show Dialog</Button>}
|
||||
/>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Do you want to allow the USB accessory to connect to this device?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Don't allow</AlertDialogCancel>
|
||||
<AlertDialogAction>Allow</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
}
|
||||
82
apps/v4/examples/base/alert-rtl.tsx
Normal file
82
apps/v4/examples/base/alert-rtl.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { CheckCircle2Icon, InfoIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
} from "@/styles/base-nova/ui-rtl/alert"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
paymentTitle: "Payment successful",
|
||||
paymentDescription:
|
||||
"Your payment of $29.99 has been processed. A receipt has been sent to your email address.",
|
||||
featureTitle: "New feature available",
|
||||
featureDescription:
|
||||
"We've added dark mode support. You can enable it in your account settings.",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
paymentTitle: "تم الدفع بنجاح",
|
||||
paymentDescription:
|
||||
"تمت معالجة دفعتك البالغة 29.99 دولارًا. تم إرسال إيصال إلى عنوان بريدك الإلكتروني.",
|
||||
featureTitle: "ميزة جديدة متاحة",
|
||||
featureDescription:
|
||||
"لقد أضفنا دعم الوضع الداكن. يمكنك تفعيله في إعدادات حسابك.",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
paymentTitle: "התשלום בוצע בהצלחה",
|
||||
paymentDescription:
|
||||
"התשלום שלך בסך 29.99 דולר עובד. קבלה נשלחה לכתובת האימייל שלך.",
|
||||
featureTitle: "תכונה חדשה זמינה",
|
||||
featureDescription:
|
||||
"הוספנו תמיכה במצב כהה. אתה יכול להפעיל אותו בהגדרות החשבון שלך.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const alerts = [
|
||||
{
|
||||
icon: CheckCircle2Icon,
|
||||
titleKey: "paymentTitle" as const,
|
||||
descriptionKey: "paymentDescription" as const,
|
||||
},
|
||||
{
|
||||
icon: InfoIcon,
|
||||
titleKey: "featureTitle" as const,
|
||||
descriptionKey: "featureDescription" as const,
|
||||
},
|
||||
] as const
|
||||
|
||||
export function AlertRtl() {
|
||||
const { dir, t } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<div className="grid w-full max-w-md items-start gap-4" dir={dir}>
|
||||
{alerts.map((alert, index) => {
|
||||
const Icon = alert.icon
|
||||
return (
|
||||
<Alert key={index}>
|
||||
<Icon />
|
||||
<AlertTitle>{t[alert.titleKey]}</AlertTitle>
|
||||
<AlertDescription>{t[alert.descriptionKey]}</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
16
apps/v4/examples/base/aspect-ratio-demo.tsx
Normal file
16
apps/v4/examples/base/aspect-ratio-demo.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { AspectRatio } from "@/styles/base-nova/ui/aspect-ratio"
|
||||
|
||||
export default function AspectRatioDemo() {
|
||||
return (
|
||||
<AspectRatio ratio={16 / 9} className="w-full max-w-sm rounded-lg bg-muted">
|
||||
<Image
|
||||
src="https://avatar.vercel.sh/shadcn1"
|
||||
alt="Photo"
|
||||
fill
|
||||
className="rounded-lg object-cover grayscale dark:brightness-20"
|
||||
/>
|
||||
</AspectRatio>
|
||||
)
|
||||
}
|
||||
19
apps/v4/examples/base/aspect-ratio-portrait.tsx
Normal file
19
apps/v4/examples/base/aspect-ratio-portrait.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { AspectRatio } from "@/styles/base-nova/ui/aspect-ratio"
|
||||
|
||||
export function AspectRatioPortrait() {
|
||||
return (
|
||||
<AspectRatio
|
||||
ratio={9 / 16}
|
||||
className="w-full max-w-[10rem] rounded-lg bg-muted"
|
||||
>
|
||||
<Image
|
||||
src="https://avatar.vercel.sh/shadcn1"
|
||||
alt="Photo"
|
||||
fill
|
||||
className="rounded-lg object-cover grayscale dark:brightness-20"
|
||||
/>
|
||||
</AspectRatio>
|
||||
)
|
||||
}
|
||||
51
apps/v4/examples/base/aspect-ratio-rtl.tsx
Normal file
51
apps/v4/examples/base/aspect-ratio-rtl.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import { AspectRatio } from "@/styles/base-nova/ui-rtl/aspect-ratio"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
caption: "Beautiful landscape",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
caption: "منظر طبيعي جميل",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
caption: "נוף יפה",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function AspectRatioRtl() {
|
||||
const { dir, t } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<figure className="w-full max-w-sm" dir={dir}>
|
||||
<AspectRatio ratio={16 / 9} className="rounded-lg bg-muted">
|
||||
<Image
|
||||
src="https://avatar.vercel.sh/shadcn1"
|
||||
alt="Photo"
|
||||
fill
|
||||
className="rounded-lg object-cover grayscale dark:brightness-20"
|
||||
/>
|
||||
</AspectRatio>
|
||||
<figcaption className="mt-2 text-center text-sm text-muted-foreground">
|
||||
{t.caption}
|
||||
</figcaption>
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
19
apps/v4/examples/base/aspect-ratio-square.tsx
Normal file
19
apps/v4/examples/base/aspect-ratio-square.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import Image from "next/image"
|
||||
|
||||
import { AspectRatio } from "@/styles/base-nova/ui/aspect-ratio"
|
||||
|
||||
export function AspectRatioSquare() {
|
||||
return (
|
||||
<AspectRatio
|
||||
ratio={1 / 1}
|
||||
className="w-full max-w-[12rem] rounded-lg bg-muted"
|
||||
>
|
||||
<Image
|
||||
src="https://avatar.vercel.sh/shadcn1"
|
||||
alt="Photo"
|
||||
fill
|
||||
className="rounded-lg object-cover grayscale dark:brightness-20"
|
||||
/>
|
||||
</AspectRatio>
|
||||
)
|
||||
}
|
||||
82
apps/v4/examples/base/attachment-demo.tsx
Normal file
82
apps/v4/examples/base/attachment-demo.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { FileCodeIcon, XIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentAction,
|
||||
AttachmentActions,
|
||||
AttachmentContent,
|
||||
AttachmentDescription,
|
||||
AttachmentGroup,
|
||||
AttachmentMedia,
|
||||
AttachmentTitle,
|
||||
} from "@/styles/base-rhea/ui/attachment"
|
||||
import { Spinner } from "@/styles/base-rhea/ui/spinner"
|
||||
|
||||
const images = [
|
||||
{
|
||||
name: "workspace.png",
|
||||
meta: "PNG · 820 KB",
|
||||
src: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80",
|
||||
alt: "Workspace",
|
||||
},
|
||||
{
|
||||
name: "desk-reference.jpg",
|
||||
meta: "JPG · 1.1 MB",
|
||||
src: "https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=900&auto=format&fit=crop&q=80",
|
||||
alt: "Desk",
|
||||
},
|
||||
{
|
||||
name: "office-reference.jpg",
|
||||
meta: "JPG · 940 KB",
|
||||
src: "https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=900&auto=format&fit=crop&q=80",
|
||||
alt: "Office",
|
||||
},
|
||||
]
|
||||
|
||||
export function AttachmentDemo() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-sm flex-col gap-3 py-12">
|
||||
<AttachmentGroup>
|
||||
{images.map((image) => (
|
||||
<Attachment key={image.name} orientation="vertical">
|
||||
<AttachmentMedia variant="image">
|
||||
<img src={image.src} alt={image.alt} />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>{image.name}</AttachmentTitle>
|
||||
<AttachmentDescription>{image.meta}</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
</Attachment>
|
||||
))}
|
||||
</AttachmentGroup>
|
||||
<Attachment state="uploading" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<Spinner />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>sales-dashboard.pdf</AttachmentTitle>
|
||||
<AttachmentDescription>Uploading · 64%</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Cancel upload">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
<Attachment className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileCodeIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>message-renderer.tsx</AttachmentTitle>
|
||||
<AttachmentDescription>TypeScript · 12 KB</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Remove message-renderer.tsx">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
71
apps/v4/examples/base/attachment-group.tsx
Normal file
71
apps/v4/examples/base/attachment-group.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
FileCodeIcon,
|
||||
FileTextIcon,
|
||||
TableIcon,
|
||||
XIcon,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentAction,
|
||||
AttachmentActions,
|
||||
AttachmentContent,
|
||||
AttachmentDescription,
|
||||
AttachmentGroup,
|
||||
AttachmentMedia,
|
||||
AttachmentTitle,
|
||||
} from "@/styles/base-rhea/ui/attachment"
|
||||
|
||||
type Item = {
|
||||
name: string
|
||||
meta: string
|
||||
icon?: LucideIcon
|
||||
src?: string
|
||||
}
|
||||
|
||||
const items: Item[] = [
|
||||
{ name: "briefing-notes.pdf", meta: "PDF · 1.4 MB", icon: FileTextIcon },
|
||||
{
|
||||
name: "workspace.png",
|
||||
meta: "PNG · 820 KB",
|
||||
src: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80",
|
||||
},
|
||||
{ name: "customers.csv", meta: "CSV · 18 KB", icon: TableIcon },
|
||||
{ name: "renderer.tsx", meta: "TSX · 12 KB", icon: FileCodeIcon },
|
||||
]
|
||||
|
||||
export function AttachmentGroupDemo() {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-sm py-12">
|
||||
<AttachmentGroup className="w-full">
|
||||
{items.map((item) => {
|
||||
const Icon = item.icon
|
||||
|
||||
return (
|
||||
<Attachment key={item.name} className="w-64">
|
||||
{item.src ? (
|
||||
<AttachmentMedia variant="image">
|
||||
<img src={item.src} alt={item.name} />
|
||||
</AttachmentMedia>
|
||||
) : Icon ? (
|
||||
<AttachmentMedia>
|
||||
<Icon />
|
||||
</AttachmentMedia>
|
||||
) : null}
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>{item.name}</AttachmentTitle>
|
||||
<AttachmentDescription>{item.meta}</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label={`Remove ${item.name}`}>
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
)
|
||||
})}
|
||||
</AttachmentGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
69
apps/v4/examples/base/attachment-image.tsx
Normal file
69
apps/v4/examples/base/attachment-image.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentAction,
|
||||
AttachmentActions,
|
||||
AttachmentContent,
|
||||
AttachmentDescription,
|
||||
AttachmentGroup,
|
||||
AttachmentMedia,
|
||||
AttachmentTitle,
|
||||
AttachmentTrigger,
|
||||
} from "@/styles/base-rhea/ui/attachment"
|
||||
|
||||
const images = [
|
||||
{
|
||||
name: "workspace.png",
|
||||
meta: "PNG · 820 KB",
|
||||
src: "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=900&auto=format&fit=crop&q=80",
|
||||
alt: "Workspace",
|
||||
},
|
||||
{
|
||||
name: "desk-reference.jpg",
|
||||
meta: "JPG · 1.1 MB",
|
||||
src: "https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=900&auto=format&fit=crop&q=80",
|
||||
alt: "Desk",
|
||||
},
|
||||
{
|
||||
name: "office-reference.jpg",
|
||||
meta: "JPG · 940 KB",
|
||||
src: "https://images.unsplash.com/photo-1497366811353-6870744d04b2?w=900&auto=format&fit=crop&q=80",
|
||||
alt: "Office",
|
||||
},
|
||||
]
|
||||
|
||||
export function AttachmentImage() {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-sm py-12">
|
||||
<AttachmentGroup className="w-full">
|
||||
{images.map((image) => (
|
||||
<Attachment key={image.name} orientation="vertical">
|
||||
<AttachmentMedia variant="image">
|
||||
<img src={image.src} alt={image.alt} />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>{image.name}</AttachmentTitle>
|
||||
<AttachmentDescription>{image.meta}</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label={`Remove ${image.name}`}>
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
<AttachmentTrigger
|
||||
render={
|
||||
<a
|
||||
href={image.src}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={`Open ${image.name}`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Attachment>
|
||||
))}
|
||||
</AttachmentGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
42
apps/v4/examples/base/attachment-sizes.tsx
Normal file
42
apps/v4/examples/base/attachment-sizes.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { FileTextIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentContent,
|
||||
AttachmentDescription,
|
||||
AttachmentMedia,
|
||||
AttachmentTitle,
|
||||
} from "@/styles/base-rhea/ui/attachment"
|
||||
|
||||
export function AttachmentSizes() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-sm flex-col gap-3 py-12">
|
||||
<Attachment size="default" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileTextIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>Default attachment</AttachmentTitle>
|
||||
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
</Attachment>
|
||||
<Attachment size="sm" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileTextIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>Small attachment</AttachmentTitle>
|
||||
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
</Attachment>
|
||||
<Attachment size="xs" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileTextIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>Extra small attachment</AttachmentTitle>
|
||||
</AttachmentContent>
|
||||
</Attachment>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
101
apps/v4/examples/base/attachment-states.tsx
Normal file
101
apps/v4/examples/base/attachment-states.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
CheckIcon,
|
||||
ClockIcon,
|
||||
FileTextIcon,
|
||||
FileWarningIcon,
|
||||
RefreshCwIcon,
|
||||
XIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentAction,
|
||||
AttachmentActions,
|
||||
AttachmentContent,
|
||||
AttachmentDescription,
|
||||
AttachmentMedia,
|
||||
AttachmentTitle,
|
||||
} from "@/styles/base-rhea/ui/attachment"
|
||||
import { Spinner } from "@/styles/base-rhea/ui/spinner"
|
||||
|
||||
export function AttachmentStates() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-sm flex-col gap-2 py-12">
|
||||
<Attachment state="idle" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<ClockIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>selected-file.pdf</AttachmentTitle>
|
||||
<AttachmentDescription>Ready to upload</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Remove selected-file.pdf">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
<Attachment state="uploading" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<Spinner />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>design-system.zip</AttachmentTitle>
|
||||
<AttachmentDescription>Uploading · 64%</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Cancel upload">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
<Attachment state="processing" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileTextIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>market-research.pdf</AttachmentTitle>
|
||||
<AttachmentDescription>Processing document</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Remove market-research.pdf">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
<Attachment state="error" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileWarningIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>financial-model.xlsx</AttachmentTitle>
|
||||
<AttachmentDescription>
|
||||
Upload failed. Try again.
|
||||
</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Retry upload">
|
||||
<RefreshCwIcon />
|
||||
</AttachmentAction>
|
||||
<AttachmentAction aria-label="Remove financial-model.xlsx">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
<Attachment state="done" className="w-full">
|
||||
<AttachmentMedia>
|
||||
<CheckIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>uploaded-report.pdf</AttachmentTitle>
|
||||
<AttachmentDescription>Uploaded · 1.8 MB</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Remove uploaded-report.pdf">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
</Attachment>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
60
apps/v4/examples/base/attachment-trigger.tsx
Normal file
60
apps/v4/examples/base/attachment-trigger.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { CopyIcon, FileSearchIcon, XIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Attachment,
|
||||
AttachmentAction,
|
||||
AttachmentActions,
|
||||
AttachmentContent,
|
||||
AttachmentDescription,
|
||||
AttachmentMedia,
|
||||
AttachmentTitle,
|
||||
AttachmentTrigger,
|
||||
} from "@/styles/base-rhea/ui/attachment"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/styles/base-rhea/ui/dialog"
|
||||
|
||||
export function AttachmentTriggerDemo() {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-sm py-12">
|
||||
<Dialog>
|
||||
<Attachment className="w-full">
|
||||
<AttachmentMedia>
|
||||
<FileSearchIcon />
|
||||
</AttachmentMedia>
|
||||
<AttachmentContent>
|
||||
<AttachmentTitle>research-summary.pdf</AttachmentTitle>
|
||||
<AttachmentDescription>Open preview dialog</AttachmentDescription>
|
||||
</AttachmentContent>
|
||||
<AttachmentActions>
|
||||
<AttachmentAction aria-label="Copy link">
|
||||
<CopyIcon />
|
||||
</AttachmentAction>
|
||||
<AttachmentAction aria-label="Remove research-summary.pdf">
|
||||
<XIcon />
|
||||
</AttachmentAction>
|
||||
</AttachmentActions>
|
||||
<DialogTrigger
|
||||
render={
|
||||
<AttachmentTrigger aria-label="Preview research-summary.pdf" />
|
||||
}
|
||||
/>
|
||||
</Attachment>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>research-summary.pdf</DialogTitle>
|
||||
<DialogDescription>
|
||||
The attachment trigger fills the card and opens the dialog, while
|
||||
the actions stay independently clickable above it.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
20
apps/v4/examples/base/avatar-badge-icon.tsx
Normal file
20
apps/v4/examples/base/avatar-badge-icon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export function AvatarBadgeIconExample() {
|
||||
return (
|
||||
<Avatar className="grayscale">
|
||||
<AvatarImage src="https://github.com/pranathip.png" alt="@pranathip" />
|
||||
<AvatarFallback>PP</AvatarFallback>
|
||||
<AvatarBadge>
|
||||
<PlusIcon />
|
||||
</AvatarBadge>
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
16
apps/v4/examples/base/avatar-badge.tsx
Normal file
16
apps/v4/examples/base/avatar-badge.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export function AvatarWithBadge() {
|
||||
return (
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
<AvatarBadge className="bg-green-600 dark:bg-green-800" />
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
18
apps/v4/examples/base/avatar-basic.tsx
Normal file
18
apps/v4/examples/base/avatar-basic.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export default function AvatarDemo() {
|
||||
return (
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt="@shadcn"
|
||||
className="grayscale"
|
||||
/>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
52
apps/v4/examples/base/avatar-demo.tsx
Normal file
52
apps/v4/examples/base/avatar-demo.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarGroupCount,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export default function AvatarDemo() {
|
||||
return (
|
||||
<div className="flex flex-row flex-wrap items-center gap-6 md:gap-12">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt="@shadcn"
|
||||
className="grayscale"
|
||||
/>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
<AvatarBadge className="bg-green-600 dark:bg-green-800" />
|
||||
</Avatar>
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<AvatarGroupCount>+3</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
42
apps/v4/examples/base/avatar-dropdown.tsx
Normal file
42
apps/v4/examples/base/avatar-dropdown.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui/dropdown-menu"
|
||||
|
||||
export function AvatarDropdown() {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant="ghost" size="icon" className="rounded-full" />}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-32">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>Profile</DropdownMenuItem>
|
||||
<DropdownMenuItem>Billing</DropdownMenuItem>
|
||||
<DropdownMenuItem>Settings</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive">Log out</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
34
apps/v4/examples/base/avatar-group-count-icon.tsx
Normal file
34
apps/v4/examples/base/avatar-group-count-icon.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarGroupCount,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export function AvatarGroupCountIconExample() {
|
||||
return (
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<AvatarGroupCount>
|
||||
<PlusIcon />
|
||||
</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
)
|
||||
}
|
||||
30
apps/v4/examples/base/avatar-group-count.tsx
Normal file
30
apps/v4/examples/base/avatar-group-count.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarGroupCount,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export function AvatarGroupCountExample() {
|
||||
return (
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<AvatarGroupCount>+3</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
)
|
||||
}
|
||||
28
apps/v4/examples/base/avatar-group.tsx
Normal file
28
apps/v4/examples/base/avatar-group.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export function AvatarGroupExample() {
|
||||
return (
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/maxleiter.png" alt="@maxleiter" />
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
</AvatarGroup>
|
||||
)
|
||||
}
|
||||
86
apps/v4/examples/base/avatar-rtl.tsx
Normal file
86
apps/v4/examples/base/avatar-rtl.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallback,
|
||||
AvatarGroup,
|
||||
AvatarGroupCount,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui-rtl/avatar"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
moreUsers: "+3",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
moreUsers: "+٣",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
moreUsers: "+3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function AvatarRtl() {
|
||||
const { dir, t } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row flex-wrap items-center gap-6 md:gap-12"
|
||||
dir={dir}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt="@shadcn"
|
||||
className="grayscale"
|
||||
/>
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
<AvatarBadge className="bg-green-600 dark:bg-green-800" />
|
||||
</Avatar>
|
||||
<AvatarGroup className="grayscale">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/maxleiter.png"
|
||||
alt="@maxleiter"
|
||||
/>
|
||||
<AvatarFallback>LR</AvatarFallback>
|
||||
</Avatar>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/evilrabbit.png"
|
||||
alt="@evilrabbit"
|
||||
/>
|
||||
<AvatarFallback>ER</AvatarFallback>
|
||||
</Avatar>
|
||||
<AvatarGroupCount>{t.moreUsers}</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
24
apps/v4/examples/base/avatar-size.tsx
Normal file
24
apps/v4/examples/base/avatar-size.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/styles/base-nova/ui/avatar"
|
||||
|
||||
export function AvatarSizeExample() {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 grayscale">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
23
apps/v4/examples/base/badge-colors.tsx
Normal file
23
apps/v4/examples/base/badge-colors.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Badge } from "@/styles/base-nova/ui/badge"
|
||||
|
||||
export function BadgeCustomColors() {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
12
apps/v4/examples/base/badge-demo.tsx
Normal file
12
apps/v4/examples/base/badge-demo.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Badge } from "@/styles/base-nova/ui/badge"
|
||||
|
||||
export default function BadgeDemo() {
|
||||
return (
|
||||
<div className="flex w-full flex-wrap justify-center gap-2">
|
||||
<Badge>Badge</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
18
apps/v4/examples/base/badge-icon.tsx
Normal file
18
apps/v4/examples/base/badge-icon.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { BadgeCheck, BookmarkIcon } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/styles/base-nova/ui/badge"
|
||||
|
||||
export function BadgeWithIconLeft() {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="secondary">
|
||||
<BadgeCheck data-icon="inline-start" />
|
||||
Verified
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
Bookmark
|
||||
<BookmarkIcon data-icon="inline-end" />
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
11
apps/v4/examples/base/badge-link.tsx
Normal file
11
apps/v4/examples/base/badge-link.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ArrowUpRightIcon } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/styles/base-nova/ui/badge"
|
||||
|
||||
export function BadgeAsLink() {
|
||||
return (
|
||||
<Badge render={<a href="#link" />}>
|
||||
Open Link <ArrowUpRightIcon data-icon="inline-end" />
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
67
apps/v4/examples/base/badge-rtl.tsx
Normal file
67
apps/v4/examples/base/badge-rtl.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { BadgeCheck, BookmarkIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import { Badge } from "@/styles/base-nova/ui-rtl/badge"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
badge: "Badge",
|
||||
secondary: "Secondary",
|
||||
destructive: "Destructive",
|
||||
outline: "Outline",
|
||||
verified: "Verified",
|
||||
bookmark: "Bookmark",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
badge: "شارة",
|
||||
secondary: "ثانوي",
|
||||
destructive: "مدمر",
|
||||
outline: "مخطط",
|
||||
verified: "متحقق",
|
||||
bookmark: "إشارة مرجعية",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
badge: "תג",
|
||||
secondary: "משני",
|
||||
destructive: "הרסני",
|
||||
outline: "קווי מתאר",
|
||||
verified: "מאומת",
|
||||
bookmark: "סימנייה",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function BadgeRtl() {
|
||||
const { dir, t } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-wrap justify-center gap-2" dir={dir}>
|
||||
<Badge>{t.badge}</Badge>
|
||||
<Badge variant="secondary">{t.secondary}</Badge>
|
||||
<Badge variant="destructive">{t.destructive}</Badge>
|
||||
<Badge variant="outline">{t.outline}</Badge>
|
||||
<Badge variant="secondary">
|
||||
<BadgeCheck data-icon="inline-start" />
|
||||
{t.verified}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{t.bookmark}
|
||||
<BookmarkIcon data-icon="inline-end" />
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
apps/v4/examples/base/badge-spinner.tsx
Normal file
17
apps/v4/examples/base/badge-spinner.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Badge } from "@/styles/base-nova/ui/badge"
|
||||
import { Spinner } from "@/styles/base-nova/ui/spinner"
|
||||
|
||||
export function BadgeWithSpinner() {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="destructive">
|
||||
<Spinner data-icon="inline-start" />
|
||||
Deleting
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
Generating
|
||||
<Spinner data-icon="inline-end" />
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
13
apps/v4/examples/base/badge-variants.tsx
Normal file
13
apps/v4/examples/base/badge-variants.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Badge } from "@/styles/base-nova/ui/badge"
|
||||
|
||||
export function BadgeVariants() {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge>Default</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Badge variant="ghost">Ghost</Badge>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
28
apps/v4/examples/base/breadcrumb-basic.tsx
Normal file
28
apps/v4/examples/base/breadcrumb-basic.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui/breadcrumb"
|
||||
|
||||
export function BreadcrumbBasic() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
57
apps/v4/examples/base/breadcrumb-demo.tsx
Normal file
57
apps/v4/examples/base/breadcrumb-demo.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui/breadcrumb"
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui/dropdown-menu"
|
||||
|
||||
export function BreadcrumbDemo() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<a href="#" />}>Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button size="icon-sm" variant="ghost" />}
|
||||
>
|
||||
<BreadcrumbEllipsis />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>Documentation</DropdownMenuItem>
|
||||
<DropdownMenuItem>Themes</DropdownMenuItem>
|
||||
<DropdownMenuItem>GitHub</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<a href="#" />}>Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
56
apps/v4/examples/base/breadcrumb-dropdown.tsx
Normal file
56
apps/v4/examples/base/breadcrumb-dropdown.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import Link from "next/link"
|
||||
import { ChevronDownIcon, DotIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui/breadcrumb"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui/dropdown-menu"
|
||||
|
||||
export function BreadcrumbDropdown() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="/" />}>Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<DotIcon />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<button className="flex items-center gap-1" />}
|
||||
>
|
||||
Components
|
||||
<ChevronDownIcon data-icon="inline-end" className="size-3.5" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>Documentation</DropdownMenuItem>
|
||||
<DropdownMenuItem>Themes</DropdownMenuItem>
|
||||
<DropdownMenuItem>GitHub</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<DotIcon />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
37
apps/v4/examples/base/breadcrumb-ellipsis.tsx
Normal file
37
apps/v4/examples/base/breadcrumb-ellipsis.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui/breadcrumb"
|
||||
|
||||
export function BreadcrumbEllipsisDemo() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="/" />}>Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbEllipsis />
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="/docs/components" />}>
|
||||
Components
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
34
apps/v4/examples/base/breadcrumb-link.tsx
Normal file
34
apps/v4/examples/base/breadcrumb-link.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui/breadcrumb"
|
||||
|
||||
export function BreadcrumbLinkDemo() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="#link-component" />}>
|
||||
Home
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="#link-component" />}>
|
||||
Components
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
104
apps/v4/examples/base/breadcrumb-rtl.tsx
Normal file
104
apps/v4/examples/base/breadcrumb-rtl.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { ChevronDownIcon, DotIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui-rtl/breadcrumb"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui-rtl/dropdown-menu"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
home: "Home",
|
||||
components: "Components",
|
||||
documentation: "Documentation",
|
||||
themes: "Themes",
|
||||
github: "GitHub",
|
||||
breadcrumb: "Breadcrumb",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
home: "الرئيسية",
|
||||
components: "المكونات",
|
||||
documentation: "التوثيق",
|
||||
themes: "السمات",
|
||||
github: "جيت هاب",
|
||||
breadcrumb: "مسار التنقل",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
home: "בית",
|
||||
components: "רכיבים",
|
||||
documentation: "תיעוד",
|
||||
themes: "ערכות נושא",
|
||||
github: "גיטהאב",
|
||||
breadcrumb: "פירורי לחם",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function BreadcrumbRtl() {
|
||||
const { dir, t, language } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<Breadcrumb dir={dir}>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="/" />}>{t.home}</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<DotIcon />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<button className="flex items-center gap-1" />}
|
||||
>
|
||||
{t.components}
|
||||
<ChevronDownIcon data-icon="inline-end" className="size-3.5" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align={dir === "rtl" ? "end" : "start"}
|
||||
data-lang={dir === "rtl" ? language : undefined}
|
||||
dir={dir}
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>{t.documentation}</DropdownMenuItem>
|
||||
<DropdownMenuItem>{t.themes}</DropdownMenuItem>
|
||||
<DropdownMenuItem>{t.github}</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<DotIcon />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{t.breadcrumb}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
37
apps/v4/examples/base/breadcrumb-separator.tsx
Normal file
37
apps/v4/examples/base/breadcrumb-separator.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import Link from "next/link"
|
||||
import { DotIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/styles/base-nova/ui/breadcrumb"
|
||||
|
||||
export function BreadcrumbSeparatorDemo() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="/" />}>Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<DotIcon />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink render={<Link href="/components" />}>
|
||||
Components
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator>
|
||||
<DotIcon />
|
||||
</BreadcrumbSeparator>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
18
apps/v4/examples/base/bubble-alignment.tsx
Normal file
18
apps/v4/examples/base/bubble-alignment.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Bubble, BubbleContent } from "@/styles/base-rhea/ui/bubble"
|
||||
|
||||
export function BubbleAlignmentDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>
|
||||
This bubble is aligned to the start. This is the default alignment.
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble align="end">
|
||||
<BubbleContent>
|
||||
This bubble is aligned to the end. Use this for user messages.
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
59
apps/v4/examples/base/bubble-collapsible.tsx
Normal file
59
apps/v4/examples/base/bubble-collapsible.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { Bubble, BubbleContent } from "@/styles/base-rhea/ui/bubble"
|
||||
import { Button } from "@/styles/base-rhea/ui/button"
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleTrigger,
|
||||
} from "@/styles/base-rhea/ui/collapsible"
|
||||
|
||||
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
|
||||
|
||||
export function BubbleCollapsible() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const isLong = text.length > previewLength
|
||||
const preview = `${text.slice(0, previewLength)}...`
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>How can I help you today?</BubbleContent>
|
||||
</Bubble>
|
||||
|
||||
<Bubble variant="muted" align="end">
|
||||
<BubbleContent className="whitespace-pre-line">
|
||||
<Collapsible open={open} onOpenChange={setOpen}>
|
||||
<div>{open || !isLong ? text : preview}</div>
|
||||
{isLong ? (
|
||||
<CollapsibleTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="link"
|
||||
className="gap-1 p-0 text-muted-foreground"
|
||||
/>
|
||||
}
|
||||
>
|
||||
{open ? "Show less" : "Show more"}
|
||||
<ChevronDownIcon
|
||||
data-icon="inline-end"
|
||||
className="group-data-panel-open/button:rotate-180"
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
) : null}
|
||||
</Collapsible>
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
48
apps/v4/examples/base/bubble-demo.tsx
Normal file
48
apps/v4/examples/base/bubble-demo.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleGroup,
|
||||
BubbleReactions,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
|
||||
export function BubbleDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
|
||||
<Bubble align="end">
|
||||
<BubbleContent>Hey there! what's up?</BubbleContent>
|
||||
</Bubble>
|
||||
<BubbleGroup>
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>Hey! Want to see chat bubbles?</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>
|
||||
I can group messages, switch sides, and keep the whole thread easy
|
||||
to scan.
|
||||
</BubbleContent>
|
||||
<BubbleReactions role="img" aria-label="Reaction: thumbs up">
|
||||
<span>👍</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
</BubbleGroup>
|
||||
<Bubble align="end">
|
||||
<BubbleContent>Sure. Hit me with your best demo.</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>
|
||||
Yes. You are reading a demo that is demoing itself. Very meta. Very
|
||||
on-brand.
|
||||
</BubbleContent>
|
||||
<BubbleReactions
|
||||
role="img"
|
||||
aria-label="Reactions: thumbs up, fire, eyes, and 2 more"
|
||||
>
|
||||
<span>👍</span>
|
||||
<span>🔥</span>
|
||||
<span>👀</span>
|
||||
<span>+2</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
36
apps/v4/examples/base/bubble-group-demo.tsx
Normal file
36
apps/v4/examples/base/bubble-group-demo.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleGroup,
|
||||
BubbleReactions,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
|
||||
export function BubbleGroupDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>Can you tell me what's the issue?</BubbleContent>
|
||||
</Bubble>
|
||||
<BubbleGroup>
|
||||
<Bubble align="end">
|
||||
<BubbleContent>You tell me!</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble align="end">
|
||||
<BubbleContent>It worked yesterday. You broke it!</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble align="end">
|
||||
<BubbleContent>Find the bug and fix it.</BubbleContent>
|
||||
<BubbleReactions aria-label="Reactions: eyes" align="start">
|
||||
<span>👀</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
</BubbleGroup>
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>
|
||||
Want me to diff yesterday's you against today's you?
|
||||
It's a bit embarrassing.
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
54
apps/v4/examples/base/bubble-link-button.tsx
Normal file
54
apps/v4/examples/base/bubble-link-button.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
"use client"
|
||||
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleGroup,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
|
||||
export function BubbleLinkButtonDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>How can I help you today?</BubbleContent>
|
||||
</Bubble>
|
||||
<BubbleGroup>
|
||||
<Bubble variant="tinted" align="end">
|
||||
<BubbleContent
|
||||
render={
|
||||
<button onClick={() => toast("You clicked forgot password")} />
|
||||
}
|
||||
>
|
||||
I forgot my password
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="tinted" align="end">
|
||||
<BubbleContent
|
||||
render={
|
||||
<button
|
||||
onClick={() => toast("You clicked help with subscription")}
|
||||
/>
|
||||
}
|
||||
>
|
||||
I need help with my subscription
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="tinted" align="end">
|
||||
<BubbleContent
|
||||
render={
|
||||
<button
|
||||
onClick={() =>
|
||||
toast("You clicked something else. Talk to a human.")
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
Something else. Talk to a human.
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
</BubbleGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
24
apps/v4/examples/base/bubble-markdown.tsx
Normal file
24
apps/v4/examples/base/bubble-markdown.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Markdown } from "@/components/markdown"
|
||||
import { Bubble, BubbleContent } from "@/styles/base-rhea/ui/bubble"
|
||||
|
||||
export function BubbleMarkdownDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-8 py-12">
|
||||
<Bubble align="end" variant="muted">
|
||||
<BubbleContent>
|
||||
<Markdown>{`Hello! Are you actually **thinking**?`}</Markdown>
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="ghost">
|
||||
<BubbleContent>
|
||||
<Markdown>{`Ghost bubbles work for assistant text, **markdown**, 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. You can also render \`code\` in it.
|
||||
|
||||
Ghost bubbles are full width and can take the full width of the container.
|
||||
`}</Markdown>
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
55
apps/v4/examples/base/bubble-popover.tsx
Normal file
55
apps/v4/examples/base/bubble-popover.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { InfoIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleReactions,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
import { Button } from "@/styles/base-rhea/ui/button"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverHeader,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@/styles/base-rhea/ui/popover"
|
||||
|
||||
export function BubblePopoverDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-4 py-12">
|
||||
<Bubble align="end">
|
||||
<BubbleContent>Run the build script.</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="destructive">
|
||||
<BubbleContent>Failed to run the command.</BubbleContent>
|
||||
<BubbleReactions>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label="Show error details"
|
||||
className="aria-expanded:text-destructive"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<InfoIcon />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverHeader>
|
||||
<PopoverTitle className="text-sm">
|
||||
Command failed with exit code 1
|
||||
</PopoverTitle>
|
||||
<PopoverDescription className="text-sm">
|
||||
ENOENT: no such file or directory, open pnpm-lock.yaml
|
||||
</PopoverDescription>
|
||||
</PopoverHeader>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
70
apps/v4/examples/base/bubble-reactions.tsx
Normal file
70
apps/v4/examples/base/bubble-reactions.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
"use client"
|
||||
|
||||
import { toast } from "sonner"
|
||||
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleReactions,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
import { Button } from "@/styles/base-rhea/ui/button"
|
||||
|
||||
export function BubbleReactionsDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-12 py-12">
|
||||
<Bubble variant="muted" align="end">
|
||||
<BubbleContent>
|
||||
I don't need tests, I know my code works.
|
||||
</BubbleContent>
|
||||
<BubbleReactions
|
||||
align="start"
|
||||
role="img"
|
||||
aria-label="Reactions: thumbs up, surprised"
|
||||
>
|
||||
<span>👍</span>
|
||||
<span>😮</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>
|
||||
Bold. Fine I'll add some tests. I'll let you know when
|
||||
they're done.
|
||||
</BubbleContent>
|
||||
<BubbleReactions
|
||||
role="img"
|
||||
aria-label="Reactions: eyes, rocket, and 2 more"
|
||||
>
|
||||
<span>👀</span>
|
||||
<span>🚀</span>
|
||||
<span>+2</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
<Bubble variant="default" align="end">
|
||||
<BubbleContent>
|
||||
Tests passed on the first try. All 142 of them. Looking good!
|
||||
</BubbleContent>
|
||||
<BubbleReactions
|
||||
side="top"
|
||||
align="start"
|
||||
role="img"
|
||||
aria-label="Reactions: party popper, clapping hands"
|
||||
>
|
||||
<span>🎉</span>
|
||||
<span>👏</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
<Bubble variant="destructive">
|
||||
<BubbleContent>Are you sure I can run this command?</BubbleContent>
|
||||
<BubbleReactions>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
onClick={() => toast.success("You clicked yes, running command...")}
|
||||
>
|
||||
Yes, run it
|
||||
</Button>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
34
apps/v4/examples/base/bubble-tooltip.tsx
Normal file
34
apps/v4/examples/base/bubble-tooltip.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { CheckIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleReactions,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
import { Button } from "@/styles/base-rhea/ui/button"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/styles/base-rhea/ui/tooltip"
|
||||
|
||||
export function BubbleTooltipDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-4 py-12">
|
||||
<Bubble variant="secondary">
|
||||
<BubbleContent>Did you remove the stale route?</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble align="end">
|
||||
<BubbleContent>Yes, removed it from the registry.</BubbleContent>
|
||||
<BubbleReactions>
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={<Button variant="ghost" size="icon-xs" />}>
|
||||
<CheckIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Read on Jan 5, 2026 at 4:32 PM</TooltipContent>
|
||||
</Tooltip>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
52
apps/v4/examples/base/bubble-variants.tsx
Normal file
52
apps/v4/examples/base/bubble-variants.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Markdown } from "@/components/markdown"
|
||||
import {
|
||||
Bubble,
|
||||
BubbleContent,
|
||||
BubbleReactions,
|
||||
} from "@/styles/base-rhea/ui/bubble"
|
||||
|
||||
export function BubbleVariantsDemo() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-12 py-12">
|
||||
<Bubble>
|
||||
<BubbleContent>This is the default primary bubble.</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="secondary" align="end">
|
||||
<BubbleContent>This is the secondary variant.</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="muted">
|
||||
<BubbleContent>
|
||||
This one is muted. It uses a lower emphasis color for the chat bubble.
|
||||
</BubbleContent>
|
||||
<BubbleReactions role="img" aria-label="Reaction: thumbs up">
|
||||
<span>👍</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
<Bubble variant="tinted" align="end">
|
||||
<BubbleContent>
|
||||
This one is tinted. The tint is a softer color derived from the
|
||||
primary color.
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="outline">
|
||||
<BubbleContent>We can also use an outlined variant.</BubbleContent>
|
||||
</Bubble>
|
||||
<Bubble variant="destructive" align="end">
|
||||
<BubbleContent>Or a destructive variant with a reaction.</BubbleContent>
|
||||
<BubbleReactions role="img" aria-label="Reaction: fire">
|
||||
<span>🔥</span>
|
||||
</BubbleReactions>
|
||||
</Bubble>
|
||||
<Bubble variant="ghost">
|
||||
<BubbleContent>
|
||||
<Markdown>{`Ghost bubbles work for assistant text, **markdown**, 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. You can also render \`code\` in it.
|
||||
|
||||
Ghost bubbles are full width and can take the full width of the container.
|
||||
`}</Markdown>
|
||||
</BubbleContent>
|
||||
</Bubble>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
5
apps/v4/examples/base/button-default.tsx
Normal file
5
apps/v4/examples/base/button-default.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonDefault() {
|
||||
return <Button>Button</Button>
|
||||
}
|
||||
14
apps/v4/examples/base/button-demo.tsx
Normal file
14
apps/v4/examples/base/button-demo.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ArrowUpIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonDemo() {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<Button variant="outline">Button</Button>
|
||||
<Button variant="outline" size="icon" aria-label="Submit">
|
||||
<ArrowUpIcon />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
5
apps/v4/examples/base/button-destructive.tsx
Normal file
5
apps/v4/examples/base/button-destructive.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonDestructive() {
|
||||
return <Button variant="destructive">Destructive</Button>
|
||||
}
|
||||
5
apps/v4/examples/base/button-ghost.tsx
Normal file
5
apps/v4/examples/base/button-ghost.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonGhost() {
|
||||
return <Button variant="ghost">Ghost</Button>
|
||||
}
|
||||
116
apps/v4/examples/base/button-group-demo.tsx
Normal file
116
apps/v4/examples/base/button-group-demo.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ArrowLeftIcon,
|
||||
CalendarPlusIcon,
|
||||
ClockIcon,
|
||||
ListFilterIcon,
|
||||
MailCheckIcon,
|
||||
MoreHorizontalIcon,
|
||||
TagIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui/dropdown-menu"
|
||||
|
||||
export default function ButtonGroupDemo() {
|
||||
const [label, setLabel] = React.useState("personal")
|
||||
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<ButtonGroup className="hidden sm:flex">
|
||||
<Button variant="outline" size="icon" aria-label="Go Back">
|
||||
<ArrowLeftIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">Archive</Button>
|
||||
<Button variant="outline">Report</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">Snooze</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button variant="outline" size="icon" aria-label="More Options" />
|
||||
}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<MailCheckIcon />
|
||||
Mark as Read
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ArchiveIcon />
|
||||
Archive
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<ClockIcon />
|
||||
Snooze
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CalendarPlusIcon />
|
||||
Add to Calendar
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ListFilterIcon />
|
||||
Add to List
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<TagIcon />
|
||||
Label As...
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={label}
|
||||
onValueChange={setLabel}
|
||||
>
|
||||
<DropdownMenuRadioItem value="personal">
|
||||
Personal
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="work">
|
||||
Work
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="other">
|
||||
Other
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<Trash2Icon />
|
||||
Trash
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
73
apps/v4/examples/base/button-group-dropdown.tsx
Normal file
73
apps/v4/examples/base/button-group-dropdown.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
AlertTriangleIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
CopyIcon,
|
||||
ShareIcon,
|
||||
TrashIcon,
|
||||
UserRoundXIcon,
|
||||
VolumeOffIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui/dropdown-menu"
|
||||
|
||||
export default function ButtonGroupDropdown() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">Follow</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button variant="outline" className="pl-2!" />}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-44">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<VolumeOffIcon />
|
||||
Mute Conversation
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CheckIcon />
|
||||
Mark as Read
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<AlertTriangleIcon />
|
||||
Report Conversation
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<UserRoundXIcon />
|
||||
Block User
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ShareIcon />
|
||||
Share Conversation
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CopyIcon />
|
||||
Copy Conversation
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<TrashIcon />
|
||||
Delete Conversation
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
60
apps/v4/examples/base/button-group-input-group.tsx
Normal file
60
apps/v4/examples/base/button-group-input-group.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { AudioLinesIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupButton,
|
||||
InputGroupInput,
|
||||
} from "@/styles/base-nova/ui/input-group"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/styles/base-nova/ui/tooltip"
|
||||
|
||||
export default function ButtonGroupInputGroup() {
|
||||
const [voiceEnabled, setVoiceEnabled] = React.useState(false)
|
||||
|
||||
return (
|
||||
<ButtonGroup className="[--radius:9999rem]">
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
placeholder={
|
||||
voiceEnabled ? "Record and send audio..." : "Send a message..."
|
||||
}
|
||||
disabled={voiceEnabled}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<InputGroupButton
|
||||
onClick={() => setVoiceEnabled(!voiceEnabled)}
|
||||
size="icon-xs"
|
||||
data-active={voiceEnabled}
|
||||
className="data-[active=true]:bg-orange-100 data-[active=true]:text-orange-700 dark:data-[active=true]:bg-orange-800 dark:data-[active=true]:text-orange-100"
|
||||
aria-pressed={voiceEnabled}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AudioLinesIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Voice Mode</TooltipContent>
|
||||
</Tooltip>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
16
apps/v4/examples/base/button-group-input.tsx
Normal file
16
apps/v4/examples/base/button-group-input.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { SearchIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import { Input } from "@/styles/base-nova/ui/input"
|
||||
|
||||
export default function ButtonGroupInput() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Input placeholder="Search..." />
|
||||
<Button variant="outline" aria-label="Search">
|
||||
<SearchIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
38
apps/v4/examples/base/button-group-nested.tsx
Normal file
38
apps/v4/examples/base/button-group-nested.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { AudioLinesIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import { Input } from "@/styles/base-nova/ui/input"
|
||||
import {
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupInput,
|
||||
} from "@/styles/base-nova/ui/input-group"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/styles/base-nova/ui/tooltip"
|
||||
|
||||
export function ButtonGroupNested() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="icon">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<InputGroup>
|
||||
<InputGroupInput placeholder="Send a message..." />
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={<InputGroupAddon align="inline-end" />}>
|
||||
<AudioLinesIcon />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Voice Mode</TooltipContent>
|
||||
</Tooltip>
|
||||
</InputGroup>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
21
apps/v4/examples/base/button-group-orientation.tsx
Normal file
21
apps/v4/examples/base/button-group-orientation.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { MinusIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
|
||||
export default function ButtonGroupOrientation() {
|
||||
return (
|
||||
<ButtonGroup
|
||||
orientation="vertical"
|
||||
aria-label="Media controls"
|
||||
className="h-fit"
|
||||
>
|
||||
<Button variant="outline" size="icon">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon">
|
||||
<MinusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
58
apps/v4/examples/base/button-group-popover.tsx
Normal file
58
apps/v4/examples/base/button-group-popover.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { BotIcon, ChevronDownIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldLabel,
|
||||
} from "@/styles/base-nova/ui/field"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverHeader,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@/styles/base-nova/ui/popover"
|
||||
import { Textarea } from "@/styles/base-nova/ui/textarea"
|
||||
|
||||
export default function ButtonGroupPopover() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">
|
||||
<BotIcon /> Copilot
|
||||
</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<Button variant="outline" size="icon" aria-label="Open Popover" />
|
||||
}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="rounded-xl text-sm">
|
||||
<PopoverHeader>
|
||||
<PopoverTitle>Start a new task with Copilot</PopoverTitle>
|
||||
<PopoverDescription>
|
||||
Describe your task in natural language.
|
||||
</PopoverDescription>
|
||||
</PopoverHeader>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="task" className="sr-only">
|
||||
Task Description
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
id="task"
|
||||
placeholder="I need to..."
|
||||
className="resize-none"
|
||||
/>
|
||||
<FieldDescription>
|
||||
Copilot will open a pull request for review.
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
186
apps/v4/examples/base/button-group-rtl.tsx
Normal file
186
apps/v4/examples/base/button-group-rtl.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ArrowLeftIcon,
|
||||
CalendarPlusIcon,
|
||||
ClockIcon,
|
||||
ListFilterIcon,
|
||||
MailCheckIcon,
|
||||
MoreHorizontalIcon,
|
||||
TagIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import { Button } from "@/styles/base-nova/ui-rtl/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui-rtl/button-group"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/styles/base-nova/ui-rtl/dropdown-menu"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
archive: "Archive",
|
||||
report: "Report",
|
||||
snooze: "Snooze",
|
||||
markAsRead: "Mark as Read",
|
||||
addToCalendar: "Add to Calendar",
|
||||
addToList: "Add to List",
|
||||
labelAs: "Label As...",
|
||||
personal: "Personal",
|
||||
work: "Work",
|
||||
other: "Other",
|
||||
trash: "Trash",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
archive: "أرشفة",
|
||||
report: "تقرير",
|
||||
snooze: "تأجيل",
|
||||
markAsRead: "وضع علامة كمقروء",
|
||||
addToCalendar: "إضافة إلى التقويم",
|
||||
addToList: "إضافة إلى القائمة",
|
||||
labelAs: "تصنيف كـ...",
|
||||
personal: "شخصي",
|
||||
work: "عمل",
|
||||
other: "آخر",
|
||||
trash: "سلة المهملات",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
archive: "ארכיון",
|
||||
report: "דוח",
|
||||
snooze: "דחה",
|
||||
markAsRead: "סמן כנקרא",
|
||||
addToCalendar: "הוסף ליומן",
|
||||
addToList: "הוסף לרשימה",
|
||||
labelAs: "תייג כ...",
|
||||
personal: "אישי",
|
||||
work: "עבודה",
|
||||
other: "אחר",
|
||||
trash: "פח",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function ButtonGroupRtl() {
|
||||
const { dir, t, language } = useTranslation(translations, "ar")
|
||||
const [label, setLabel] = React.useState("personal")
|
||||
|
||||
return (
|
||||
<div dir={dir}>
|
||||
<ButtonGroup>
|
||||
<ButtonGroup className="hidden sm:flex">
|
||||
<Button variant="outline" size="icon" aria-label="Go Back">
|
||||
<ArrowLeftIcon className="rtl:rotate-180" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">{t.archive}</Button>
|
||||
<Button variant="outline">{t.report}</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">{t.snooze}</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="More Options"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align={dir === "rtl" ? "start" : "end"}
|
||||
data-lang={dir === "rtl" ? language : undefined}
|
||||
dir={dir}
|
||||
className="w-40"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<MailCheckIcon />
|
||||
{t.markAsRead}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ArchiveIcon />
|
||||
{t.archive}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<ClockIcon />
|
||||
{t.snooze}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CalendarPlusIcon />
|
||||
{t.addToCalendar}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<ListFilterIcon />
|
||||
{t.addToList}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<TagIcon />
|
||||
{t.labelAs}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent
|
||||
dir={dir}
|
||||
data-lang={dir === "rtl" ? language : undefined}
|
||||
>
|
||||
<DropdownMenuRadioGroup
|
||||
value={label}
|
||||
onValueChange={setLabel}
|
||||
>
|
||||
<DropdownMenuRadioItem value="personal">
|
||||
{t.personal}
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="work">
|
||||
{t.work}
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="other">
|
||||
{t.other}
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem variant="destructive">
|
||||
<Trash2Icon />
|
||||
{t.trash}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
55
apps/v4/examples/base/button-group-select.tsx
Normal file
55
apps/v4/examples/base/button-group-select.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { ArrowRightIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
import { Input } from "@/styles/base-nova/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "@/styles/base-nova/ui/select"
|
||||
|
||||
const CURRENCIES = [
|
||||
{ label: "US Dollar", value: "$" },
|
||||
{ label: "Euro", value: "€" },
|
||||
{ label: "British Pound", value: "£" },
|
||||
]
|
||||
|
||||
export default function ButtonGroupSelect() {
|
||||
const [currency, setCurrency] = React.useState("$")
|
||||
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Select
|
||||
items={CURRENCIES}
|
||||
value={currency}
|
||||
onValueChange={(value) => setCurrency(value as string)}
|
||||
>
|
||||
<SelectTrigger className="font-mono">{currency}</SelectTrigger>
|
||||
<SelectContent alignItemWithTrigger={false} align="start">
|
||||
<SelectGroup>
|
||||
{CURRENCIES.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.value}{" "}
|
||||
<span className="text-muted-foreground">{item.label}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input placeholder="10.00" pattern="[0-9]*" />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button aria-label="Send" size="icon" variant="outline">
|
||||
<ArrowRightIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
19
apps/v4/examples/base/button-group-separator.tsx
Normal file
19
apps/v4/examples/base/button-group-separator.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupSeparator,
|
||||
} from "@/styles/base-nova/ui/button-group"
|
||||
|
||||
export default function ButtonGroupSeparatorDemo() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button variant="secondary" size="sm">
|
||||
Copy
|
||||
</Button>
|
||||
<ButtonGroupSeparator />
|
||||
<Button variant="secondary" size="sm">
|
||||
Paste
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
47
apps/v4/examples/base/button-group-size.tsx
Normal file
47
apps/v4/examples/base/button-group-size.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { PlusIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { ButtonGroup } from "@/styles/base-nova/ui/button-group"
|
||||
|
||||
export default function ButtonGroupSize() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-8">
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="sm">
|
||||
Small
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Button
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
Group
|
||||
</Button>
|
||||
<Button variant="outline" size="icon-sm">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline">Default</Button>
|
||||
<Button variant="outline">Button</Button>
|
||||
<Button variant="outline">Group</Button>
|
||||
<Button variant="outline" size="icon">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup>
|
||||
<Button variant="outline" size="lg">
|
||||
Large
|
||||
</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
Button
|
||||
</Button>
|
||||
<Button variant="outline" size="lg">
|
||||
Group
|
||||
</Button>
|
||||
<Button variant="outline" size="icon-lg">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
19
apps/v4/examples/base/button-group-split.tsx
Normal file
19
apps/v4/examples/base/button-group-split.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { IconPlus } from "@tabler/icons-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupSeparator,
|
||||
} from "@/styles/base-nova/ui/button-group"
|
||||
|
||||
export default function ButtonGroupSplit() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button variant="secondary">Button</Button>
|
||||
<ButtonGroupSeparator />
|
||||
<Button size="icon" variant="secondary">
|
||||
<IconPlus />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)
|
||||
}
|
||||
11
apps/v4/examples/base/button-icon.tsx
Normal file
11
apps/v4/examples/base/button-icon.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { CircleFadingArrowUpIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonIcon() {
|
||||
return (
|
||||
<Button variant="outline" size="icon">
|
||||
<CircleFadingArrowUpIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
5
apps/v4/examples/base/button-link.tsx
Normal file
5
apps/v4/examples/base/button-link.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonLink() {
|
||||
return <Button variant="link">Link</Button>
|
||||
}
|
||||
5
apps/v4/examples/base/button-outline.tsx
Normal file
5
apps/v4/examples/base/button-outline.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonOutline() {
|
||||
return <Button variant="outline">Outline</Button>
|
||||
}
|
||||
12
apps/v4/examples/base/button-render.tsx
Normal file
12
apps/v4/examples/base/button-render.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { buttonVariants } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonRender() {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
className={buttonVariants({ variant: "secondary", size: "sm" })}
|
||||
>
|
||||
Login
|
||||
</a>
|
||||
)
|
||||
}
|
||||
14
apps/v4/examples/base/button-rounded.tsx
Normal file
14
apps/v4/examples/base/button-rounded.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ArrowUpIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonRounded() {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button className="rounded-full">Get Started</Button>
|
||||
<Button variant="outline" size="icon" className="rounded-full">
|
||||
<ArrowUpIcon />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
apps/v4/examples/base/button-rtl.tsx
Normal file
61
apps/v4/examples/base/button-rtl.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import { ArrowRightIcon, PlusIcon } from "lucide-react"
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
type Translations,
|
||||
} from "@/components/language-selector"
|
||||
import { Button } from "@/styles/base-nova/ui-rtl/button"
|
||||
import { Spinner } from "@/styles/base-nova/ui-rtl/spinner"
|
||||
|
||||
const translations: Translations = {
|
||||
en: {
|
||||
dir: "ltr",
|
||||
values: {
|
||||
button: "Button",
|
||||
submit: "Submit",
|
||||
delete: "Delete",
|
||||
loading: "Loading",
|
||||
},
|
||||
},
|
||||
ar: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
button: "زر",
|
||||
submit: "إرسال",
|
||||
delete: "حذف",
|
||||
loading: "جاري التحميل",
|
||||
},
|
||||
},
|
||||
he: {
|
||||
dir: "rtl",
|
||||
values: {
|
||||
button: "כפתור",
|
||||
submit: "שלח",
|
||||
delete: "מחק",
|
||||
loading: "טוען",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function ButtonRtl() {
|
||||
const { dir, t } = useTranslation(translations, "ar")
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 md:flex-row" dir={dir}>
|
||||
<Button variant="outline">{t.button}</Button>
|
||||
<Button variant="destructive">{t.delete}</Button>
|
||||
<Button variant="outline">
|
||||
{t.submit}{" "}
|
||||
<ArrowRightIcon className="rtl:rotate-180" data-icon="inline-end" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" aria-label="Add">
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button variant="secondary" disabled>
|
||||
<Spinner data-icon="inline-start" /> {t.loading}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
5
apps/v4/examples/base/button-secondary.tsx
Normal file
5
apps/v4/examples/base/button-secondary.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonSecondary() {
|
||||
return <Button variant="secondary">Secondary</Button>
|
||||
}
|
||||
40
apps/v4/examples/base/button-size.tsx
Normal file
40
apps/v4/examples/base/button-size.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ArrowUpRightIcon } from "lucide-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonSize() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-8 sm:flex-row">
|
||||
<div className="flex items-start gap-2">
|
||||
<Button size="xs" variant="outline">
|
||||
Extra Small
|
||||
</Button>
|
||||
<Button size="icon-xs" aria-label="Submit" variant="outline">
|
||||
<ArrowUpRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<Button size="sm" variant="outline">
|
||||
Small
|
||||
</Button>
|
||||
<Button size="icon-sm" aria-label="Submit" variant="outline">
|
||||
<ArrowUpRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<Button variant="outline">Default</Button>
|
||||
<Button size="icon" aria-label="Submit" variant="outline">
|
||||
<ArrowUpRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<Button variant="outline" size="lg">
|
||||
Large
|
||||
</Button>
|
||||
<Button size="icon-lg" aria-label="Submit" variant="outline">
|
||||
<ArrowUpRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
apps/v4/examples/base/button-spinner.tsx
Normal file
17
apps/v4/examples/base/button-spinner.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { Spinner } from "@/styles/base-nova/ui/spinner"
|
||||
|
||||
export default function ButtonLoading() {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" disabled>
|
||||
<Spinner data-icon="inline-start" />
|
||||
Generating
|
||||
</Button>
|
||||
<Button variant="secondary" disabled>
|
||||
Downloading
|
||||
<Spinner data-icon="inline-start" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
apps/v4/examples/base/button-with-icon.tsx
Normal file
17
apps/v4/examples/base/button-with-icon.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IconGitBranch, IconGitFork } from "@tabler/icons-react"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
|
||||
export default function ButtonWithIcon() {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline">
|
||||
<IconGitBranch data-icon="inline-start" /> New Branch
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
Fork
|
||||
<IconGitFork data-icon="inline-end" />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
7
apps/v4/examples/base/calendar-basic.tsx
Normal file
7
apps/v4/examples/base/calendar-basic.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
|
||||
export default function CalendarBasic() {
|
||||
return <Calendar mode="single" className="rounded-lg border" />
|
||||
}
|
||||
36
apps/v4/examples/base/calendar-booked-dates.tsx
Normal file
36
apps/v4/examples/base/calendar-booked-dates.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
import { Card, CardContent } from "@/styles/base-nova/ui/card"
|
||||
|
||||
export function CalendarBookedDates() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(new Date().getFullYear(), 0, 6)
|
||||
)
|
||||
const bookedDates = Array.from(
|
||||
{ length: 15 },
|
||||
(_, i) => new Date(new Date().getFullYear(), 0, 12 + i)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="mx-auto w-fit p-0">
|
||||
<CardContent className="p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={bookedDates}
|
||||
modifiers={{
|
||||
booked: bookedDates,
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
booked: "[&>button]:line-through opacity-100",
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
13
apps/v4/examples/base/calendar-caption.tsx
Normal file
13
apps/v4/examples/base/calendar-caption.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
|
||||
export function CalendarCaption() {
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
captionLayout="dropdown"
|
||||
className="rounded-lg border"
|
||||
/>
|
||||
)
|
||||
}
|
||||
51
apps/v4/examples/base/calendar-custom-days.tsx
Normal file
51
apps/v4/examples/base/calendar-custom-days.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar, CalendarDayButton } from "@/styles/base-nova/ui/calendar"
|
||||
import { Card, CardContent } from "@/styles/base-nova/ui/card"
|
||||
|
||||
export function CalendarCustomDays() {
|
||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 11, 8),
|
||||
to: addDays(new Date(new Date().getFullYear(), 11, 8), 10),
|
||||
})
|
||||
|
||||
return (
|
||||
<Card className="mx-auto w-fit p-0">
|
||||
<CardContent className="p-0">
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={range?.from}
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={1}
|
||||
captionLayout="dropdown"
|
||||
className="[--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)]"
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) => {
|
||||
return date.toLocaleString("default", { month: "long" })
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
DayButton: ({ children, modifiers, day, ...props }) => {
|
||||
const isWeekend =
|
||||
day.date.getDay() === 0 || day.date.getDay() === 6
|
||||
|
||||
return (
|
||||
<CalendarDayButton day={day} modifiers={modifiers} {...props}>
|
||||
{children}
|
||||
{!modifiers.outside && (
|
||||
<span>{isWeekend ? "$120" : "$100"}</span>
|
||||
)}
|
||||
</CalendarDayButton>
|
||||
)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
19
apps/v4/examples/base/calendar-demo.tsx
Normal file
19
apps/v4/examples/base/calendar-demo.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
|
||||
export default function CalendarDemo() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border"
|
||||
captionLayout="dropdown"
|
||||
/>
|
||||
)
|
||||
}
|
||||
233
apps/v4/examples/base/calendar-hijri.tsx
Normal file
233
apps/v4/examples/base/calendar-hijri.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Vazirmatn } from "next/font/google"
|
||||
import { cn } from "cn"
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
} from "lucide-react"
|
||||
import { getDefaultClassNames, type DayButton } from "react-day-picker"
|
||||
import { DayPicker } from "react-day-picker/persian"
|
||||
|
||||
import { Button, buttonVariants } from "@/styles/base-nova/ui/button"
|
||||
|
||||
const vazirmatn = Vazirmatn({ subsets: ["arabic"] })
|
||||
|
||||
export default function CalendarHijri() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(2025, 5, 12)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={vazirmatn.className}>
|
||||
<Calendar
|
||||
mode="single"
|
||||
defaultMonth={date}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
className="rounded-lg border"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// The code below is for this example only.
|
||||
// For your own calendar, you would edit the calendar.tsx component directly.
|
||||
// ----------------------------------------------------------------------------
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
captionLayout = "label",
|
||||
buttonVariant = "ghost",
|
||||
formatters,
|
||||
components,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DayPicker> & {
|
||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
||||
}) {
|
||||
const defaultClassNames = getDefaultClassNames()
|
||||
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn(
|
||||
"group/calendar bg-background p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||
className
|
||||
)}
|
||||
captionLayout={captionLayout}
|
||||
formatters={{
|
||||
formatMonthDropdown: (date) =>
|
||||
date.toLocaleString("default", { month: "short" }),
|
||||
...formatters,
|
||||
}}
|
||||
classNames={{
|
||||
root: cn("w-fit", defaultClassNames.root),
|
||||
months: cn(
|
||||
"relative flex flex-col gap-4 md:flex-row",
|
||||
defaultClassNames.months
|
||||
),
|
||||
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
||||
nav: cn(
|
||||
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
||||
defaultClassNames.nav
|
||||
),
|
||||
button_previous: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
||||
defaultClassNames.button_previous
|
||||
),
|
||||
button_next: cn(
|
||||
buttonVariants({ variant: buttonVariant }),
|
||||
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
||||
defaultClassNames.button_next
|
||||
),
|
||||
month_caption: cn(
|
||||
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
|
||||
defaultClassNames.month_caption
|
||||
),
|
||||
dropdowns: cn(
|
||||
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
||||
defaultClassNames.dropdowns
|
||||
),
|
||||
dropdown_root: cn(
|
||||
"relative rounded-md border border-input shadow-xs has-focus:border-ring has-focus:ring-[3px] has-focus:ring-ring/50",
|
||||
defaultClassNames.dropdown_root
|
||||
),
|
||||
dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
|
||||
caption_label: cn(
|
||||
"font-medium select-none",
|
||||
captionLayout === "label"
|
||||
? "text-sm"
|
||||
: "flex h-8 items-center gap-1 rounded-md pr-1 pl-2 text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
||||
defaultClassNames.caption_label
|
||||
),
|
||||
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||
weekday: cn(
|
||||
"flex-1 rounded-md text-[0.8rem] font-normal text-muted-foreground select-none",
|
||||
defaultClassNames.weekday
|
||||
),
|
||||
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||
week_number_header: cn(
|
||||
"w-(--cell-size) select-none",
|
||||
defaultClassNames.week_number_header
|
||||
),
|
||||
week_number: cn(
|
||||
"text-[0.8rem] text-muted-foreground select-none",
|
||||
defaultClassNames.week_number
|
||||
),
|
||||
day: cn(
|
||||
"group/day relative aspect-square h-full w-full p-0 text-center select-none [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
|
||||
defaultClassNames.day
|
||||
),
|
||||
range_start: cn(
|
||||
"rounded-l-md bg-accent",
|
||||
defaultClassNames.range_start
|
||||
),
|
||||
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
|
||||
today: cn(
|
||||
"rounded-md bg-accent text-accent-foreground data-[selected=true]:rounded-none",
|
||||
defaultClassNames.today
|
||||
),
|
||||
outside: cn(
|
||||
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||
defaultClassNames.outside
|
||||
),
|
||||
disabled: cn(
|
||||
"text-muted-foreground opacity-50",
|
||||
defaultClassNames.disabled
|
||||
),
|
||||
hidden: cn("invisible", defaultClassNames.hidden),
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
Root: ({ className, rootRef, ...props }) => {
|
||||
return (
|
||||
<div
|
||||
data-slot="calendar"
|
||||
ref={rootRef}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
Chevron: ({ className, orientation, ...props }) => {
|
||||
if (orientation === "left") {
|
||||
return (
|
||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
if (orientation === "right") {
|
||||
return (
|
||||
<ChevronRightIcon
|
||||
className={cn("size-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||
)
|
||||
},
|
||||
DayButton: CalendarDayButton,
|
||||
WeekNumber: ({ children, ...props }) => {
|
||||
return (
|
||||
<td {...props}>
|
||||
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
||||
{children}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
},
|
||||
...components,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CalendarDayButton({
|
||||
className,
|
||||
day,
|
||||
modifiers,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DayButton>) {
|
||||
const defaultClassNames = getDefaultClassNames()
|
||||
|
||||
const ref = React.useRef<HTMLButtonElement>(null)
|
||||
React.useEffect(() => {
|
||||
if (modifiers.focused) ref.current?.focus()
|
||||
}, [modifiers.focused])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
data-day={day.date.toLocaleDateString()}
|
||||
data-selected-single={
|
||||
modifiers.selected &&
|
||||
!modifiers.range_start &&
|
||||
!modifiers.range_end &&
|
||||
!modifiers.range_middle
|
||||
}
|
||||
data-range-start={modifiers.range_start}
|
||||
data-range-end={modifiers.range_end}
|
||||
data-range-middle={modifiers.range_middle}
|
||||
className={cn(
|
||||
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-accent-foreground [&>span]:text-xs [&>span]:opacity-70",
|
||||
defaultClassNames.day,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
12
apps/v4/examples/base/calendar-multiple.tsx
Normal file
12
apps/v4/examples/base/calendar-multiple.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
import { Card, CardContent } from "@/styles/base-nova/ui/card"
|
||||
|
||||
export function CalendarMultiple() {
|
||||
return (
|
||||
<Card className="mx-auto w-fit p-0">
|
||||
<CardContent className="p-0">
|
||||
<Calendar mode="multiple" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
58
apps/v4/examples/base/calendar-presets.tsx
Normal file
58
apps/v4/examples/base/calendar-presets.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
|
||||
import { Button } from "@/styles/base-nova/ui/button"
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
import { Card, CardContent, CardFooter } from "@/styles/base-nova/ui/card"
|
||||
|
||||
export function CalendarWithPresets() {
|
||||
const [date, setDate] = React.useState<Date | undefined>(
|
||||
new Date(new Date().getFullYear(), 1, 12)
|
||||
)
|
||||
const [currentMonth, setCurrentMonth] = React.useState<Date>(
|
||||
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
|
||||
)
|
||||
|
||||
return (
|
||||
<Card className="mx-auto w-fit max-w-[300px]" size="sm">
|
||||
<CardContent>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
month={currentMonth}
|
||||
onMonthChange={setCurrentMonth}
|
||||
fixedWeeks
|
||||
className="p-0 [--cell-size:--spacing(9.5)]"
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-wrap gap-2 border-t">
|
||||
{[
|
||||
{ label: "Today", value: 0 },
|
||||
{ label: "Tomorrow", value: 1 },
|
||||
{ label: "In 3 days", value: 3 },
|
||||
{ label: "In a week", value: 7 },
|
||||
{ label: "In 2 weeks", value: 14 },
|
||||
].map((preset) => (
|
||||
<Button
|
||||
key={preset.value}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
const newDate = addDays(new Date(), preset.value)
|
||||
setDate(newDate)
|
||||
setCurrentMonth(
|
||||
new Date(newDate.getFullYear(), newDate.getMonth(), 1)
|
||||
)
|
||||
}}
|
||||
>
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
26
apps/v4/examples/base/calendar-range.tsx
Normal file
26
apps/v4/examples/base/calendar-range.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { addDays } from "date-fns"
|
||||
import { type DateRange } from "react-day-picker"
|
||||
|
||||
import { Calendar } from "@/styles/base-nova/ui/calendar"
|
||||
import { Card, CardContent } from "@/styles/base-nova/ui/card"
|
||||
|
||||
export function CalendarRange() {
|
||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
||||
from: new Date(new Date().getFullYear(), 0, 12),
|
||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
|
||||
})
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={setDateRange}
|
||||
numberOfMonths={2}
|
||||
className="rounded-lg border"
|
||||
/>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user