parent
88b6d2a597
commit
99a2893d51
@ -0,0 +1,50 @@ |
|||||||
|
'use client'; |
||||||
|
|
||||||
|
import { |
||||||
|
AlertDialog, |
||||||
|
AlertDialogAction, |
||||||
|
AlertDialogDescription, |
||||||
|
AlertDialogHeader, |
||||||
|
AlertDialogContent, |
||||||
|
AlertDialogTitle, |
||||||
|
AlertDialogTrigger, |
||||||
|
AlertDialogFooter, |
||||||
|
AlertDialogCancel, |
||||||
|
} from '@/components/ui/alert-dialog'; |
||||||
|
import { Button } from '@/components/ui/button'; |
||||||
|
import { useState } from 'react'; |
||||||
|
|
||||||
|
export default function AlertDialogPage() { |
||||||
|
const [dialogResult, setDialogResult] = useState(''); |
||||||
|
|
||||||
|
function handleContinue() { |
||||||
|
setDialogResult('계속을 눌렀습니다.'); |
||||||
|
} |
||||||
|
|
||||||
|
function handleCancel() { |
||||||
|
setDialogResult('취소를 눌렀습니다.'); |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<AlertDialog> |
||||||
|
<AlertDialogTrigger asChild> |
||||||
|
<Button variant="outline">다이얼로그 출력</Button> |
||||||
|
</AlertDialogTrigger> |
||||||
|
<AlertDialogContent> |
||||||
|
<AlertDialogHeader> |
||||||
|
<AlertDialogTitle>작업을 확인하세요</AlertDialogTitle> |
||||||
|
<AlertDialogDescription> |
||||||
|
이 작업은 되돌릴 수 없습니다. 진행하시겠습니까? |
||||||
|
</AlertDialogDescription> |
||||||
|
</AlertDialogHeader> |
||||||
|
<AlertDialogFooter> |
||||||
|
<AlertDialogCancel onClick={handleCancel}>취소</AlertDialogCancel> |
||||||
|
<AlertDialogAction onClick={handleContinue}>계속</AlertDialogAction> |
||||||
|
</AlertDialogFooter> |
||||||
|
</AlertDialogContent> |
||||||
|
</AlertDialog> |
||||||
|
{dialogResult && <p className="mt-4 text-blue-500">{dialogResult}</p>} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
'use client'; |
||||||
|
|
||||||
|
import { Badge } from '@/components/ui/badge'; |
||||||
|
import { BadgeCheckIcon } from 'lucide-react'; |
||||||
|
|
||||||
|
export default function BadgePage() { |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="flex items-center justify-center space-x-3"> |
||||||
|
<div className="flex flex-col items-center gap-2 rounded p-3 shadow-lg"> |
||||||
|
<Badge>Badge</Badge> |
||||||
|
<Badge variant="secondary">Secondary</Badge> |
||||||
|
<Badge variant="outline">Outline</Badge> |
||||||
|
<Badge variant="destructive">Destructive</Badge> |
||||||
|
</div> |
||||||
|
<div className="flex flex-col items-center gap-2 rounded p-3 shadow-lg"> |
||||||
|
<Badge> |
||||||
|
<BadgeCheckIcon /> |
||||||
|
Varified |
||||||
|
</Badge> |
||||||
|
<Badge variant="destructive" className="h-5 min-w-5 rounded-full font-mono tabular-nums"> |
||||||
|
99 |
||||||
|
</Badge> |
||||||
|
<Badge variant="outline" className="h-5 min-w-5 rounded-full font-mono tabular-nums"> |
||||||
|
100+ |
||||||
|
</Badge> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
'use client'; |
||||||
|
|
||||||
|
import { Calendar } from '@/components/ui/calendar'; |
||||||
|
import { DateTime } from 'luxon'; |
||||||
|
import { useState } from 'react'; |
||||||
|
|
||||||
|
export default function CalendarPage() { |
||||||
|
const [date, setDate] = useState<Date | undefined>(new Date()); |
||||||
|
const [luxonDate, setLuxonDate] = useState<DateTime | undefined>(DateTime.now()); |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="flex items-center justify-center space-x-4"> |
||||||
|
<div> |
||||||
|
<Calendar |
||||||
|
mode="single" |
||||||
|
selected={date} |
||||||
|
onSelect={setDate} |
||||||
|
className="rounded-md border shadow-sm" |
||||||
|
captionLayout="dropdown" |
||||||
|
/> |
||||||
|
<p className="text-center text-sm text-gray-600">{date?.toLocaleDateString()}</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div> |
||||||
|
<Calendar |
||||||
|
mode="single" |
||||||
|
selected={luxonDate ? luxonDate.toJSDate() : undefined} |
||||||
|
onSelect={(d: Date | undefined) => setLuxonDate(d ? DateTime.fromJSDate(d) : undefined)} |
||||||
|
className="rounded-md border shadow-sm" |
||||||
|
captionLayout="dropdown" |
||||||
|
/> |
||||||
|
<p className="text-center text-sm text-gray-600"> |
||||||
|
{luxonDate?.toJSDate().toLocaleDateString()} |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
import { buttonVariants } from "@/components/ui/button" |
||||||
|
|
||||||
|
function AlertDialog({ |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) { |
||||||
|
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} /> |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogTrigger({ |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} /> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogPortal({ |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} /> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogOverlay({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Overlay |
||||||
|
data-slot="alert-dialog-overlay" |
||||||
|
className={cn( |
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogContent({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPortal> |
||||||
|
<AlertDialogOverlay /> |
||||||
|
<AlertDialogPrimitive.Content |
||||||
|
data-slot="alert-dialog-content" |
||||||
|
className={cn( |
||||||
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
</AlertDialogPortal> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogHeader({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<"div">) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
data-slot="alert-dialog-header" |
||||||
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogFooter({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<"div">) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
data-slot="alert-dialog-footer" |
||||||
|
className={cn( |
||||||
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogTitle({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Title |
||||||
|
data-slot="alert-dialog-title" |
||||||
|
className={cn("text-lg font-semibold", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogDescription({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Description |
||||||
|
data-slot="alert-dialog-description" |
||||||
|
className={cn("text-muted-foreground text-sm", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogAction({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Action |
||||||
|
className={cn(buttonVariants(), className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDialogCancel({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { |
||||||
|
return ( |
||||||
|
<AlertDialogPrimitive.Cancel |
||||||
|
className={cn(buttonVariants({ variant: "outline" }), className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
AlertDialog, |
||||||
|
AlertDialogPortal, |
||||||
|
AlertDialogOverlay, |
||||||
|
AlertDialogTrigger, |
||||||
|
AlertDialogContent, |
||||||
|
AlertDialogHeader, |
||||||
|
AlertDialogFooter, |
||||||
|
AlertDialogTitle, |
||||||
|
AlertDialogDescription, |
||||||
|
AlertDialogAction, |
||||||
|
AlertDialogCancel, |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
import * as React from "react" |
||||||
|
import { cva, type VariantProps } from "class-variance-authority" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const alertVariants = cva( |
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", |
||||||
|
{ |
||||||
|
variants: { |
||||||
|
variant: { |
||||||
|
default: "bg-card text-card-foreground", |
||||||
|
destructive: |
||||||
|
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", |
||||||
|
}, |
||||||
|
}, |
||||||
|
defaultVariants: { |
||||||
|
variant: "default", |
||||||
|
}, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
function Alert({ |
||||||
|
className, |
||||||
|
variant, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
data-slot="alert" |
||||||
|
role="alert" |
||||||
|
className={cn(alertVariants({ variant }), className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
data-slot="alert-title" |
||||||
|
className={cn( |
||||||
|
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function AlertDescription({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<"div">) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
data-slot="alert-description" |
||||||
|
className={cn( |
||||||
|
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription } |
@ -0,0 +1,46 @@ |
|||||||
|
import * as React from "react" |
||||||
|
import { Slot } from "@radix-ui/react-slot" |
||||||
|
import { cva, type VariantProps } from "class-variance-authority" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
const badgeVariants = cva( |
||||||
|
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", |
||||||
|
{ |
||||||
|
variants: { |
||||||
|
variant: { |
||||||
|
default: |
||||||
|
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", |
||||||
|
secondary: |
||||||
|
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", |
||||||
|
destructive: |
||||||
|
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", |
||||||
|
outline: |
||||||
|
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", |
||||||
|
}, |
||||||
|
}, |
||||||
|
defaultVariants: { |
||||||
|
variant: "default", |
||||||
|
}, |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
function Badge({ |
||||||
|
className, |
||||||
|
variant, |
||||||
|
asChild = false, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<"span"> & |
||||||
|
VariantProps<typeof badgeVariants> & { asChild?: boolean }) { |
||||||
|
const Comp = asChild ? Slot : "span" |
||||||
|
|
||||||
|
return ( |
||||||
|
<Comp |
||||||
|
data-slot="badge" |
||||||
|
className={cn(badgeVariants({ variant }), className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export { Badge, badgeVariants } |
@ -0,0 +1,213 @@ |
|||||||
|
"use client" |
||||||
|
|
||||||
|
import * as React from "react" |
||||||
|
import { |
||||||
|
ChevronDownIcon, |
||||||
|
ChevronLeftIcon, |
||||||
|
ChevronRightIcon, |
||||||
|
} from "lucide-react" |
||||||
|
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
import { Button, buttonVariants } from "@/components/ui/button" |
||||||
|
|
||||||
|
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( |
||||||
|
"bg-background group/calendar 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( |
||||||
|
"flex gap-4 flex-col md:flex-row relative", |
||||||
|
defaultClassNames.months |
||||||
|
), |
||||||
|
month: cn("flex flex-col w-full gap-4", defaultClassNames.month), |
||||||
|
nav: cn( |
||||||
|
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", |
||||||
|
defaultClassNames.nav |
||||||
|
), |
||||||
|
button_previous: cn( |
||||||
|
buttonVariants({ variant: buttonVariant }), |
||||||
|
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", |
||||||
|
defaultClassNames.button_previous |
||||||
|
), |
||||||
|
button_next: cn( |
||||||
|
buttonVariants({ variant: buttonVariant }), |
||||||
|
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", |
||||||
|
defaultClassNames.button_next |
||||||
|
), |
||||||
|
month_caption: cn( |
||||||
|
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", |
||||||
|
defaultClassNames.month_caption |
||||||
|
), |
||||||
|
dropdowns: cn( |
||||||
|
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", |
||||||
|
defaultClassNames.dropdowns |
||||||
|
), |
||||||
|
dropdown_root: cn( |
||||||
|
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", |
||||||
|
defaultClassNames.dropdown_root |
||||||
|
), |
||||||
|
dropdown: cn( |
||||||
|
"absolute bg-popover inset-0 opacity-0", |
||||||
|
defaultClassNames.dropdown |
||||||
|
), |
||||||
|
caption_label: cn( |
||||||
|
"select-none font-medium", |
||||||
|
captionLayout === "label" |
||||||
|
? "text-sm" |
||||||
|
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", |
||||||
|
defaultClassNames.caption_label |
||||||
|
), |
||||||
|
table: "w-full border-collapse", |
||||||
|
weekdays: cn("flex", defaultClassNames.weekdays), |
||||||
|
weekday: cn( |
||||||
|
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", |
||||||
|
defaultClassNames.weekday |
||||||
|
), |
||||||
|
week: cn("flex w-full mt-2", defaultClassNames.week), |
||||||
|
week_number_header: cn( |
||||||
|
"select-none w-(--cell-size)", |
||||||
|
defaultClassNames.week_number_header |
||||||
|
), |
||||||
|
week_number: cn( |
||||||
|
"text-[0.8rem] select-none text-muted-foreground", |
||||||
|
defaultClassNames.week_number |
||||||
|
), |
||||||
|
day: cn( |
||||||
|
"relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", |
||||||
|
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( |
||||||
|
"bg-accent text-accent-foreground rounded-md 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 |
||||||
|
ref={ref} |
||||||
|
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( |
||||||
|
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground 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:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", |
||||||
|
defaultClassNames.day, |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export { Calendar, CalendarDayButton } |
@ -0,0 +1,168 @@ |
|||||||
|
import * as React from "react" |
||||||
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" |
||||||
|
import { cva } from "class-variance-authority" |
||||||
|
import { ChevronDownIcon } from "lucide-react" |
||||||
|
|
||||||
|
import { cn } from "@/lib/utils" |
||||||
|
|
||||||
|
function NavigationMenu({ |
||||||
|
className, |
||||||
|
children, |
||||||
|
viewport = true, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & { |
||||||
|
viewport?: boolean |
||||||
|
}) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.Root |
||||||
|
data-slot="navigation-menu" |
||||||
|
data-viewport={viewport} |
||||||
|
className={cn( |
||||||
|
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{children} |
||||||
|
{viewport && <NavigationMenuViewport />} |
||||||
|
</NavigationMenuPrimitive.Root> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function NavigationMenuList({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.List |
||||||
|
data-slot="navigation-menu-list" |
||||||
|
className={cn( |
||||||
|
"group flex flex-1 list-none items-center justify-center gap-1", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function NavigationMenuItem({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.Item |
||||||
|
data-slot="navigation-menu-item" |
||||||
|
className={cn("relative", className)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const navigationMenuTriggerStyle = cva( |
||||||
|
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1" |
||||||
|
) |
||||||
|
|
||||||
|
function NavigationMenuTrigger({ |
||||||
|
className, |
||||||
|
children, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.Trigger |
||||||
|
data-slot="navigation-menu-trigger" |
||||||
|
className={cn(navigationMenuTriggerStyle(), "group", className)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
{children}{" "} |
||||||
|
<ChevronDownIcon |
||||||
|
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</NavigationMenuPrimitive.Trigger> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function NavigationMenuContent({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.Content |
||||||
|
data-slot="navigation-menu-content" |
||||||
|
className={cn( |
||||||
|
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto", |
||||||
|
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function NavigationMenuViewport({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={cn( |
||||||
|
"absolute top-full left-0 isolate z-50 flex justify-center" |
||||||
|
)} |
||||||
|
> |
||||||
|
<NavigationMenuPrimitive.Viewport |
||||||
|
data-slot="navigation-menu-viewport" |
||||||
|
className={cn( |
||||||
|
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function NavigationMenuLink({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.Link |
||||||
|
data-slot="navigation-menu-link" |
||||||
|
className={cn( |
||||||
|
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function NavigationMenuIndicator({ |
||||||
|
className, |
||||||
|
...props |
||||||
|
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) { |
||||||
|
return ( |
||||||
|
<NavigationMenuPrimitive.Indicator |
||||||
|
data-slot="navigation-menu-indicator" |
||||||
|
className={cn( |
||||||
|
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...props} |
||||||
|
> |
||||||
|
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" /> |
||||||
|
</NavigationMenuPrimitive.Indicator> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
NavigationMenu, |
||||||
|
NavigationMenuList, |
||||||
|
NavigationMenuItem, |
||||||
|
NavigationMenuContent, |
||||||
|
NavigationMenuTrigger, |
||||||
|
NavigationMenuLink, |
||||||
|
NavigationMenuIndicator, |
||||||
|
NavigationMenuViewport, |
||||||
|
navigationMenuTriggerStyle, |
||||||
|
} |
Loading…
Reference in new issue