@mzc-fe/design-system 0.0.6 → 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 (173) 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 +3481 -28494
  162. package/dist/design-system.umd.js +4 -257
  163. package/foundations/ThemeProvider.tsx +77 -0
  164. package/foundations/color.css +232 -0
  165. package/foundations/palette.css +249 -0
  166. package/foundations/spacing.css +8 -0
  167. package/foundations/typography.css +143 -0
  168. package/hooks/use-mobile.ts +19 -0
  169. package/index.css +173 -0
  170. package/index.ts +339 -0
  171. package/lib/utils.ts +6 -0
  172. package/package.json +40 -19
  173. package/README.md +0 -184
@@ -0,0 +1 @@
1
+ export * from "./skeleton";
@@ -0,0 +1,21 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ /**
4
+ * 콘텐츠 로딩 상태를 표시하는 스켈레톤 컴포넌트입니다.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * <Skeleton className="h-4 w-[250px]" />
9
+ * ```
10
+ */
11
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
12
+ return (
13
+ <div
14
+ data-slot="skeleton"
15
+ className={cn("bg-accent animate-pulse rounded-md", className)}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ export { Skeleton }
@@ -0,0 +1 @@
1
+ export * from "./slider";
@@ -0,0 +1,75 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SliderPrimitive from "@radix-ui/react-slider"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ /**
9
+ * 범위 내에서 값을 선택하는 슬라이더 컴포넌트입니다.
10
+ *
11
+ * @param props.min - 최소값 (기본값: 0)
12
+ * @param props.max - 최대값 (기본값: 100)
13
+ * @param props.step - 단계 값
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * <Slider defaultValue={[50]} max={100} />
18
+ * ```
19
+ */
20
+ function Slider({
21
+ className,
22
+ defaultValue,
23
+ value,
24
+ min = 0,
25
+ max = 100,
26
+ ...props
27
+ }: React.ComponentProps<typeof SliderPrimitive.Root>) {
28
+ const _values = React.useMemo(
29
+ () =>
30
+ Array.isArray(value)
31
+ ? value
32
+ : Array.isArray(defaultValue)
33
+ ? defaultValue
34
+ : [min, max],
35
+ [value, defaultValue, min, max]
36
+ )
37
+
38
+ return (
39
+ <SliderPrimitive.Root
40
+ data-slot="slider"
41
+ defaultValue={defaultValue}
42
+ value={value}
43
+ min={min}
44
+ max={max}
45
+ className={cn(
46
+ "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
47
+ className
48
+ )}
49
+ {...props}
50
+ >
51
+ <SliderPrimitive.Track
52
+ data-slot="slider-track"
53
+ className={cn(
54
+ "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
55
+ )}
56
+ >
57
+ <SliderPrimitive.Range
58
+ data-slot="slider-range"
59
+ className={cn(
60
+ "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
61
+ )}
62
+ />
63
+ </SliderPrimitive.Track>
64
+ {Array.from({ length: _values.length }, (_, index) => (
65
+ <SliderPrimitive.Thumb
66
+ data-slot="slider-thumb"
67
+ key={index}
68
+ className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
69
+ />
70
+ ))}
71
+ </SliderPrimitive.Root>
72
+ )
73
+ }
74
+
75
+ export { Slider }
@@ -0,0 +1,2 @@
1
+ export { Toaster } from "./sonner";
2
+ export { toast } from "sonner";
@@ -0,0 +1,52 @@
1
+ import {
2
+ CircleCheckIcon,
3
+ InfoIcon,
4
+ Loader2Icon,
5
+ OctagonXIcon,
6
+ TriangleAlertIcon,
7
+ } from "lucide-react";
8
+ import { useTheme } from "next-themes";
9
+ import { Toaster as Sonner, type ToasterProps } from "sonner";
10
+
11
+ /**
12
+ * 토스트 알림을 표시하는 컴포넌트입니다.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * // 앱에 Toaster 배치
17
+ * <Toaster />
18
+ *
19
+ * // toast 함수로 알림 표시
20
+ * toast("알림 메시지")
21
+ * toast.success("성공!")
22
+ * toast.error("오류 발생")
23
+ * ```
24
+ */
25
+ const Toaster = ({ ...props }: ToasterProps) => {
26
+ const { theme = "system" } = useTheme();
27
+
28
+ return (
29
+ <Sonner
30
+ theme={theme as ToasterProps["theme"]}
31
+ className="toaster group"
32
+ icons={{
33
+ success: <CircleCheckIcon className="size-4" />,
34
+ info: <InfoIcon className="size-4" />,
35
+ warning: <TriangleAlertIcon className="size-4" />,
36
+ error: <OctagonXIcon className="size-4" />,
37
+ loading: <Loader2Icon className="size-4 animate-spin" />,
38
+ }}
39
+ style={
40
+ {
41
+ "--normal-bg": "var(--popover)",
42
+ "--normal-text": "var(--popover-foreground)",
43
+ "--normal-border": "var(--border)",
44
+ "--border-radius": "var(--radius)",
45
+ } as React.CSSProperties
46
+ }
47
+ {...props}
48
+ />
49
+ );
50
+ };
51
+
52
+ export { Toaster };
@@ -0,0 +1 @@
1
+ export * from "./spinner";
@@ -0,0 +1,26 @@
1
+ import { Loader2Icon } from "lucide-react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ /**
6
+ * 로딩 상태를 나타내는 스피너 컴포넌트입니다.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <Spinner />
11
+ * <Spinner className="size-8" />
12
+ * ```
13
+ */
14
+ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
15
+ return (
16
+ // @ts-expect-error - React 18/19 호환성: SVG props 타입 불일치
17
+ <Loader2Icon
18
+ role="status"
19
+ aria-label="Loading"
20
+ className={cn("size-4 animate-spin", className)}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ export { Spinner }
@@ -0,0 +1 @@
1
+ export * from "./switch";
@@ -0,0 +1,39 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SwitchPrimitive from "@radix-ui/react-switch"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ /**
9
+ * ON/OFF 상태를 전환하는 스위치 컴포넌트입니다.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <Switch checked={isEnabled} onCheckedChange={setIsEnabled} />
14
+ * ```
15
+ */
16
+ function Switch({
17
+ className,
18
+ ...props
19
+ }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
20
+ return (
21
+ <SwitchPrimitive.Root
22
+ data-slot="switch"
23
+ className={cn(
24
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ <SwitchPrimitive.Thumb
30
+ data-slot="switch-thumb"
31
+ className={cn(
32
+ "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
33
+ )}
34
+ />
35
+ </SwitchPrimitive.Root>
36
+ )
37
+ }
38
+
39
+ export { Switch }
@@ -0,0 +1 @@
1
+ export * from "./table";
@@ -0,0 +1,140 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ /**
6
+ * 데이터를 행과 열로 표시하는 테이블 컴포넌트입니다.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <Table>
11
+ * <TableHeader>
12
+ * <TableRow>
13
+ * <TableHead>이름</TableHead>
14
+ * <TableHead>이메일</TableHead>
15
+ * </TableRow>
16
+ * </TableHeader>
17
+ * <TableBody>
18
+ * <TableRow>
19
+ * <TableCell>홍길동</TableCell>
20
+ * <TableCell>hong@example.com</TableCell>
21
+ * </TableRow>
22
+ * </TableBody>
23
+ * </Table>
24
+ * ```
25
+ */
26
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
27
+ return (
28
+ <div
29
+ data-slot="table-container"
30
+ className="relative w-full overflow-x-auto"
31
+ >
32
+ <table
33
+ data-slot="table"
34
+ className={cn("w-full caption-bottom text-sm", className)}
35
+ {...props}
36
+ />
37
+ </div>
38
+ )
39
+ }
40
+
41
+ /** 테이블 헤더 영역입니다. */
42
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
43
+ return (
44
+ <thead
45
+ data-slot="table-header"
46
+ className={cn("[&_tr]:border-b", className)}
47
+ {...props}
48
+ />
49
+ )
50
+ }
51
+
52
+ /** 테이블 본문 영역입니다. */
53
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
54
+ return (
55
+ <tbody
56
+ data-slot="table-body"
57
+ className={cn("[&_tr:last-child]:border-0", className)}
58
+ {...props}
59
+ />
60
+ )
61
+ }
62
+
63
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
64
+ return (
65
+ <tfoot
66
+ data-slot="table-footer"
67
+ className={cn(
68
+ "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ )
74
+ }
75
+
76
+ /** 테이블 행입니다. */
77
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
78
+ return (
79
+ <tr
80
+ data-slot="table-row"
81
+ className={cn(
82
+ "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ )
88
+ }
89
+
90
+ /** 테이블 헤더 셀입니다. */
91
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
92
+ return (
93
+ <th
94
+ data-slot="table-head"
95
+ className={cn(
96
+ "text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
97
+ className
98
+ )}
99
+ {...props}
100
+ />
101
+ )
102
+ }
103
+
104
+ /** 테이블 데이터 셀입니다. */
105
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
106
+ return (
107
+ <td
108
+ data-slot="table-cell"
109
+ className={cn(
110
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
111
+ className
112
+ )}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function TableCaption({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<"caption">) {
122
+ return (
123
+ <caption
124
+ data-slot="table-caption"
125
+ className={cn("text-muted-foreground mt-4 text-sm", className)}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ export {
132
+ Table,
133
+ TableHeader,
134
+ TableBody,
135
+ TableFooter,
136
+ TableHead,
137
+ TableRow,
138
+ TableCell,
139
+ TableCaption,
140
+ }
@@ -0,0 +1 @@
1
+ export * from "./tabs";
@@ -0,0 +1,94 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+ import { Tabs as TabsPrimitive } from "radix-ui"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ export type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>;
10
+ export function Tabs({
11
+ className,
12
+ orientation = "horizontal",
13
+ ...props
14
+ }: TabsProps) {
15
+ return (
16
+ <TabsPrimitive.Root
17
+ data-slot="tabs"
18
+ data-orientation={orientation}
19
+ orientation={orientation}
20
+ className={cn(
21
+ "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
22
+ className
23
+ )}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ const tabsListVariants = cva(
30
+ "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
31
+ {
32
+ variants: {
33
+ variant: {
34
+ default: "bg-muted",
35
+ line: "gap-1 bg-transparent",
36
+ },
37
+ },
38
+ defaultVariants: {
39
+ variant: "default",
40
+ },
41
+ }
42
+ )
43
+
44
+ export type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List> &
45
+ VariantProps<typeof tabsListVariants>
46
+ export function TabsList({
47
+ className,
48
+ /** 탭 리스트 스타일 */
49
+ variant = "default",
50
+ ...props
51
+ }: TabsListProps) {
52
+ return (
53
+ <TabsPrimitive.List
54
+ data-slot="tabs-list"
55
+ data-variant={variant}
56
+ className={cn(tabsListVariants({ variant }), className)}
57
+ {...props}
58
+ />
59
+ )
60
+ }
61
+
62
+ export type TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Trigger>;
63
+ export function TabsTrigger({
64
+ className,
65
+ ...props
66
+ }: TabsTriggerProps) {
67
+ return (
68
+ <TabsPrimitive.Trigger
69
+ data-slot="tabs-trigger"
70
+ className={cn(
71
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
72
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
73
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
74
+ "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ export type TabsContentProps = React.ComponentProps<typeof TabsPrimitive.Content>;
83
+ export function TabsContent({
84
+ className,
85
+ ...props
86
+ }: TabsContentProps) {
87
+ return (
88
+ <TabsPrimitive.Content
89
+ data-slot="tabs-content"
90
+ className={cn("flex-1 outline-none", className)}
91
+ {...props}
92
+ />
93
+ )
94
+ }
@@ -0,0 +1 @@
1
+ export * from "./textarea";
@@ -0,0 +1,26 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ /**
6
+ * 여러 줄의 텍스트를 입력받는 텍스트 영역 컴포넌트입니다.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <Textarea placeholder="메시지를 입력하세요" />
11
+ * ```
12
+ */
13
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
14
+ return (
15
+ <textarea
16
+ data-slot="textarea"
17
+ className={cn(
18
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ export { Textarea }
@@ -0,0 +1 @@
1
+ export * from "./toggle";
@@ -0,0 +1,58 @@
1
+ import * as React from "react"
2
+ import * as TogglePrimitive from "@radix-ui/react-toggle"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ /**
8
+ * 눌린 상태와 눌리지 않은 상태를 전환하는 토글 버튼 컴포넌트입니다.
9
+ *
10
+ * @param props.variant - 스타일 변형 ('default' | 'outline')
11
+ * @param props.size - 크기 ('sm' | 'default' | 'lg')
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <Toggle aria-label="Toggle bold">
16
+ * <BoldIcon />
17
+ * </Toggle>
18
+ * ```
19
+ */
20
+ const toggleVariants = cva(
21
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
22
+ {
23
+ variants: {
24
+ variant: {
25
+ default: "bg-transparent",
26
+ outline:
27
+ "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
28
+ },
29
+ size: {
30
+ default: "h-9 px-2 min-w-9",
31
+ sm: "h-8 px-1.5 min-w-8",
32
+ lg: "h-10 px-2.5 min-w-10",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ size: "default",
38
+ },
39
+ }
40
+ )
41
+
42
+ function Toggle({
43
+ className,
44
+ variant,
45
+ size,
46
+ ...props
47
+ }: React.ComponentProps<typeof TogglePrimitive.Root> &
48
+ VariantProps<typeof toggleVariants>) {
49
+ return (
50
+ <TogglePrimitive.Root
51
+ data-slot="toggle"
52
+ className={cn(toggleVariants({ variant, size, className }))}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ export { Toggle, toggleVariants }
@@ -0,0 +1 @@
1
+ export * from "./toggle-group";
@@ -0,0 +1,97 @@
1
+ import * as React from "react";
2
+ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
3
+ import { type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+ import { toggleVariants } from "@/components/toggle";
7
+
8
+ /**
9
+ * 여러 개의 토글 버튼을 그룹으로 묶어 단일/다중 선택을 지원하는 컴포넌트입니다.
10
+ *
11
+ * @param props.type - 'single'은 하나만, 'multiple'은 여러 개 선택 가능
12
+ * @param props.variant - 스타일 변형
13
+ * @param props.size - 크기
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * <ToggleGroup type="single">
18
+ * <ToggleGroupItem value="bold"><BoldIcon /></ToggleGroupItem>
19
+ * <ToggleGroupItem value="italic"><ItalicIcon /></ToggleGroupItem>
20
+ * </ToggleGroup>
21
+ * ```
22
+ */
23
+ const ToggleGroupContext = React.createContext<
24
+ VariantProps<typeof toggleVariants> & {
25
+ spacing?: number;
26
+ }
27
+ >({
28
+ size: "default",
29
+ variant: "default",
30
+ spacing: 0,
31
+ });
32
+
33
+ function ToggleGroup({
34
+ className,
35
+ variant,
36
+ size,
37
+ spacing = 0,
38
+ children,
39
+ ...props
40
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
41
+ VariantProps<typeof toggleVariants> & {
42
+ spacing?: number;
43
+ }) {
44
+ return (
45
+ <ToggleGroupPrimitive.Root
46
+ data-slot="toggle-group"
47
+ data-variant={variant}
48
+ data-size={size}
49
+ data-spacing={spacing}
50
+ style={{ "--gap": spacing } as React.CSSProperties}
51
+ className={cn(
52
+ "group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
53
+ className
54
+ )}
55
+ {...props}
56
+ >
57
+ <ToggleGroupContext.Provider value={{ variant, size, spacing }}>
58
+ {children}
59
+ </ToggleGroupContext.Provider>
60
+ </ToggleGroupPrimitive.Root>
61
+ );
62
+ }
63
+
64
+ /** 토글 그룹의 개별 아이템입니다. */
65
+ function ToggleGroupItem({
66
+ className,
67
+ children,
68
+ variant,
69
+ size,
70
+ ...props
71
+ }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
72
+ VariantProps<typeof toggleVariants>) {
73
+ const context = React.useContext(ToggleGroupContext);
74
+
75
+ return (
76
+ <ToggleGroupPrimitive.Item
77
+ data-slot="toggle-group-item"
78
+ data-variant={context.variant || variant}
79
+ data-size={context.size || size}
80
+ data-spacing={context.spacing}
81
+ className={cn(
82
+ toggleVariants({
83
+ variant: context.variant || variant,
84
+ size: context.size || size,
85
+ }),
86
+ "w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
87
+ "data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
88
+ className
89
+ )}
90
+ {...props}
91
+ >
92
+ {children}
93
+ </ToggleGroupPrimitive.Item>
94
+ );
95
+ }
96
+
97
+ export { ToggleGroup, ToggleGroupItem };
@@ -0,0 +1 @@
1
+ export * from "./tooltip";