@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.
- 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 +3481 -28494
- package/dist/design-system.umd.js +4 -257
- 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 @@
|
|
|
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,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";
|