@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.
Files changed (174) hide show
  1. package/components/accordion/accordion.tsx +114 -0
  2. package/components/accordion/index.ts +1 -0
  3. package/components/alert/alert.tsx +97 -0
  4. package/components/alert/index.ts +1 -0
  5. package/components/alert-dialog/alert-dialog.tsx +190 -0
  6. package/components/alert-dialog/index.ts +1 -0
  7. package/components/aspect-ratio/aspect-ratio.tsx +23 -0
  8. package/components/aspect-ratio/index.ts +1 -0
  9. package/components/avatar/avatar.tsx +62 -0
  10. package/components/avatar/index.ts +1 -0
  11. package/components/badge/badge.tsx +58 -0
  12. package/components/badge/index.ts +1 -0
  13. package/components/breadcrumb/breadcrumb.tsx +132 -0
  14. package/components/breadcrumb/index.ts +1 -0
  15. package/components/button/button.tsx +77 -0
  16. package/components/button/index.ts +1 -0
  17. package/components/button-group/button-group.tsx +99 -0
  18. package/components/button-group/index.ts +1 -0
  19. package/components/calendar/calendar.tsx +235 -0
  20. package/components/calendar/index.ts +1 -0
  21. package/components/card/card.tsx +107 -0
  22. package/components/card/index.ts +1 -0
  23. package/components/carousel/carousel.tsx +263 -0
  24. package/components/carousel/index.ts +1 -0
  25. package/components/chart/chart.tsx +377 -0
  26. package/components/chart/index.ts +1 -0
  27. package/components/checkbox/checkbox.tsx +41 -0
  28. package/components/checkbox/index.ts +1 -0
  29. package/components/collapsible/collapsible.tsx +44 -0
  30. package/components/collapsible/index.ts +1 -0
  31. package/components/command/command.tsx +201 -0
  32. package/components/command/index.ts +1 -0
  33. package/components/context-menu/context-menu.tsx +270 -0
  34. package/components/context-menu/index.ts +1 -0
  35. package/components/dialog/dialog.tsx +166 -0
  36. package/components/dialog/index.ts +1 -0
  37. package/components/drawer/drawer.tsx +154 -0
  38. package/components/drawer/index.ts +1 -0
  39. package/components/dropdown-menu/dropdown-menu.tsx +276 -0
  40. package/components/dropdown-menu/index.ts +1 -0
  41. package/components/empty/empty.tsx +129 -0
  42. package/components/empty/index.ts +1 -0
  43. package/components/field/field.tsx +272 -0
  44. package/components/field/index.ts +1 -0
  45. package/components/form/form.tsx +197 -0
  46. package/components/form/index.ts +1 -0
  47. package/components/hover-card/hover-card.tsx +57 -0
  48. package/components/hover-card/index.ts +1 -0
  49. package/components/input/index.ts +1 -0
  50. package/components/input/input.tsx +31 -0
  51. package/components/input-group/index.ts +1 -0
  52. package/components/input-group/input-group.tsx +189 -0
  53. package/components/input-otp/index.ts +1 -0
  54. package/components/input-otp/input-otp.tsx +99 -0
  55. package/components/item/index.ts +1 -0
  56. package/components/item/item.tsx +225 -0
  57. package/components/kbd/index.ts +1 -0
  58. package/components/kbd/kbd.tsx +38 -0
  59. package/components/label/index.ts +1 -0
  60. package/components/label/label.tsx +33 -0
  61. package/components/menubar/index.ts +1 -0
  62. package/components/menubar/menubar.tsx +299 -0
  63. package/components/navigation-menu/index.ts +1 -0
  64. package/components/navigation-menu/navigation-menu.tsx +194 -0
  65. package/components/pagination/index.ts +1 -0
  66. package/components/pagination/pagination.tsx +153 -0
  67. package/components/popover/index.ts +1 -0
  68. package/components/popover/popover.tsx +106 -0
  69. package/components/progress/index.ts +1 -0
  70. package/components/progress/progress.tsx +39 -0
  71. package/components/radio-group/index.ts +1 -0
  72. package/components/radio-group/radio-group.tsx +57 -0
  73. package/components/resizable/index.ts +1 -0
  74. package/components/resizable/resizable.tsx +73 -0
  75. package/components/scroll-area/index.ts +1 -0
  76. package/components/scroll-area/scroll-area.tsx +72 -0
  77. package/components/select/index.ts +1 -0
  78. package/components/select/select.tsx +213 -0
  79. package/components/separator/index.ts +1 -0
  80. package/components/separator/separator.tsx +39 -0
  81. package/components/sheet/index.ts +1 -0
  82. package/components/sheet/sheet.tsx +160 -0
  83. package/components/sidebar/index.ts +1 -0
  84. package/components/sidebar/sidebar.tsx +776 -0
  85. package/components/skeleton/index.ts +1 -0
  86. package/components/skeleton/skeleton.tsx +21 -0
  87. package/components/slider/index.ts +1 -0
  88. package/components/slider/slider.tsx +75 -0
  89. package/components/sonner/index.ts +2 -0
  90. package/components/sonner/sonner.tsx +52 -0
  91. package/components/spinner/index.ts +1 -0
  92. package/components/spinner/spinner.tsx +26 -0
  93. package/components/switch/index.ts +1 -0
  94. package/components/switch/switch.tsx +39 -0
  95. package/components/table/index.ts +1 -0
  96. package/components/table/table.tsx +140 -0
  97. package/components/tabs/index.ts +1 -0
  98. package/components/tabs/tabs.tsx +94 -0
  99. package/components/textarea/index.ts +1 -0
  100. package/components/textarea/textarea.tsx +26 -0
  101. package/components/toggle/index.ts +1 -0
  102. package/components/toggle/toggle.tsx +58 -0
  103. package/components/toggle-group/index.ts +1 -0
  104. package/components/toggle-group/toggle-group.tsx +97 -0
  105. package/components/tooltip/index.ts +1 -0
  106. package/components/tooltip/tooltip.tsx +82 -0
  107. package/dist/components/accordion/accordion.d.ts +50 -0
  108. package/dist/components/alert/alert.d.ts +31 -0
  109. package/dist/components/alert-dialog/alert-dialog.d.ts +35 -0
  110. package/dist/components/aspect-ratio/aspect-ratio.d.ts +12 -0
  111. package/dist/components/avatar/avatar.d.ts +11 -0
  112. package/dist/components/badge/badge.d.ts +12 -0
  113. package/dist/components/breadcrumb/breadcrumb.d.ts +23 -0
  114. package/dist/components/button/button.d.ts +15 -0
  115. package/dist/components/button-group/button-group.d.ts +16 -0
  116. package/dist/components/calendar/calendar.d.ts +15 -0
  117. package/dist/components/card/card.d.ts +15 -0
  118. package/dist/components/carousel/carousel.d.ts +24 -0
  119. package/dist/components/chart/chart.d.ts +20 -0
  120. package/dist/components/checkbox/checkbox.d.ts +9 -0
  121. package/dist/components/collapsible/collapsible.d.ts +13 -0
  122. package/dist/components/command/command.d.ts +18 -0
  123. package/dist/components/context-menu/context-menu.d.ts +18 -0
  124. package/dist/components/dialog/dialog.d.ts +25 -0
  125. package/dist/components/drawer/drawer.d.ts +18 -0
  126. package/dist/components/dropdown-menu/dropdown-menu.d.ts +21 -0
  127. package/dist/components/empty/empty.d.ts +25 -0
  128. package/dist/components/field/field.d.ts +26 -0
  129. package/dist/components/form/form.d.ts +30 -1
  130. package/dist/components/hover-card/hover-card.d.ts +13 -0
  131. package/dist/components/input/input.d.ts +10 -0
  132. package/dist/components/input-group/input-group.d.ts +19 -0
  133. package/dist/components/input-otp/input-otp.d.ts +23 -0
  134. package/dist/components/item/item.d.ts +33 -1
  135. package/dist/components/kbd/kbd.d.ts +10 -0
  136. package/dist/components/label/label.d.ts +9 -0
  137. package/dist/components/menubar/menubar.d.ts +25 -0
  138. package/dist/components/navigation-menu/navigation-menu.d.ts +26 -0
  139. package/dist/components/pagination/pagination.d.ts +26 -0
  140. package/dist/components/popover/popover.d.ts +17 -0
  141. package/dist/components/progress/progress.d.ts +10 -0
  142. package/dist/components/radio-group/radio-group.d.ts +12 -0
  143. package/dist/components/resizable/resizable.d.ts +19 -0
  144. package/dist/components/scroll-area/scroll-area.d.ts +14 -0
  145. package/dist/components/select/select.d.ts +25 -0
  146. package/dist/components/separator/separator.d.ts +11 -0
  147. package/dist/components/sheet/sheet.d.ts +23 -0
  148. package/dist/components/sidebar/sidebar.d.ts +50 -0
  149. package/dist/components/skeleton/skeleton.d.ts +8 -0
  150. package/dist/components/slider/slider.d.ts +12 -0
  151. package/dist/components/sonner/sonner.d.ts +14 -0
  152. package/dist/components/spinner/spinner.d.ts +9 -0
  153. package/dist/components/switch/switch.d.ts +8 -0
  154. package/dist/components/table/table.d.ts +26 -0
  155. package/dist/components/tabs/tabs.d.ts +16 -6
  156. package/dist/components/textarea/textarea.d.ts +8 -0
  157. package/dist/components/toggle/toggle.d.ts +13 -0
  158. package/dist/components/toggle-group/toggle-group.d.ts +1 -0
  159. package/dist/components/tooltip/tooltip.d.ts +21 -0
  160. package/dist/design-system.css +1 -1
  161. package/dist/design-system.es.js +3493 -28470
  162. package/dist/design-system.umd.js +4 -257
  163. package/dist/index.d.ts +1 -1
  164. package/foundations/ThemeProvider.tsx +77 -0
  165. package/foundations/color.css +232 -0
  166. package/foundations/palette.css +249 -0
  167. package/foundations/spacing.css +8 -0
  168. package/foundations/typography.css +143 -0
  169. package/hooks/use-mobile.ts +19 -0
  170. package/index.css +173 -0
  171. package/index.ts +339 -0
  172. package/lib/utils.ts +6 -0
  173. package/package.json +40 -19
  174. package/README.md +0 -184
