@mzc-fe/design-system 0.0.5 → 0.0.7-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/accordion/accordion.tsx +114 -0
- package/components/accordion/index.ts +1 -0
- package/components/alert/alert.tsx +97 -0
- package/components/alert/index.ts +1 -0
- package/components/alert-dialog/alert-dialog.tsx +190 -0
- package/components/alert-dialog/index.ts +1 -0
- package/components/aspect-ratio/aspect-ratio.tsx +23 -0
- package/components/aspect-ratio/index.ts +1 -0
- package/components/avatar/avatar.tsx +62 -0
- package/components/avatar/index.ts +1 -0
- package/components/badge/badge.tsx +58 -0
- package/components/badge/index.ts +1 -0
- package/components/breadcrumb/breadcrumb.tsx +132 -0
- package/components/breadcrumb/index.ts +1 -0
- package/components/button/button.tsx +77 -0
- package/components/button/index.ts +1 -0
- package/components/button-group/button-group.tsx +99 -0
- package/components/button-group/index.ts +1 -0
- package/components/calendar/calendar.tsx +235 -0
- package/components/calendar/index.ts +1 -0
- package/components/card/card.tsx +107 -0
- package/components/card/index.ts +1 -0
- package/components/carousel/carousel.tsx +263 -0
- package/components/carousel/index.ts +1 -0
- package/components/chart/chart.tsx +377 -0
- package/components/chart/index.ts +1 -0
- package/components/checkbox/checkbox.tsx +41 -0
- package/components/checkbox/index.ts +1 -0
- package/components/collapsible/collapsible.tsx +44 -0
- package/components/collapsible/index.ts +1 -0
- package/components/command/command.tsx +201 -0
- package/components/command/index.ts +1 -0
- package/components/context-menu/context-menu.tsx +270 -0
- package/components/context-menu/index.ts +1 -0
- package/components/dialog/dialog.tsx +166 -0
- package/components/dialog/index.ts +1 -0
- package/components/drawer/drawer.tsx +154 -0
- package/components/drawer/index.ts +1 -0
- package/components/dropdown-menu/dropdown-menu.tsx +276 -0
- package/components/dropdown-menu/index.ts +1 -0
- package/components/empty/empty.tsx +129 -0
- package/components/empty/index.ts +1 -0
- package/components/field/field.tsx +272 -0
- package/components/field/index.ts +1 -0
- package/components/form/form.tsx +197 -0
- package/components/form/index.ts +1 -0
- package/components/hover-card/hover-card.tsx +57 -0
- package/components/hover-card/index.ts +1 -0
- package/components/input/index.ts +1 -0
- package/components/input/input.tsx +31 -0
- package/components/input-group/index.ts +1 -0
- package/components/input-group/input-group.tsx +189 -0
- package/components/input-otp/index.ts +1 -0
- package/components/input-otp/input-otp.tsx +99 -0
- package/components/item/index.ts +1 -0
- package/components/item/item.tsx +225 -0
- package/components/kbd/index.ts +1 -0
- package/components/kbd/kbd.tsx +38 -0
- package/components/label/index.ts +1 -0
- package/components/label/label.tsx +33 -0
- package/components/menubar/index.ts +1 -0
- package/components/menubar/menubar.tsx +299 -0
- package/components/navigation-menu/index.ts +1 -0
- package/components/navigation-menu/navigation-menu.tsx +194 -0
- package/components/pagination/index.ts +1 -0
- package/components/pagination/pagination.tsx +153 -0
- package/components/popover/index.ts +1 -0
- package/components/popover/popover.tsx +106 -0
- package/components/progress/index.ts +1 -0
- package/components/progress/progress.tsx +39 -0
- package/components/radio-group/index.ts +1 -0
- package/components/radio-group/radio-group.tsx +57 -0
- package/components/resizable/index.ts +1 -0
- package/components/resizable/resizable.tsx +73 -0
- package/components/scroll-area/index.ts +1 -0
- package/components/scroll-area/scroll-area.tsx +72 -0
- package/components/select/index.ts +1 -0
- package/components/select/select.tsx +213 -0
- package/components/separator/index.ts +1 -0
- package/components/separator/separator.tsx +39 -0
- package/components/sheet/index.ts +1 -0
- package/components/sheet/sheet.tsx +160 -0
- package/components/sidebar/index.ts +1 -0
- package/components/sidebar/sidebar.tsx +776 -0
- package/components/skeleton/index.ts +1 -0
- package/components/skeleton/skeleton.tsx +21 -0
- package/components/slider/index.ts +1 -0
- package/components/slider/slider.tsx +75 -0
- package/components/sonner/index.ts +2 -0
- package/components/sonner/sonner.tsx +52 -0
- package/components/spinner/index.ts +1 -0
- package/components/spinner/spinner.tsx +26 -0
- package/components/switch/index.ts +1 -0
- package/components/switch/switch.tsx +39 -0
- package/components/table/index.ts +1 -0
- package/components/table/table.tsx +140 -0
- package/components/tabs/index.ts +1 -0
- package/components/tabs/tabs.tsx +94 -0
- package/components/textarea/index.ts +1 -0
- package/components/textarea/textarea.tsx +26 -0
- package/components/toggle/index.ts +1 -0
- package/components/toggle/toggle.tsx +58 -0
- package/components/toggle-group/index.ts +1 -0
- package/components/toggle-group/toggle-group.tsx +97 -0
- package/components/tooltip/index.ts +1 -0
- package/components/tooltip/tooltip.tsx +82 -0
- package/dist/components/accordion/accordion.d.ts +50 -0
- package/dist/components/alert/alert.d.ts +31 -0
- package/dist/components/alert-dialog/alert-dialog.d.ts +35 -0
- package/dist/components/aspect-ratio/aspect-ratio.d.ts +12 -0
- package/dist/components/avatar/avatar.d.ts +11 -0
- package/dist/components/badge/badge.d.ts +12 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts +23 -0
- package/dist/components/button/button.d.ts +15 -0
- package/dist/components/button-group/button-group.d.ts +16 -0
- package/dist/components/calendar/calendar.d.ts +15 -0
- package/dist/components/card/card.d.ts +15 -0
- package/dist/components/carousel/carousel.d.ts +24 -0
- package/dist/components/chart/chart.d.ts +20 -0
- package/dist/components/checkbox/checkbox.d.ts +9 -0
- package/dist/components/collapsible/collapsible.d.ts +13 -0
- package/dist/components/command/command.d.ts +18 -0
- package/dist/components/context-menu/context-menu.d.ts +18 -0
- package/dist/components/dialog/dialog.d.ts +25 -0
- package/dist/components/drawer/drawer.d.ts +18 -0
- package/dist/components/dropdown-menu/dropdown-menu.d.ts +21 -0
- package/dist/components/empty/empty.d.ts +25 -0
- package/dist/components/field/field.d.ts +26 -0
- package/dist/components/form/form.d.ts +30 -1
- package/dist/components/hover-card/hover-card.d.ts +13 -0
- package/dist/components/input/input.d.ts +10 -0
- package/dist/components/input-group/input-group.d.ts +19 -0
- package/dist/components/input-otp/input-otp.d.ts +23 -0
- package/dist/components/item/item.d.ts +33 -1
- package/dist/components/kbd/kbd.d.ts +10 -0
- package/dist/components/label/label.d.ts +9 -0
- package/dist/components/menubar/menubar.d.ts +25 -0
- package/dist/components/navigation-menu/navigation-menu.d.ts +26 -0
- package/dist/components/pagination/pagination.d.ts +26 -0
- package/dist/components/popover/popover.d.ts +17 -0
- package/dist/components/progress/progress.d.ts +10 -0
- package/dist/components/radio-group/radio-group.d.ts +12 -0
- package/dist/components/resizable/resizable.d.ts +19 -0
- package/dist/components/scroll-area/scroll-area.d.ts +14 -0
- package/dist/components/select/select.d.ts +25 -0
- package/dist/components/separator/separator.d.ts +11 -0
- package/dist/components/sheet/sheet.d.ts +23 -0
- package/dist/components/sidebar/sidebar.d.ts +50 -0
- package/dist/components/skeleton/skeleton.d.ts +8 -0
- package/dist/components/slider/slider.d.ts +12 -0
- package/dist/components/sonner/sonner.d.ts +14 -0
- package/dist/components/spinner/spinner.d.ts +9 -0
- package/dist/components/switch/switch.d.ts +8 -0
- package/dist/components/table/table.d.ts +26 -0
- package/dist/components/tabs/tabs.d.ts +16 -6
- package/dist/components/textarea/textarea.d.ts +8 -0
- package/dist/components/toggle/toggle.d.ts +13 -0
- package/dist/components/toggle-group/toggle-group.d.ts +1 -0
- package/dist/components/tooltip/tooltip.d.ts +21 -0
- package/dist/design-system.css +1 -1
- package/dist/design-system.es.js +3493 -28470
- package/dist/design-system.umd.js +4 -257
- package/dist/index.d.ts +1 -1
- package/foundations/ThemeProvider.tsx +77 -0
- package/foundations/color.css +232 -0
- package/foundations/palette.css +249 -0
- package/foundations/spacing.css +8 -0
- package/foundations/typography.css +143 -0
- package/hooks/use-mobile.ts +19 -0
- package/index.css +173 -0
- package/index.ts +339 -0
- package/lib/utils.ts +6 -0
- package/package.json +40 -19
- package/README.md +0 -184
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Drawer as DrawerPrimitive } from "vaul"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 화면 하단에서 슬라이드업되는 모바일 친화적 드로어 컴포넌트입니다.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Drawer>
|
|
14
|
+
* <DrawerTrigger>열기</DrawerTrigger>
|
|
15
|
+
* <DrawerContent>
|
|
16
|
+
* <DrawerHeader>
|
|
17
|
+
* <DrawerTitle>제목</DrawerTitle>
|
|
18
|
+
* </DrawerHeader>
|
|
19
|
+
* </DrawerContent>
|
|
20
|
+
* </Drawer>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function Drawer({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
|
26
|
+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function DrawerTrigger({
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
|
32
|
+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function DrawerPortal({
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
|
38
|
+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function DrawerClose({
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
|
44
|
+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function DrawerOverlay({
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
|
51
|
+
return (
|
|
52
|
+
<DrawerPrimitive.Overlay
|
|
53
|
+
data-slot="drawer-overlay"
|
|
54
|
+
className={cn(
|
|
55
|
+
"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",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function DrawerContent({
|
|
64
|
+
className,
|
|
65
|
+
children,
|
|
66
|
+
...props
|
|
67
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
|
68
|
+
return (
|
|
69
|
+
// @ts-expect-error - React 18/19 호환성: DrawerPortal children prop
|
|
70
|
+
<DrawerPortal data-slot="drawer-portal">
|
|
71
|
+
<DrawerOverlay />
|
|
72
|
+
<DrawerPrimitive.Content
|
|
73
|
+
data-slot="drawer-content"
|
|
74
|
+
className={cn(
|
|
75
|
+
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
|
76
|
+
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
|
77
|
+
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
|
78
|
+
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
|
79
|
+
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
|
85
|
+
{children}
|
|
86
|
+
</DrawerPrimitive.Content>
|
|
87
|
+
</DrawerPortal>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 드로어 헤더 영역입니다. */
|
|
92
|
+
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
data-slot="drawer-header"
|
|
96
|
+
className={cn(
|
|
97
|
+
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
|
|
98
|
+
className
|
|
99
|
+
)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 드로어 푸터 영역입니다. */
|
|
106
|
+
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
data-slot="drawer-footer"
|
|
110
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
111
|
+
{...props}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 드로어 제목 컴포넌트입니다. */
|
|
117
|
+
function DrawerTitle({
|
|
118
|
+
className,
|
|
119
|
+
...props
|
|
120
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
|
121
|
+
return (
|
|
122
|
+
<DrawerPrimitive.Title
|
|
123
|
+
data-slot="drawer-title"
|
|
124
|
+
className={cn("text-foreground font-semibold", className)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function DrawerDescription({
|
|
131
|
+
className,
|
|
132
|
+
...props
|
|
133
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
|
134
|
+
return (
|
|
135
|
+
<DrawerPrimitive.Description
|
|
136
|
+
data-slot="drawer-description"
|
|
137
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
138
|
+
{...props}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
Drawer,
|
|
145
|
+
DrawerPortal,
|
|
146
|
+
DrawerOverlay,
|
|
147
|
+
DrawerTrigger,
|
|
148
|
+
DrawerClose,
|
|
149
|
+
DrawerContent,
|
|
150
|
+
DrawerHeader,
|
|
151
|
+
DrawerFooter,
|
|
152
|
+
DrawerTitle,
|
|
153
|
+
DrawerDescription,
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./drawer";
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
3
|
+
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 트리거를 클릭하면 표시되는 드롭다운 메뉴 컴포넌트입니다.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <DropdownMenu>
|
|
13
|
+
* <DropdownMenuTrigger>메뉴</DropdownMenuTrigger>
|
|
14
|
+
* <DropdownMenuContent>
|
|
15
|
+
* <DropdownMenuItem>항목 1</DropdownMenuItem>
|
|
16
|
+
* <DropdownMenuItem>항목 2</DropdownMenuItem>
|
|
17
|
+
* </DropdownMenuContent>
|
|
18
|
+
* </DropdownMenu>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function DropdownMenu({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
|
24
|
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function DropdownMenuPortal({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
|
30
|
+
return (
|
|
31
|
+
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function DropdownMenuTrigger({
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
38
|
+
return (
|
|
39
|
+
<DropdownMenuPrimitive.Trigger
|
|
40
|
+
data-slot="dropdown-menu-trigger"
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function DropdownMenuContent({
|
|
47
|
+
className,
|
|
48
|
+
sideOffset = 4,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
|
51
|
+
return (
|
|
52
|
+
<DropdownMenuPrimitive.Portal>
|
|
53
|
+
<DropdownMenuPrimitive.Content
|
|
54
|
+
data-slot="dropdown-menu-content"
|
|
55
|
+
sideOffset={sideOffset}
|
|
56
|
+
className={cn(
|
|
57
|
+
"bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
</DropdownMenuPrimitive.Portal>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function DropdownMenuGroup({
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
|
69
|
+
return (
|
|
70
|
+
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 드롭다운 메뉴 아이템입니다.
|
|
76
|
+
* @param props.inset - 왼쪽 여백 추가 여부
|
|
77
|
+
* @param props.variant - 스타일 변형 ('default' | 'destructive')
|
|
78
|
+
*/
|
|
79
|
+
function DropdownMenuItem({
|
|
80
|
+
className,
|
|
81
|
+
inset,
|
|
82
|
+
variant = "default",
|
|
83
|
+
...props
|
|
84
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
85
|
+
inset?: boolean
|
|
86
|
+
variant?: "default" | "destructive"
|
|
87
|
+
}) {
|
|
88
|
+
return (
|
|
89
|
+
<DropdownMenuPrimitive.Item
|
|
90
|
+
data-slot="dropdown-menu-item"
|
|
91
|
+
data-inset={inset}
|
|
92
|
+
data-variant={variant}
|
|
93
|
+
className={cn(
|
|
94
|
+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 체크박스 형태의 메뉴 아이템입니다. */
|
|
103
|
+
function DropdownMenuCheckboxItem({
|
|
104
|
+
className,
|
|
105
|
+
children,
|
|
106
|
+
checked,
|
|
107
|
+
...props
|
|
108
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
|
109
|
+
return (
|
|
110
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
111
|
+
data-slot="dropdown-menu-checkbox-item"
|
|
112
|
+
className={cn(
|
|
113
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
114
|
+
className
|
|
115
|
+
)}
|
|
116
|
+
checked={checked}
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
120
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
121
|
+
<CheckIcon className="size-4" />
|
|
122
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
123
|
+
</span>
|
|
124
|
+
{children}
|
|
125
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function DropdownMenuRadioGroup({
|
|
130
|
+
...props
|
|
131
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
|
132
|
+
return (
|
|
133
|
+
<DropdownMenuPrimitive.RadioGroup
|
|
134
|
+
data-slot="dropdown-menu-radio-group"
|
|
135
|
+
{...props}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** 라디오 버튼 형태의 메뉴 아이템입니다. */
|
|
141
|
+
function DropdownMenuRadioItem({
|
|
142
|
+
className,
|
|
143
|
+
children,
|
|
144
|
+
...props
|
|
145
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
|
146
|
+
return (
|
|
147
|
+
<DropdownMenuPrimitive.RadioItem
|
|
148
|
+
data-slot="dropdown-menu-radio-item"
|
|
149
|
+
className={cn(
|
|
150
|
+
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
151
|
+
className
|
|
152
|
+
)}
|
|
153
|
+
{...props}
|
|
154
|
+
>
|
|
155
|
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
|
156
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
157
|
+
<CircleIcon className="size-2 fill-current" />
|
|
158
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
159
|
+
</span>
|
|
160
|
+
{children}
|
|
161
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function DropdownMenuLabel({
|
|
166
|
+
className,
|
|
167
|
+
inset,
|
|
168
|
+
...props
|
|
169
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
170
|
+
inset?: boolean
|
|
171
|
+
}) {
|
|
172
|
+
return (
|
|
173
|
+
<DropdownMenuPrimitive.Label
|
|
174
|
+
data-slot="dropdown-menu-label"
|
|
175
|
+
data-inset={inset}
|
|
176
|
+
className={cn(
|
|
177
|
+
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
|
178
|
+
className
|
|
179
|
+
)}
|
|
180
|
+
{...props}
|
|
181
|
+
/>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function DropdownMenuSeparator({
|
|
186
|
+
className,
|
|
187
|
+
...props
|
|
188
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
|
189
|
+
return (
|
|
190
|
+
<DropdownMenuPrimitive.Separator
|
|
191
|
+
data-slot="dropdown-menu-separator"
|
|
192
|
+
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
|
193
|
+
{...props}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function DropdownMenuShortcut({
|
|
199
|
+
className,
|
|
200
|
+
...props
|
|
201
|
+
}: React.ComponentProps<"span">) {
|
|
202
|
+
return (
|
|
203
|
+
<span
|
|
204
|
+
data-slot="dropdown-menu-shortcut"
|
|
205
|
+
className={cn(
|
|
206
|
+
"text-muted-foreground ml-auto text-xs tracking-widest",
|
|
207
|
+
className
|
|
208
|
+
)}
|
|
209
|
+
{...props}
|
|
210
|
+
/>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function DropdownMenuSub({
|
|
215
|
+
...props
|
|
216
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
|
217
|
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function DropdownMenuSubTrigger({
|
|
221
|
+
className,
|
|
222
|
+
inset,
|
|
223
|
+
children,
|
|
224
|
+
...props
|
|
225
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
226
|
+
inset?: boolean
|
|
227
|
+
}) {
|
|
228
|
+
return (
|
|
229
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
230
|
+
data-slot="dropdown-menu-sub-trigger"
|
|
231
|
+
data-inset={inset}
|
|
232
|
+
className={cn(
|
|
233
|
+
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
234
|
+
className
|
|
235
|
+
)}
|
|
236
|
+
{...props}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
<ChevronRightIcon className="ml-auto size-4" />
|
|
240
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function DropdownMenuSubContent({
|
|
245
|
+
className,
|
|
246
|
+
...props
|
|
247
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
|
248
|
+
return (
|
|
249
|
+
<DropdownMenuPrimitive.SubContent
|
|
250
|
+
data-slot="dropdown-menu-sub-content"
|
|
251
|
+
className={cn(
|
|
252
|
+
"bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
|
253
|
+
className
|
|
254
|
+
)}
|
|
255
|
+
{...props}
|
|
256
|
+
/>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export {
|
|
261
|
+
DropdownMenu,
|
|
262
|
+
DropdownMenuPortal,
|
|
263
|
+
DropdownMenuTrigger,
|
|
264
|
+
DropdownMenuContent,
|
|
265
|
+
DropdownMenuGroup,
|
|
266
|
+
DropdownMenuLabel,
|
|
267
|
+
DropdownMenuItem,
|
|
268
|
+
DropdownMenuCheckboxItem,
|
|
269
|
+
DropdownMenuRadioGroup,
|
|
270
|
+
DropdownMenuRadioItem,
|
|
271
|
+
DropdownMenuSeparator,
|
|
272
|
+
DropdownMenuShortcut,
|
|
273
|
+
DropdownMenuSub,
|
|
274
|
+
DropdownMenuSubTrigger,
|
|
275
|
+
DropdownMenuSubContent,
|
|
276
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./dropdown-menu";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 콘텐츠가 없을 때 빈 상태를 표시하는 컴포넌트입니다.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <Empty>
|
|
11
|
+
* <EmptyHeader>
|
|
12
|
+
* <EmptyMedia><InboxIcon /></EmptyMedia>
|
|
13
|
+
* <EmptyTitle>항목이 없습니다</EmptyTitle>
|
|
14
|
+
* <EmptyDescription>새 항목을 추가해보세요</EmptyDescription>
|
|
15
|
+
* </EmptyHeader>
|
|
16
|
+
* <EmptyContent>
|
|
17
|
+
* <Button>항목 추가</Button>
|
|
18
|
+
* </EmptyContent>
|
|
19
|
+
* </Empty>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
data-slot="empty"
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Empty 컴포넌트의 헤더 영역입니다. */
|
|
36
|
+
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
data-slot="empty-header"
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const emptyMediaVariants = cva(
|
|
50
|
+
"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
51
|
+
{
|
|
52
|
+
variants: {
|
|
53
|
+
variant: {
|
|
54
|
+
default: "bg-transparent",
|
|
55
|
+
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
defaultVariants: {
|
|
59
|
+
variant: "default",
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Empty 컴포넌트의 미디어(아이콘/이미지) 영역입니다.
|
|
66
|
+
* @param props.variant - 스타일 변형 ('default' | 'icon')
|
|
67
|
+
*/
|
|
68
|
+
function EmptyMedia({
|
|
69
|
+
className,
|
|
70
|
+
variant = "default",
|
|
71
|
+
...props
|
|
72
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="empty-icon"
|
|
76
|
+
data-variant={variant}
|
|
77
|
+
className={cn(emptyMediaVariants({ variant, className }))}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Empty 컴포넌트의 제목입니다. */
|
|
84
|
+
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="empty-title"
|
|
88
|
+
className={cn("text-lg font-medium tracking-tight", className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Empty 컴포넌트의 설명 텍스트입니다. */
|
|
95
|
+
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
data-slot="empty-description"
|
|
99
|
+
className={cn(
|
|
100
|
+
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
|
|
101
|
+
className
|
|
102
|
+
)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Empty 컴포넌트의 액션 영역입니다. */
|
|
109
|
+
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
data-slot="empty-content"
|
|
113
|
+
className={cn(
|
|
114
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
|
115
|
+
className
|
|
116
|
+
)}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
Empty,
|
|
124
|
+
EmptyHeader,
|
|
125
|
+
EmptyTitle,
|
|
126
|
+
EmptyDescription,
|
|
127
|
+
EmptyContent,
|
|
128
|
+
EmptyMedia,
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./empty";
|