@@ -0,0 +1,189 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ import { cn } from "@/lib/utils";
7
+ import { Button } from "@/components/button";
8
+ import { Input } from "@/components/input";
9
+ import { Textarea } from "@/components/textarea";
10
+
11
+ /**
12
+ * 입력 필드와 관련 요소들을 그룹화하는 컴포넌트입니다.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <InputGroup>
17
+ * <InputGroupAddon align="inline-start">$</InputGroupAddon>
18
+ * <InputGroupInput placeholder="0.00" />
19
+ * </InputGroup>
20
+ * ```
21
+ */
22
+ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
23
+ return (
24
+ <div
25
+ data-slot="input-group"
26
+ role="group"
27
+ className={cn(
28
+ "group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
29
+ "h-9 min-w-0 has-[>textarea]:h-auto",
30
+
31
+ // Variants based on alignment.
32
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
33
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
34
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
35
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
36
+
37
+ // Focus state.
38
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
39
+
40
+ // Error state.
41
+ "has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
42
+
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ const inputGroupAddonVariants = cva(
51
+ "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
52
+ {
53
+ variants: {
54
+ align: {
55
+ "inline-start":
56
+ "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
57
+ "inline-end":
58
+ "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
59
+ "block-start":
60
+ "order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
61
+ "block-end":
62
+ "order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
63
+ },
64
+ },
65
+ defaultVariants: {
66
+ align: "inline-start",
67
+ },
68
+ }
69
+ );
70
+
71
+ /**
72
+ * 입력 그룹에 추가 요소(아이콘, 텍스트, 버튼 등)를 배치하는 컴포넌트입니다.
73
+ * @param props.align - 배치 위치 ('inline-start' | 'inline-end' | 'block-start' | 'block-end')
74
+ */
75
+ function InputGroupAddon({
76
+ className,
77
+ align = "inline-start",
78
+ ...props
79
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
80
+ return (
81
+ <div
82
+ role="group"
83
+ data-slot="input-group-addon"
84
+ data-align={align}
85
+ className={cn(inputGroupAddonVariants({ align }), className)}
86
+ onClick={(e) => {
87
+ if ((e.target as HTMLElement).closest("button")) {
88
+ return;
89
+ }
90
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
91
+ }}
92
+ {...props}
93
+ />
94
+ );
95
+ }
96
+
97
+ const inputGroupButtonVariants = cva(
98
+ "text-sm shadow-none flex gap-2 items-center",
99
+ {
100
+ variants: {
101
+ size: {
102
+ xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
103
+ sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
104
+ "icon-xs":
105
+ "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
106
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
107
+ },
108
+ },
109
+ defaultVariants: {
110
+ size: "xs",
111
+ },
112
+ }
113
+ );
114
+
115
+ /** 입력 그룹 내에서 사용하는 버튼 컴포넌트입니다. */
116
+ function InputGroupButton({
117
+ className,
118
+ type = "button",
119
+ variant = "ghost",
120
+ size = "xs",
121
+ ...props
122
+ }: Omit<React.ComponentProps<typeof Button>, "size"> &
123
+ VariantProps<typeof inputGroupButtonVariants>) {
124
+ return (
125
+ <Button
126
+ type={type}
127
+ data-size={size}
128
+ variant={variant}
129
+ className={cn(inputGroupButtonVariants({ size }), className)}
130
+ {...props}
131
+ />
132
+ );
133
+ }
134
+
135
+ /** 입력 그룹 내 텍스트 레이블 컴포넌트입니다. */
136
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
137
+ return (
138
+ <span
139
+ className={cn(
140
+ "text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
141
+ className
142
+ )}
143
+ {...props}
144
+ />
145
+ );
146
+ }
147
+
148
+ /** 입력 그룹용 입력 필드 컴포넌트입니다. */
149
+ function InputGroupInput({
150
+ className,
151
+ ...props
152
+ }: React.ComponentProps<"input">) {
153
+ return (
154
+ <Input
155
+ data-slot="input-group-control"
156
+ className={cn(
157
+ "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
158
+ className
159
+ )}
160
+ {...props}
161
+ />
162
+ );
163
+ }
164
+
165
+ /** 입력 그룹용 텍스트 영역 컴포넌트입니다. */
166
+ function InputGroupTextarea({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<"textarea">) {
170
+ return (
171
+ <Textarea
172
+ data-slot="input-group-control"
173
+ className={cn(
174
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
175
+ className
176
+ )}
177
+ {...props}
178
+ />
179
+ );
180
+ }
181
+
182
+ export {
183
+ InputGroup,
184
+ InputGroupAddon,
185
+ InputGroupButton,
186
+ InputGroupText,
187
+ InputGroupInput,
188
+ InputGroupTextarea,
189
+ };
@@ -0,0 +1 @@
1
+ export * from "./input-otp";
@@ -0,0 +1,99 @@
1
+ import * as React from "react";
2
+ import { OTPInput, OTPInputContext } from "input-otp";
3
+ import { MinusIcon } from "lucide-react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ /**
8
+ * OTP(일회용 비밀번호) 입력을 위한 컴포넌트입니다.
9
+ *
10
+ * @param props.maxLength - 최대 입력 자릿수
11
+ * @param props.containerClassName - 컨테이너 클래스명
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <InputOTP maxLength={6}>
16
+ * <InputOTPGroup>
17
+ * <InputOTPSlot index={0} />
18
+ * <InputOTPSlot index={1} />
19
+ * <InputOTPSlot index={2} />
20
+ * </InputOTPGroup>
21
+ * </InputOTP>
22
+ * ```
23
+ */
24
+ function InputOTP({
25
+ className,
26
+ containerClassName,
27
+ ...props
28
+ }: React.ComponentProps<typeof OTPInput> & {
29
+ containerClassName?: string;
30
+ }) {
31
+ return (
32
+ <OTPInput
33
+ data-slot="input-otp"
34
+ containerClassName={cn(
35
+ "flex items-center gap-2 has-disabled:opacity-50",
36
+ containerClassName
37
+ )}
38
+ className={cn("disabled:cursor-not-allowed", className)}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ /** OTP 슬롯들을 그룹화하는 컴포넌트입니다. */
45
+ function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
46
+ return (
47
+ <div
48
+ data-slot="input-otp-group"
49
+ className={cn("flex items-center", className)}
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+
55
+ /**
56
+ * 개별 OTP 입력 슬롯입니다.
57
+ * @param props.index - 슬롯 인덱스 (0부터 시작)
58
+ */
59
+ function InputOTPSlot({
60
+ index,
61
+ className,
62
+ ...props
63
+ }: React.ComponentProps<"div"> & {
64
+ index: number;
65
+ }) {
66
+ // @ts-expect-error - React 18/19 호환성: Context 타입 불일치
67
+ const inputOTPContext = React.useContext(OTPInputContext);
68
+ const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
69
+
70
+ return (
71
+ <div
72
+ data-slot="input-otp-slot"
73
+ data-active={isActive}
74
+ className={cn(
75
+ "data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
76
+ className
77
+ )}
78
+ {...props}
79
+ >
80
+ {char}
81
+ {hasFakeCaret && (
82
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
83
+ <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
84
+ </div>
85
+ )}
86
+ </div>
87
+ );
88
+ }
89
+
90
+ /** OTP 그룹 사이의 구분선 컴포넌트입니다. */
91
+ function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
92
+ return (
93
+ <div data-slot="input-otp-separator" role="separator" {...props}>
94
+ <MinusIcon />
95
+ </div>
96
+ );
97
+ }
98
+
99
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
@@ -0,0 +1 @@
1
+ export * from "./item";
@@ -0,0 +1,225 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+ import { Separator } from "@/components/separator";
7
+
8
+ /**
9
+ * 리스트 아이템들을 그룹화하는 컴포넌트입니다.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <ItemGroup>
14
+ * <Item>
15
+ * <ItemMedia variant="icon"><UserIcon /></ItemMedia>
16
+ * <ItemContent>
17
+ * <ItemTitle>제목</ItemTitle>
18
+ * <ItemDescription>설명</ItemDescription>
19
+ * </ItemContent>
20
+ * </Item>
21
+ * </ItemGroup>
22
+ * ```
23
+ */
24
+ function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
25
+ return (
26
+ <div
27
+ role="list"
28
+ data-slot="item-group"
29
+ className={cn("group/item-group flex flex-col", className)}
30
+ {...props}
31
+ />
32
+ );
33
+ }
34
+
35
+ /** 아이템 사이의 구분선 컴포넌트입니다. */
36
+ function ItemSeparator({
37
+ className,
38
+ ...props
39
+ }: React.ComponentProps<typeof Separator>) {
40
+ return (
41
+ <Separator
42
+ data-slot="item-separator"
43
+ orientation="horizontal"
44
+ className={cn("my-0", className)}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ const itemVariants = cva(
51
+ "group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
52
+ {
53
+ variants: {
54
+ variant: {
55
+ default: "bg-transparent",
56
+ outline: "border-border",
57
+ muted: "bg-muted/50",
58
+ },
59
+ size: {
60
+ default: "p-4 gap-4 ",
61
+ sm: "py-3 px-4 gap-2.5",
62
+ },
63
+ },
64
+ defaultVariants: {
65
+ variant: "default",
66
+ size: "default",
67
+ },
68
+ }
69
+ );
70
+
71
+ /**
72
+ * 개별 리스트 아이템 컴포넌트입니다.
73
+ * @param props.variant - 스타일 변형 ('default' | 'outline' | 'muted')
74
+ * @param props.size - 크기 ('default' | 'sm')
75
+ */
76
+ function Item({
77
+ className,
78
+ variant = "default",
79
+ size = "default",
80
+ asChild = false,
81
+ ...props
82
+ }: React.ComponentProps<"div"> &
83
+ VariantProps<typeof itemVariants> & { asChild?: boolean }) {
84
+ const Comp = asChild ? Slot : "div";
85
+ return (
86
+ <Comp
87
+ data-slot="item"
88
+ data-variant={variant}
89
+ data-size={size}
90
+ className={cn(itemVariants({ variant, size, className }))}
91
+ {...props}
92
+ />
93
+ );
94
+ }
95
+
96
+ const itemMediaVariants = cva(
97
+ "flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5",
98
+ {
99
+ variants: {
100
+ variant: {
101
+ default: "bg-transparent",
102
+ icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
103
+ image:
104
+ "size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
105
+ },
106
+ },
107
+ defaultVariants: {
108
+ variant: "default",
109
+ },
110
+ }
111
+ );
112
+
113
+ /**
114
+ * 아이템의 미디어(아이콘/이미지) 영역입니다.
115
+ * @param props.variant - 스타일 변형 ('default' | 'icon' | 'image')
116
+ */
117
+ function ItemMedia({
118
+ className,
119
+ variant = "default",
120
+ ...props
121
+ }: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
122
+ return (
123
+ <div
124
+ data-slot="item-media"
125
+ data-variant={variant}
126
+ className={cn(itemMediaVariants({ variant, className }))}
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ /** 아이템의 콘텐츠 영역입니다. */
133
+ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
134
+ return (
135
+ <div
136
+ data-slot="item-content"
137
+ className={cn(
138
+ "flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ );
144
+ }
145
+
146
+ /** 아이템의 제목 컴포넌트입니다. */
147
+ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
148
+ return (
149
+ <div
150
+ data-slot="item-title"
151
+ className={cn(
152
+ "flex w-fit items-center gap-2 text-sm leading-snug font-medium",
153
+ className
154
+ )}
155
+ {...props}
156
+ />
157
+ );
158
+ }
159
+
160
+ /** 아이템의 설명 텍스트입니다. */
161
+ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
162
+ return (
163
+ <p
164
+ data-slot="item-description"
165
+ className={cn(
166
+ "text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
167
+ "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
168
+ className
169
+ )}
170
+ {...props}
171
+ />
172
+ );
173
+ }
174
+
175
+ /** 아이템의 액션 버튼 영역입니다. */
176
+ function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
177
+ return (
178
+ <div
179
+ data-slot="item-actions"
180
+ className={cn("flex items-center gap-2", className)}
181
+ {...props}
182
+ />
183
+ );
184
+ }
185
+
186
+ /** 아이템의 헤더 영역입니다. */
187
+ function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
188
+ return (
189
+ <div
190
+ data-slot="item-header"
191
+ className={cn(
192
+ "flex basis-full items-center justify-between gap-2",
193
+ className
194
+ )}
195
+ {...props}
196
+ />
197
+ );
198
+ }
199
+
200
+ /** 아이템의 푸터 영역입니다. */
201
+ function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
202
+ return (
203
+ <div
204
+ data-slot="item-footer"
205
+ className={cn(
206
+ "flex basis-full items-center justify-between gap-2",
207
+ className
208
+ )}
209
+ {...props}
210
+ />
211
+ );
212
+ }
213
+
214
+ export {
215
+ Item,
216
+ ItemMedia,
217
+ ItemContent,
218
+ ItemActions,
219
+ ItemGroup,
220
+ ItemSeparator,
221
+ ItemTitle,
222
+ ItemDescription,
223
+ ItemHeader,
224
+ ItemFooter,
225
+ };
@@ -0,0 +1 @@
1
+ export * from "./kbd";
@@ -0,0 +1,38 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ /**
4
+ * 키보드 단축키를 표시하는 컴포넌트입니다.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * <Kbd>⌘</Kbd>
9
+ * <Kbd>K</Kbd>
10
+ * ```
11
+ */
12
+ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
13
+ return (
14
+ <kbd
15
+ data-slot="kbd"
16
+ className={cn(
17
+ "bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
18
+ "[&_svg:not([class*='size-'])]:size-3",
19
+ "[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ )
25
+ }
26
+
27
+ /** 여러 키보드 단축키를 그룹화하는 컴포넌트입니다. */
28
+ function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
29
+ return (
30
+ <kbd
31
+ data-slot="kbd-group"
32
+ className={cn("inline-flex items-center gap-1", className)}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ export { Kbd, KbdGroup }
@@ -0,0 +1 @@
1
+ export * from "./label";
@@ -0,0 +1,33 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ /**
9
+ * 폼 요소에 대한 레이블을 표시하는 컴포넌트입니다.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <Label htmlFor="email">이메일</Label>
14
+ * <Input id="email" type="email" />
15
+ * ```
16
+ */
17
+ function Label({
18
+ className,
19
+ ...props
20
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
21
+ return (
22
+ <LabelPrimitive.Root
23
+ data-slot="label"
24
+ className={cn(
25
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ export { Label }
@@ -0,0 +1 @@
1
+ export * from "./menubar";