@crossangle-org/cs-ui 0.2.5 → 0.2.7
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/dist/components/accordion/cs-accordion.js +116 -0
- package/dist/components/accordion/cs-accordion.js.map +1 -0
- package/dist/components/alert-dialog/cs-alert-dialog.js +148 -0
- package/dist/components/alert-dialog/cs-alert-dialog.js.map +1 -0
- package/dist/components/avatar/cs-avatar.js +44 -0
- package/dist/components/avatar/cs-avatar.js.map +1 -0
- package/dist/components/badge/cs-badge.js +40 -0
- package/dist/components/badge/cs-badge.js.map +1 -0
- package/dist/components/box/cs-box.js +37 -0
- package/dist/components/box/cs-box.js.map +1 -0
- package/dist/components/button/cs-button.js +91 -0
- package/dist/components/button/cs-button.js.map +1 -0
- package/dist/components/calendar/cs-calendar.js +199 -0
- package/dist/components/calendar/cs-calendar.js.map +1 -0
- package/dist/components/card/cs-card.js +95 -0
- package/dist/components/card/cs-card.js.map +1 -0
- package/dist/components/chart/cs-chart.js +88 -0
- package/dist/components/chart/cs-chart.js.map +1 -0
- package/dist/components/checkbox/cs-checkbox.js +55 -0
- package/dist/components/checkbox/cs-checkbox.js.map +1 -0
- package/dist/components/code-block/cs-code-block.js +39 -0
- package/dist/components/code-block/cs-code-block.js.map +1 -0
- package/dist/components/code-block/cs-code-highlighter.js +59 -0
- package/dist/components/code-block/cs-code-highlighter.js.map +1 -0
- package/dist/components/collapsible/cs-collapsible.js +36 -0
- package/dist/components/collapsible/cs-collapsible.js.map +1 -0
- package/dist/components/date-picker/cs-date-picker.js +25 -0
- package/dist/components/date-picker/cs-date-picker.js.map +1 -0
- package/dist/components/dialog/cs-dialog.js +131 -0
- package/dist/components/dialog/cs-dialog.js.map +1 -0
- package/dist/components/drawer/cs-drawer.js +131 -0
- package/dist/components/drawer/cs-drawer.js.map +1 -0
- package/dist/components/dropdown-menu/cs-dropdown-menu.js +247 -0
- package/dist/components/dropdown-menu/cs-dropdown-menu.js.map +1 -0
- package/dist/components/dropzone/cs-dropzone.js +147 -0
- package/dist/components/dropzone/cs-dropzone.js.map +1 -0
- package/dist/components/empty/cs-empty.js +107 -0
- package/dist/components/empty/cs-empty.js.map +1 -0
- package/dist/components/field/cs-field.js +218 -0
- package/dist/components/field/cs-field.js.map +1 -0
- package/dist/components/input/cs-input-group.js +207 -0
- package/dist/components/input/cs-input-group.js.map +1 -0
- package/dist/components/input/cs-input.js +40 -0
- package/dist/components/input/cs-input.js.map +1 -0
- package/dist/components/label/cs-label.js +26 -0
- package/dist/components/label/cs-label.js.map +1 -0
- package/dist/components/navigation-menu/cs-navigation-menu.js +214 -0
- package/dist/components/navigation-menu/cs-navigation-menu.js.map +1 -0
- package/dist/components/pagination/cs-pagination.js +124 -0
- package/dist/components/pagination/cs-pagination.js.map +1 -0
- package/dist/components/popover/cs-popover.js +60 -0
- package/dist/components/popover/cs-popover.js.map +1 -0
- package/dist/components/progress/cs-progress.js +62 -0
- package/dist/components/progress/cs-progress.js.map +1 -0
- package/dist/components/scroll-area/cs-scroll-area.js +61 -0
- package/dist/components/scroll-area/cs-scroll-area.js.map +1 -0
- package/dist/components/select/cs-select.js +195 -0
- package/dist/components/select/cs-select.js.map +1 -0
- package/dist/components/select/cs-simple-select.js +32 -0
- package/dist/components/select/cs-simple-select.js.map +1 -0
- package/dist/components/separator/cs-separator.js +28 -0
- package/dist/components/separator/cs-separator.js.map +1 -0
- package/dist/components/sheet/cs-sheet.js +128 -0
- package/dist/components/sheet/cs-sheet.js.map +1 -0
- package/dist/components/sidebar/cs-sidebar.js +657 -0
- package/dist/components/sidebar/cs-sidebar.js.map +1 -0
- package/dist/components/skeleton/cs-skeleton.js +32 -0
- package/dist/components/skeleton/cs-skeleton.js.map +1 -0
- package/dist/components/sonner/cs-sonner.js +76 -0
- package/dist/components/sonner/cs-sonner.js.map +1 -0
- package/dist/components/spinner/cs-spinner.js +34 -0
- package/dist/components/spinner/cs-spinner.js.map +1 -0
- package/dist/components/switch/cs-switch.js +38 -0
- package/dist/components/switch/cs-switch.js.map +1 -0
- package/dist/components/table/cs-data-base-table.js +108 -0
- package/dist/components/table/cs-data-base-table.js.map +1 -0
- package/dist/components/table/cs-data-table.js +32 -0
- package/dist/components/table/cs-data-table.js.map +1 -0
- package/dist/components/table/cs-skeleton-table.js +41 -0
- package/dist/components/table/cs-skeleton-table.js.map +1 -0
- package/dist/components/table/cs-table.js +120 -0
- package/dist/components/table/cs-table.js.map +1 -0
- package/dist/components/tabs/cs-simple-tabs.js +24 -0
- package/dist/components/tabs/cs-simple-tabs.js.map +1 -0
- package/dist/components/tabs/cs-tabs.js +114 -0
- package/dist/components/tabs/cs-tabs.js.map +1 -0
- package/dist/components/toggle/cs-toggle-group.js +65 -0
- package/dist/components/toggle/cs-toggle-group.js.map +1 -0
- package/dist/components/toggle/cs-toggle.js +46 -0
- package/dist/components/toggle/cs-toggle.js.map +1 -0
- package/dist/components/tooltip/cs-simple-tooltip.js +16 -0
- package/dist/components/tooltip/cs-simple-tooltip.js.map +1 -0
- package/dist/components/tooltip/cs-tooltip.js +72 -0
- package/dist/components/tooltip/cs-tooltip.js.map +1 -0
- package/dist/constants/cs-chart-option.constant.js +105 -0
- package/dist/constants/cs-chart-option.constant.js.map +1 -0
- package/dist/cs-ui.css +73 -108
- package/dist/hooks/use-accordion.js +54 -0
- package/dist/hooks/use-accordion.js.map +1 -0
- package/dist/hooks/use-infinite-scroll.js +40 -0
- package/dist/hooks/use-infinite-scroll.js.map +1 -0
- package/dist/hooks/use-laptop.js +20 -0
- package/dist/hooks/use-laptop.js.map +1 -0
- package/dist/hooks/use-mobile.js +20 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +19 -6
- package/dist/index.js +287 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/chart.util.js +48 -0
- package/dist/lib/chart.util.js.map +1 -0
- package/dist/lib/style.util.js +19 -0
- package/dist/lib/style.util.js.map +1 -0
- package/dist/lib/utils.js +27 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +4 -5
- package/dist/index.cjs.js +0 -147659
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -147624
- package/dist/index.es.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cs-dropdown-menu.js","sources":["../../../src/components/dropdown-menu/cs-dropdown-menu.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from \"lucide-react\"\n\nimport { cn } from \"../../lib/utils\"\n\n/**\n * CsDropdownMenu 아이템 공통 스타일 (Design Token Reference)\n *\n * ## State 매핑\n * | UI 상태 | Token state |\n * |---------|-------------|\n * | 기본 | default |\n * | hover: | hover |\n * | 선택됨 (data-[state=checked]) | active |\n * | disabled: | disabled |\n *\n * ## CSS Variables\n * ```css\n * --dropdown-container-radius | padding-x | padding-y | gap | bg | border | border-width\n * --dropdown-item-common-radius | padding-x | padding-y | height | bg | border | border-width\n * --dropdown-item-simple-{state}-font | bg\n * --dropdown-item-icon-gap | icon-size | default-icon | disabled-icon\n * --dropdown-item-header-font\n * ```\n */\nconst csDropdownMenuItemCommonClassName = [\n \"flex cursor-default items-center gap-(--dropdown-item-icon-gap) outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"rounded-(--dropdown-item-common-radius) px-(--dropdown-item-common-padding-x) py-(--dropdown-item-common-padding-y) h-(--dropdown-item-common-height) bg-(--dropdown-item-common-bg)\",\n \"typo-body-sm text-(--dropdown-item-simple-default-font) hover:text-(--dropdown-item-simple-hover-font) hover:bg-(--dropdown-item-simple-hover-bg) data-[state=checked]:text-(--dropdown-item-simple-active-font) data-[disabled]:text-(--dropdown-item-simple-disabled-font)\",\n \"[&_svg:not([class*='size-'])]:size-(--dropdown-item-icon-icon-size) [&_svg]:text-(--dropdown-item-icon-default-icon) data-[state=checked]:[&_svg]:text-(--dropdown-item-simple-active-icon) data-[disabled]:[&_svg]:text-(--dropdown-item-icon-disabled-icon)\", \n].join(\" \")\n\n/**\n * CS Design System 드롭다운 메뉴 컴포넌트\n *\n * 버튼이나 아이콘 클릭 시 나타나는 액션 메뉴 컴포넌트.\n * 사용자 프로필 메뉴, 컨텍스트 메뉴, 더보기 옵션 등에 사용되며, 여러 서브 컴포넌트를 조합하여 구성합니다.\n *\n * ## 사용 시나리오\n * - 사용자 메뉴: 프로필, 설정, 로그아웃 등 계정 관련 액션\n * - 더보기 메뉴: 테이블 행의 ... 버튼 (수정, 삭제, 공유 등)\n * - 컨텍스트 메뉴: 우클릭 시 나타나는 액션 목록\n * - 필터/정렬 메뉴: 체크박스/라디오 선택 포함\n *\n * ## 유사 컴포넌트와의 차이\n * - **CsSelect**: 값 선택 (폼 입력) - 선택한 값이 저장됨\n * - **CsPopover**: 정보 표시/간단한 폼 (클릭 후 입력)\n * - DropdownMenu는 액션 실행, Select는 값 선택\n *\n * @example 기본 조합\n * ```tsx\n * <CsDropdownMenu>\n * <CsDropdownMenuTrigger asChild>\n * <CsButton>메뉴 열기</CsButton>\n * </CsDropdownMenuTrigger>\n * <CsDropdownMenuContent>\n * <CsDropdownMenuItem>항목 1</CsDropdownMenuItem>\n * <CsDropdownMenuItem>항목 2</CsDropdownMenuItem>\n * </CsDropdownMenuContent>\n * </CsDropdownMenu>\n * ```\n *\n * @example 그룹 + 라벨 + 구분선\n * ```tsx\n * <CsDropdownMenuContent>\n * <CsDropdownMenuGroup>\n * <CsDropdownMenuLabel>설정</CsDropdownMenuLabel>\n * <CsDropdownMenuItem>프로필</CsDropdownMenuItem>\n * </CsDropdownMenuGroup>\n * <CsDropdownMenuSeparator />\n * <CsDropdownMenuItem>로그아웃</CsDropdownMenuItem>\n * </CsDropdownMenuContent>\n * ```\n *\n * @example 체크박스/라디오 아이템\n * ```tsx\n * <CsDropdownMenuCheckboxItem checked={checked} onCheckedChange={setChecked}>\n * 알림 켜기\n * </CsDropdownMenuCheckboxItem>\n *\n * <CsDropdownMenuRadioGroup value={value} onValueChange={setValue}>\n * <CsDropdownMenuRadioItem value=\"light\">라이트</CsDropdownMenuRadioItem>\n * <CsDropdownMenuRadioItem value=\"dark\">다크</CsDropdownMenuRadioItem>\n * </CsDropdownMenuRadioGroup>\n * ```\n *\n * @see {@link https://www.radix-ui.com/primitives/docs/components/dropdown-menu | Radix DropdownMenu}\n */\nfunction CsDropdownMenu({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\n/** 드롭다운 포탈 - body에 렌더링 */\nfunction CsDropdownMenuPortal({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n return (\n <DropdownMenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n )\n}\n\n/** 드롭다운 트리거 - asChild로 커스텀 버튼 사용 권장 */\nfunction CsDropdownMenuTrigger({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n return (\n <DropdownMenuPrimitive.Trigger\n data-slot=\"dropdown-menu-trigger\"\n {...props}\n />\n )\n}\n\n/** 드롭다운 컨텐츠 컨테이너 - Portal 자동 포함 */\nfunction CsDropdownMenuContent({\n className,\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n className={cn(\n \"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) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto\",\n \"rounded-(--dropdown-container-radius) gap-(--dropdown-container-gap) px-(--dropdown-container-padding-x) py-(--dropdown-container-padding-y) bg-(--dropdown-container-bg) border-(--dropdown-container-border) border-(length:--dropdown-container-border-width) boxshadow-sm\",\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n}\n\n/** 아이템 그룹 컨테이너 */\nfunction CsDropdownMenuGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n return (\n <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n )\n}\n\n/**\n * 개별 메뉴 아이템\n *\n * 아이콘 크기 자동 조절: `[&_svg]:size-(--dropdown-item-icon-icon-size)`\n *\n * @param inset - 왼쪽 여백 추가 (체크박스/라디오 아이템과 정렬 시)\n * @param variant - `\"default\"` | `\"destructive\"`\n */\nfunction CsDropdownMenuItem({\n className,\n inset,\n variant = \"default\",\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n variant?: \"default\" | \"destructive\"\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n csDropdownMenuItemCommonClassName,\n \"relative data-disabled:pointer-events-none\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** 체크박스 아이템 - 체크 시 CheckIcon 표시 */\nfunction CsDropdownMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n className={cn(\n csDropdownMenuItemCommonClassName,\n \"data-disabled:pointer-events-none\",\n className\n )}\n checked={checked}\n {...props}\n >\n {checked \n ? (<DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon />\n </DropdownMenuPrimitive.ItemIndicator>)\n : (<div className=\"size-(--dropdown-item-icon-icon-size)\" />)\n }\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n )\n}\n\n/** 라디오 그룹 - 단일 선택 아이템 그룹 */\nfunction CsDropdownMenuRadioGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n return (\n <DropdownMenuPrimitive.RadioGroup\n data-slot=\"dropdown-menu-radio-group\"\n {...props}\n />\n )\n}\n\n/** 라디오 아이템 - 선택 시 CircleIcon 표시 */\nfunction CsDropdownMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n return (\n <DropdownMenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(\n csDropdownMenuItemCommonClassName,\n \"group data-disabled:pointer-events-none\",\n className\n )}\n {...props}\n >\n <DropdownMenuPrimitive.ItemIndicator>\n <CircleIcon className=\"fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n <div className=\"size-(--dropdown-item-icon-icon-size) group-data-[state=checked]:hidden\" />\n {children}\n </DropdownMenuPrimitive.RadioItem>\n )\n}\n\n/** 그룹 라벨 - 선택 불가 텍스트 */\nfunction CsDropdownMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.Label\n data-slot=\"dropdown-menu-label\"\n data-inset={inset}\n className={cn(\n csDropdownMenuItemCommonClassName,\n \"typo-productive-label-md text-(--dropdown-item-header-font)\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** 아이템 그룹 구분선 */\nfunction CsDropdownMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"bg-(--dropdown-item-common-border) w-full h-(--dropdown-item-common-border-width)\", className)}\n {...props}\n />\n )\n}\n\n/** 단축키 표시 (아이템 우측 정렬) */\nfunction CsDropdownMenuShortcut({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn(\n \"text-(--dropdown-item-icon-default-icon) ml-auto typo-body-xs\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** 서브메뉴 Root */\nfunction CsDropdownMenuSub({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\n/** 서브메뉴 트리거 - 기본 ChevronRightIcon, icon prop으로 변경 가능 */\nfunction CsDropdownMenuSubTrigger({\n className,\n inset,\n children,\n icon,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n icon?: React.ReactNode\n}) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default select-none items-center rounded-(--dropdown-item-common-radius) py-(--dropdown-item-common-padding-y) pl-8 pr-2 outline-none transition-colors data-disabled:pointer-events-none data-disabled:opacity-50 focus:bg-(--dropdown-item-simple-hover-bg) focus:text-(--dropdown-item-simple-hover-font)\",\n className\n )}\n {...props}\n >\n {children}\n {icon ?? <ChevronRightIcon />}\n </DropdownMenuPrimitive.SubTrigger>\n )\n}\n\n/** 서브메뉴 컨텐츠 컨테이너 */\nfunction CsDropdownMenuSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n return (\n <DropdownMenuPrimitive.SubContent\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n \"z-50 min-w-32 overflow-hidden text-popover-foreground boxshadow-default 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 origin-(--radix-dropdown-menu-content-transform-origin) rounded-(--dropdown-container-radius) border-(--dropdown-container-border) p-(--dropdown-container-padding-x) shadow-lg bg-(--dropdown-container-bg)\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n CsDropdownMenu,\n CsDropdownMenuPortal,\n CsDropdownMenuTrigger,\n CsDropdownMenuContent,\n CsDropdownMenuGroup,\n CsDropdownMenuLabel,\n CsDropdownMenuItem,\n CsDropdownMenuCheckboxItem,\n CsDropdownMenuRadioGroup,\n CsDropdownMenuRadioItem,\n CsDropdownMenuSeparator,\n CsDropdownMenuShortcut,\n CsDropdownMenuSub,\n CsDropdownMenuSubTrigger,\n CsDropdownMenuSubContent,\n csDropdownMenuItemCommonClassName\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { UploadIcon } from "lucide-react";
|
|
4
|
+
import { createContext, useContext } from "react";
|
|
5
|
+
import { useDropzone } from "react-dropzone";
|
|
6
|
+
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { CsButton } from "../button/cs-button.js";
|
|
8
|
+
const renderBytes = (bytes) => {
|
|
9
|
+
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
10
|
+
let size = bytes;
|
|
11
|
+
let unitIndex = 0;
|
|
12
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
13
|
+
size /= 1024;
|
|
14
|
+
unitIndex++;
|
|
15
|
+
}
|
|
16
|
+
return `${size.toFixed(2)}${units[unitIndex]}`;
|
|
17
|
+
};
|
|
18
|
+
const CsDropzoneContext = createContext(
|
|
19
|
+
void 0
|
|
20
|
+
);
|
|
21
|
+
const CsDropzone = ({
|
|
22
|
+
accept,
|
|
23
|
+
maxFiles = 1,
|
|
24
|
+
maxSize,
|
|
25
|
+
minSize,
|
|
26
|
+
onDrop,
|
|
27
|
+
onError,
|
|
28
|
+
disabled,
|
|
29
|
+
src,
|
|
30
|
+
className,
|
|
31
|
+
children,
|
|
32
|
+
...props
|
|
33
|
+
}) => {
|
|
34
|
+
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
35
|
+
accept,
|
|
36
|
+
maxFiles,
|
|
37
|
+
maxSize,
|
|
38
|
+
minSize,
|
|
39
|
+
onError,
|
|
40
|
+
disabled,
|
|
41
|
+
onDrop: (acceptedFiles, fileRejections, event) => {
|
|
42
|
+
if (fileRejections.length > 0) {
|
|
43
|
+
const message = fileRejections.at(0)?.errors.at(0)?.message;
|
|
44
|
+
onError?.(new Error(message));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
onDrop?.(acceptedFiles, fileRejections, event);
|
|
48
|
+
},
|
|
49
|
+
...props
|
|
50
|
+
});
|
|
51
|
+
return /* @__PURE__ */ jsx(
|
|
52
|
+
CsDropzoneContext.Provider,
|
|
53
|
+
{
|
|
54
|
+
value: { src, accept, maxSize, minSize, maxFiles },
|
|
55
|
+
children: /* @__PURE__ */ jsxs(
|
|
56
|
+
CsButton,
|
|
57
|
+
{
|
|
58
|
+
className: cn(
|
|
59
|
+
"relative h-auto w-full flex-col overflow-hidden border-dashed",
|
|
60
|
+
"rounded-(--dropzone-common-radius) px-(--dropzone-common-padding-x) py-(--dropzone-common-padding-y) gap-(--dropzone-common-gap)",
|
|
61
|
+
"bg-(--dropzone-common-bg) hover:bg-(--dropzone-common-bg) border-(--dropzone-common-border) border-(length:--dropzone-common-border-width)",
|
|
62
|
+
isDragActive && "outline-none border-solid",
|
|
63
|
+
className
|
|
64
|
+
),
|
|
65
|
+
disabled,
|
|
66
|
+
type: "button",
|
|
67
|
+
variant: "outline",
|
|
68
|
+
...getRootProps(),
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ jsx("input", { ...getInputProps(), disabled }),
|
|
71
|
+
children
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
},
|
|
76
|
+
JSON.stringify(src)
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
const useDropzoneContext = () => {
|
|
80
|
+
const context = useContext(CsDropzoneContext);
|
|
81
|
+
if (!context) {
|
|
82
|
+
throw new Error("useDropzoneContext must be used within a Dropzone");
|
|
83
|
+
}
|
|
84
|
+
return context;
|
|
85
|
+
};
|
|
86
|
+
const maxLabelItems = 3;
|
|
87
|
+
const CsDropzoneContent = ({
|
|
88
|
+
children,
|
|
89
|
+
className
|
|
90
|
+
}) => {
|
|
91
|
+
const { src } = useDropzoneContext();
|
|
92
|
+
if (!src) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (children) {
|
|
96
|
+
return children;
|
|
97
|
+
}
|
|
98
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center justify-center gap-(--dropzone-container-content-gap)", className), children: [
|
|
99
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-8 items-center justify-center bg-(--dropzone-container-icon-bg) text-(--dropzone-container-icon-color) rounded-(--dropzone-container-icon-radius)", children: /* @__PURE__ */ jsx(UploadIcon, { size: 16 }) }),
|
|
100
|
+
/* @__PURE__ */ jsx("p", { className: "w-full truncate typo-body-sm text-(--dropzone-container-content-title)", children: src.length > maxLabelItems ? `${new Intl.ListFormat("en").format(
|
|
101
|
+
src.slice(0, maxLabelItems).map((file) => file.name)
|
|
102
|
+
)} and ${src.length - maxLabelItems} more` : new Intl.ListFormat("en").format(src.map((file) => file.name)) }),
|
|
103
|
+
/* @__PURE__ */ jsx("p", { className: "w-full text-wrap typo-body-xs text-(--dropzone-container-content-description)", children: "Drag and drop or click to replace" })
|
|
104
|
+
] });
|
|
105
|
+
};
|
|
106
|
+
const CsDropzoneEmptyState = ({
|
|
107
|
+
children,
|
|
108
|
+
className
|
|
109
|
+
}) => {
|
|
110
|
+
const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext();
|
|
111
|
+
if (src) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (children) {
|
|
115
|
+
return children;
|
|
116
|
+
}
|
|
117
|
+
let caption = "";
|
|
118
|
+
if (accept) {
|
|
119
|
+
caption += "Accepts ";
|
|
120
|
+
caption += new Intl.ListFormat("en").format(Object.keys(accept));
|
|
121
|
+
}
|
|
122
|
+
if (minSize && maxSize) {
|
|
123
|
+
caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`;
|
|
124
|
+
} else if (minSize) {
|
|
125
|
+
caption += ` at least ${renderBytes(minSize)}`;
|
|
126
|
+
} else if (maxSize) {
|
|
127
|
+
caption += ` less than ${renderBytes(maxSize)}`;
|
|
128
|
+
}
|
|
129
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center justify-center gap-(--dropzone-container-content-gap)", className), children: [
|
|
130
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-8 items-center justify-center bg-(--dropzone-container-icon-bg) text-(--dropzone-container-icon-color) rounded-(--dropzone-container-icon-radius)", children: /* @__PURE__ */ jsx(UploadIcon, { size: 16 }) }),
|
|
131
|
+
/* @__PURE__ */ jsxs("p", { className: "w-full truncate text-wrap typo-body-sm text-(--dropzone-container-content-title)", children: [
|
|
132
|
+
"Upload ",
|
|
133
|
+
maxFiles === 1 ? "a file" : "files"
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsx("p", { className: "w-full truncate text-wrap typo-body-xs text-(--dropzone-container-content-description)", children: "Drag and drop or click to upload" }),
|
|
136
|
+
caption && /* @__PURE__ */ jsxs("p", { className: "text-wrap typo-body-xs text-(--dropzone-container-content-description)", children: [
|
|
137
|
+
caption,
|
|
138
|
+
"."
|
|
139
|
+
] })
|
|
140
|
+
] });
|
|
141
|
+
};
|
|
142
|
+
export {
|
|
143
|
+
CsDropzone,
|
|
144
|
+
CsDropzoneContent,
|
|
145
|
+
CsDropzoneEmptyState
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=cs-dropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cs-dropzone.js","sources":["../../../src/components/dropzone/cs-dropzone.tsx"],"sourcesContent":["'use client';\n\nimport { UploadIcon } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\nimport type { DropEvent, DropzoneOptions, FileRejection } from 'react-dropzone';\nimport { useDropzone } from 'react-dropzone';\nimport { cn } from '../../lib/utils';\nimport { CsButton } from '../button';\n\n/**\n * CsDropzone 스타일 시스템 (Design Token Reference)\n *\n * ## Figma Token → Props 매핑\n * | Figma 토큰 패턴 | 적용 |\n * |----------------|------|\n * | `dropzone/common/*` | 컨테이너 스타일 (radius, padding, bg, border) |\n * | `dropzone/container/content-*` | 내부 콘텐츠 스타일 |\n * | `dropzone/container/icon-*` | 아이콘 영역 스타일 |\n *\n * ## State 매핑\n * | UI 상태 | 조건 | 스타일 변화 |\n * |---------|------|------------|\n * | 기본 | - | dashed border |\n * | 드래그 중 | isDragActive | solid border |\n * | 비활성 | disabled | opacity 감소 |\n *\n * ## CSS Variables\n * ```css\n * --dropzone-common-radius | padding-x | padding-y | gap | bg | border | border-width\n * --dropzone-container-content-gap | icon-bg | icon-color | icon-radius\n * --dropzone-container-content-title | description\n * ```\n *\n * ## 서브 컴포넌트 구조\n * ```\n * CsDropzone (Root, react-dropzone 연동)\n * ├── CsDropzoneEmptyState (src 없을 때)\n * └── CsDropzoneContent (src 있을 때)\n * ```\n *\n * ## Props 옵션\n * | prop | 설명 |\n * |------|------|\n * | accept | 허용 MIME 타입 (e.g., `{ \"image/*\": [\".png\", \".jpg\"] }`) |\n * | maxSize | 최대 파일 크기 (bytes) |\n * | minSize | 최소 파일 크기 (bytes) |\n * | maxFiles | 최대 파일 개수 |\n * | src | 현재 업로드된 파일 목록 |\n *\n * @see {@link https://react-dropzone.js.org/ | react-dropzone}\n */\n\n/**\n * CS Design System 드롭존 컴포넌트\n *\n * 사용자가 파일을 드래그 앤 드롭하거나 클릭하여 업로드할 수 있는 영역.\n * 프로필 이미지, 문서 첨부, 파일 업로드 등에 사용됩니다.\n * react-dropzone 기반으로 파일 유효성 검사(타입, 크기)를 지원합니다.\n *\n * ## 사용 시나리오\n * - 프로필 이미지 업로드: 이미지 파일만 허용, 크기 제한\n * - 문서 첨부: PDF, DOC 등 특정 형식만 허용\n * - 다중 파일 업로드: 갤러리, 첨부파일 여러 개\n * - 드래그 앤 드롭: 대용량 파일 편리한 업로드\n * - 파일 미리보기: 업로드 전 파일 목록 표시\n *\n * @example 기본\n * ```tsx\n * <CsDropzone onDrop={handleFiles} src={files}>\n * <CsDropzoneEmptyState />\n * <CsDropzoneContent />\n * </CsDropzone>\n * ```\n *\n * @example 이미지만 허용 (5MB 제한)\n * ```tsx\n * <CsDropzone\n * accept={{ \"image/*\": [\".png\", \".jpg\", \".jpeg\"] }}\n * maxSize={5 * 1024 * 1024}\n * onDrop={handleFiles}\n * src={files}\n * >\n * <CsDropzoneEmptyState />\n * <CsDropzoneContent />\n * </CsDropzone>\n * ```\n *\n * @example 다중 파일\n * ```tsx\n * <CsDropzone maxFiles={5} onDrop={handleFiles} src={files}>\n * <CsDropzoneEmptyState />\n * <CsDropzoneContent />\n * </CsDropzone>\n * ```\n */\ntype CsDropzoneContextType = {\n src?: File[];\n accept?: DropzoneOptions['accept'];\n maxSize?: DropzoneOptions['maxSize'];\n minSize?: DropzoneOptions['minSize'];\n maxFiles?: DropzoneOptions['maxFiles'];\n};\n\nconst renderBytes = (bytes: number) => {\n const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n let size = bytes;\n let unitIndex = 0;\n\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024;\n unitIndex++;\n }\n\n return `${size.toFixed(2)}${units[unitIndex]}`;\n};\n\nconst CsDropzoneContext = createContext<CsDropzoneContextType | undefined>(\n undefined\n);\n\nexport type CsDropzoneProps = Omit<DropzoneOptions, 'onDrop'> & {\n src?: File[];\n className?: string;\n onDrop?: (\n acceptedFiles: File[],\n fileRejections: FileRejection[],\n event: DropEvent\n ) => void;\n children?: ReactNode;\n};\n\nexport const CsDropzone = ({\n accept,\n maxFiles = 1,\n maxSize,\n minSize,\n onDrop,\n onError,\n disabled,\n src,\n className,\n children,\n ...props\n}: CsDropzoneProps) => {\n const { getRootProps, getInputProps, isDragActive } = useDropzone({\n accept,\n maxFiles,\n maxSize,\n minSize,\n onError,\n disabled,\n onDrop: (acceptedFiles, fileRejections, event) => {\n if (fileRejections.length > 0) {\n const message = fileRejections.at(0)?.errors.at(0)?.message;\n onError?.(new Error(message));\n return;\n }\n\n onDrop?.(acceptedFiles, fileRejections, event);\n },\n ...props,\n });\n\n return (\n <CsDropzoneContext.Provider\n key={JSON.stringify(src)}\n value={{ src, accept, maxSize, minSize, maxFiles }}\n >\n <CsButton\n className={cn(\n 'relative h-auto w-full flex-col overflow-hidden border-dashed',\n 'rounded-(--dropzone-common-radius) px-(--dropzone-common-padding-x) py-(--dropzone-common-padding-y) gap-(--dropzone-common-gap)',\n 'bg-(--dropzone-common-bg) hover:bg-(--dropzone-common-bg) border-(--dropzone-common-border) border-(length:--dropzone-common-border-width)',\n isDragActive && 'outline-none border-solid',\n className\n )}\n disabled={disabled}\n type=\"button\"\n variant=\"outline\"\n {...getRootProps()}\n >\n <input {...getInputProps()} disabled={disabled} />\n {children}\n </CsButton>\n </CsDropzoneContext.Provider>\n );\n};\n\nconst useDropzoneContext = () => {\n const context = useContext(CsDropzoneContext);\n\n if (!context) {\n throw new Error('useDropzoneContext must be used within a Dropzone');\n }\n\n return context;\n};\n\n/**\n * CsDropzoneContent Props\n */\nexport type CsDropzoneContentProps = {\n /** 커스텀 콘텐츠 (없으면 기본 파일 목록 표시) */\n children?: ReactNode;\n className?: string;\n};\n\nconst maxLabelItems = 3;\n\n/**\n * 드롭존 콘텐츠 영역 (파일 있을 때)\n *\n * src에 파일이 있을 때만 렌더링.\n * children 없으면 기본 파일 목록 UI 표시.\n *\n * @example 기본 (자동 파일 목록)\n * ```tsx\n * <CsDropzoneContent />\n * ```\n *\n * @example 커스텀 콘텐츠\n * ```tsx\n * <CsDropzoneContent>\n * <MyCustomFileList files={files} />\n * </CsDropzoneContent>\n * ```\n */\nexport const CsDropzoneContent = ({\n children,\n className,\n}: CsDropzoneContentProps) => {\n const { src } = useDropzoneContext();\n\n if (!src) {\n return null;\n }\n\n if (children) {\n return children;\n }\n\n return (\n <div className={cn('flex flex-col items-center justify-center gap-(--dropzone-container-content-gap)', className)}>\n <div className=\"flex size-8 items-center justify-center bg-(--dropzone-container-icon-bg) text-(--dropzone-container-icon-color) rounded-(--dropzone-container-icon-radius)\">\n <UploadIcon size={16} />\n </div>\n <p className=\"w-full truncate typo-body-sm text-(--dropzone-container-content-title)\">\n {src.length > maxLabelItems\n ? `${new Intl.ListFormat('en').format(\n src.slice(0, maxLabelItems).map((file) => file.name)\n )} and ${src.length - maxLabelItems} more`\n : new Intl.ListFormat('en').format(src.map((file) => file.name))}\n </p>\n <p className=\"w-full text-wrap typo-body-xs text-(--dropzone-container-content-description)\">\n Drag and drop or click to replace\n </p>\n </div>\n );\n};\n\n/**\n * CsDropzoneEmptyState Props\n */\nexport type CsDropzoneEmptyStateProps = {\n /** 커스텀 콘텐츠 (없으면 기본 안내 UI 표시) */\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * 드롭존 빈 상태 영역 (파일 없을 때)\n *\n * src에 파일이 없을 때만 렌더링.\n * children 없으면 accept, maxSize 등 제약조건 기반 안내 UI 자동 생성.\n *\n * @example 기본 (자동 안내 메시지)\n * ```tsx\n * <CsDropzoneEmptyState />\n * ```\n *\n * @example 커스텀 콘텐츠\n * ```tsx\n * <CsDropzoneEmptyState>\n * <p>이미지를 여기에 드래그하세요</p>\n * </CsDropzoneEmptyState>\n * ```\n */\nexport const CsDropzoneEmptyState = ({\n children,\n className,\n}: CsDropzoneEmptyStateProps) => {\n const { src, accept, maxSize, minSize, maxFiles } = useDropzoneContext();\n\n if (src) {\n return null;\n }\n\n if (children) {\n return children;\n }\n\n let caption = '';\n\n if (accept) {\n caption += 'Accepts ';\n caption += new Intl.ListFormat('en').format(Object.keys(accept));\n }\n\n if (minSize && maxSize) {\n caption += ` between ${renderBytes(minSize)} and ${renderBytes(maxSize)}`;\n } else if (minSize) {\n caption += ` at least ${renderBytes(minSize)}`;\n } else if (maxSize) {\n caption += ` less than ${renderBytes(maxSize)}`;\n }\n\n return (\n <div className={cn('flex flex-col items-center justify-center gap-(--dropzone-container-content-gap)', className)}>\n <div className=\"flex size-8 items-center justify-center bg-(--dropzone-container-icon-bg) text-(--dropzone-container-icon-color) rounded-(--dropzone-container-icon-radius)\">\n <UploadIcon size={16} />\n </div>\n <p className=\"w-full truncate text-wrap typo-body-sm text-(--dropzone-container-content-title)\">\n Upload {maxFiles === 1 ? 'a file' : 'files'}\n </p>\n <p className=\"w-full truncate text-wrap typo-body-xs text-(--dropzone-container-content-description)\">\n Drag and drop or click to upload\n </p>\n {caption && (\n <p className=\"text-wrap typo-body-xs text-(--dropzone-container-content-description)\">{caption}.</p>\n )}\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
function CsEmpty({ className, ...props }) {
|
|
5
|
+
return /* @__PURE__ */ jsx(
|
|
6
|
+
"div",
|
|
7
|
+
{
|
|
8
|
+
"data-slot": "empty",
|
|
9
|
+
className: cn(
|
|
10
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center text-center text-balance",
|
|
11
|
+
"gap-(--empty-common-gap) px-(--empty-common-padding-x) py-(--empty-common-padding-y) rounded-(--empty-common-radius)",
|
|
12
|
+
className
|
|
13
|
+
),
|
|
14
|
+
...props
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function CsEmptyHeader({ className, ...props }) {
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
"div",
|
|
21
|
+
{
|
|
22
|
+
"data-slot": "empty-header",
|
|
23
|
+
className: cn(
|
|
24
|
+
"flex max-w-sm flex-col items-center text-center",
|
|
25
|
+
"gap-(--empty-container-content-gap)",
|
|
26
|
+
className
|
|
27
|
+
),
|
|
28
|
+
...props
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const csEmptyMediaVariants = cva(
|
|
33
|
+
"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
34
|
+
{
|
|
35
|
+
variants: {
|
|
36
|
+
variant: {
|
|
37
|
+
default: "bg-transparent",
|
|
38
|
+
icon: "flex size-10 shrink-0 items-center justify-center [&_svg:not([class*='size-'])]:size-6 bg-(--empty-container-content-icon-bg) text-(--empty-container-content-icon) rounded-(--empty-common-radius)"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
defaultVariants: {
|
|
42
|
+
variant: "default"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
function CsEmptyMedia({
|
|
47
|
+
className,
|
|
48
|
+
variant = "default",
|
|
49
|
+
...props
|
|
50
|
+
}) {
|
|
51
|
+
return /* @__PURE__ */ jsx(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
"data-slot": "empty-icon",
|
|
55
|
+
"data-variant": variant,
|
|
56
|
+
className: cn(csEmptyMediaVariants({ variant, className })),
|
|
57
|
+
...props
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function CsEmptyTitle({ className, ...props }) {
|
|
62
|
+
return /* @__PURE__ */ jsx(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
"data-slot": "empty-title",
|
|
66
|
+
className: cn("text-(--empty-container-content-title) typo-heading-sm", className),
|
|
67
|
+
...props
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
function CsEmptyDescription({ className, ...props }) {
|
|
72
|
+
return /* @__PURE__ */ jsx(
|
|
73
|
+
"div",
|
|
74
|
+
{
|
|
75
|
+
"data-slot": "empty-description",
|
|
76
|
+
className: cn(
|
|
77
|
+
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 text-(--empty-container-content-description) typo-body-sm",
|
|
78
|
+
className
|
|
79
|
+
),
|
|
80
|
+
...props
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
function CsEmptyContent({ className, ...props }) {
|
|
85
|
+
return /* @__PURE__ */ jsx(
|
|
86
|
+
"div",
|
|
87
|
+
{
|
|
88
|
+
"data-slot": "empty-content",
|
|
89
|
+
className: cn(
|
|
90
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center text-balance",
|
|
91
|
+
"gap-(--empty-container-footer-gap)",
|
|
92
|
+
className
|
|
93
|
+
),
|
|
94
|
+
...props
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
CsEmpty,
|
|
100
|
+
CsEmptyContent,
|
|
101
|
+
CsEmptyDescription,
|
|
102
|
+
CsEmptyHeader,
|
|
103
|
+
CsEmptyMedia,
|
|
104
|
+
CsEmptyTitle,
|
|
105
|
+
csEmptyMediaVariants
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=cs-empty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cs-empty.js","sources":["../../../src/components/empty/cs-empty.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"../../lib/utils\"\n\n/**\n * CsEmpty 스타일 시스템 (Design Token Reference)\n *\n * ## Figma Token → Props 매핑\n * | Figma 토큰 패턴 | 컴포넌트 |\n * |----------------|---------|\n * | `empty/common/*` | `CsEmpty` (컨테이너) |\n * | `empty/container/content-*` | `CsEmptyHeader`, `CsEmptyMedia`, `CsEmptyTitle`, `CsEmptyDescription` |\n * | `empty/container/footer-*` | `CsEmptyContent` |\n *\n * ## CSS Variables\n * ```css\n * --empty-common-gap | padding-x | padding-y | radius\n * --empty-container-content-gap | content-icon-bg | content-icon | content-title | content-description\n * --empty-container-footer-gap\n * ```\n *\n * ## 서브 컴포넌트 구조\n * ```\n * CsEmpty (Root)\n * ├── CsEmptyHeader\n * │ ├── CsEmptyMedia (아이콘/이미지)\n * │ ├── CsEmptyTitle\n * │ └── CsEmptyDescription\n * └── CsEmptyContent (액션 버튼 등)\n * ```\n */\n\n/**\n * CS Design System 빈 상태 컴포넌트\n *\n * 데이터가 없는 상태를 표시하는 컴포넌트.\n * 아이콘/이미지, 제목, 설명, 액션 버튼을 조합하여 사용자에게 빈 상태를 안내합니다.\n * 서브 컴포넌트(CsEmptyHeader, CsEmptyMedia, CsEmptyTitle 등)를 조합하여 구성합니다.\n *\n * ## 사용 시나리오\n * - 검색 결과 없음: \"검색 결과가 없습니다\" + 검색어 수정 안내\n * - 빈 리스트: \"아직 항목이 없습니다\" + \"항목 추가\" 버튼\n * - 빈 폴더/디렉토리: \"폴더가 비어 있습니다\" + 업로드 안내\n * - 필터 결과 없음: \"필터 조건에 맞는 항목이 없습니다\" + 필터 초기화\n * - 빈 대시보드: \"데이터가 아직 없습니다\" + 시작 가이드\n * - 아이콘 표시: `CsEmptyMedia variant=\"icon\"` - 둥근 배경 + 아이콘\n * - 이미지 표시: `CsEmptyMedia` - 일러스트, 이미지 포함\n *\n * @example 기본 조합\n * ```tsx\n * <CsEmpty>\n * <CsEmptyHeader>\n * <CsEmptyMedia variant=\"icon\"><Inbox /></CsEmptyMedia>\n * <CsEmptyTitle>데이터가 없습니다</CsEmptyTitle>\n * <CsEmptyDescription>아직 등록된 항목이 없습니다.</CsEmptyDescription>\n * </CsEmptyHeader>\n * <CsEmptyContent>\n * <CsButton>항목 추가</CsButton>\n * </CsEmptyContent>\n * </CsEmpty>\n * ```\n *\n * @example 이미지 포함\n * ```tsx\n * <CsEmpty>\n * <CsEmptyHeader>\n * <CsEmptyMedia><img src=\"/empty.svg\" alt=\"\" /></CsEmptyMedia>\n * <CsEmptyTitle>검색 결과 없음</CsEmptyTitle>\n * </CsEmptyHeader>\n * </CsEmpty>\n * ```\n */\n/** 빈 상태 루트 컴포넌트 */\nfunction CsEmpty({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty\"\n className={cn(\n \"flex min-w-0 flex-1 flex-col items-center justify-center text-center text-balance\",\n \"gap-(--empty-common-gap) px-(--empty-common-padding-x) py-(--empty-common-padding-y) rounded-(--empty-common-radius)\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** 빈 상태 헤더 - 아이콘, 제목, 설명 영역 */\nfunction CsEmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-header\"\n className={cn(\n \"flex max-w-sm flex-col items-center text-center\",\n \"gap-(--empty-container-content-gap)\",\n className\n )}\n {...props}\n />\n )\n}\n\n/**\n * CsEmptyMedia Props\n */\ntype CsEmptyMediaProps = React.ComponentProps<\"div\"> & VariantProps<typeof csEmptyMediaVariants> & {\n /**\n * 미디어 표시 스타일\n * - `default`: 기본 (이미지/커스텀)\n * - `icon`: 아이콘 배경 스타일\n * @default 'default'\n */\n variant?: 'default' | 'icon'\n}\n\nconst csEmptyMediaVariants = cva(\n \"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n icon: \"flex size-10 shrink-0 items-center justify-center [&_svg:not([class*='size-'])]:size-6 bg-(--empty-container-content-icon-bg) text-(--empty-container-content-icon) rounded-(--empty-common-radius)\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\n/** 빈 상태 미디어 - 아이콘 또는 이미지 표시 */\nfunction CsEmptyMedia({\n className,\n variant = \"default\",\n ...props\n}: CsEmptyMediaProps) {\n return (\n <div\n data-slot=\"empty-icon\"\n data-variant={variant}\n className={cn(csEmptyMediaVariants({ variant, className }))}\n {...props}\n />\n )\n}\n\n/** 빈 상태 제목 */\nfunction CsEmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-title\"\n className={cn(\"text-(--empty-container-content-title) typo-heading-sm\", className)}\n {...props}\n />\n )\n}\n\n/** 빈 상태 설명 */\nfunction CsEmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <div\n data-slot=\"empty-description\"\n className={cn(\n \"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 text-(--empty-container-content-description) typo-body-sm\",\n className\n )}\n {...props}\n />\n )\n}\n\n/** 빈 상태 액션 영역 */\nfunction CsEmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-content\"\n className={cn(\n \"flex w-full max-w-sm min-w-0 flex-col items-center text-balance\",\n \"gap-(--empty-container-footer-gap)\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n CsEmpty,\n CsEmptyHeader,\n CsEmptyTitle,\n CsEmptyDescription,\n CsEmptyContent,\n CsEmptyMedia,\n csEmptyMediaVariants,\n type CsEmptyMediaProps,\n}\n"],"names":[],"mappings":";;;AAyEA,SAAS,QAAQ,EAAE,WAAW,GAAG,SAAsC;AACrE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAGA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAsC;AAC3E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAeA,MAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,IAAA;AAAA,EACX;AAEJ;AAGA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA,UAAU;AAAA,EACV,GAAG;AACL,GAAsB;AACpB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,qBAAqB,EAAE,SAAS,UAAA,CAAW,CAAC;AAAA,MACzD,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAGA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;AAC1E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,0DAA0D,SAAS;AAAA,MAChF,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAGA,SAAS,mBAAmB,EAAE,WAAW,GAAG,SAAoC;AAC9E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAGA,SAAS,eAAe,EAAE,WAAW,GAAG,SAAsC;AAC5E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { CsSeparator } from "../separator/cs-separator.js";
|
|
7
|
+
import { CsLabel } from "../label/cs-label.js";
|
|
8
|
+
function CsFieldSet({ className, ...props }) {
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
"fieldset",
|
|
11
|
+
{
|
|
12
|
+
"data-slot": "field-set",
|
|
13
|
+
className: cn(
|
|
14
|
+
"flex flex-col gap-(--spacing-component-gap-lg)",
|
|
15
|
+
"has-[>[data-slot=checkbox-group]]:gap-(--spacing-component-gap-md) has-[>[data-slot=radio-group]]:gap-(--spacing-component-gap-md)",
|
|
16
|
+
className
|
|
17
|
+
),
|
|
18
|
+
...props
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function CsFieldLegend({
|
|
23
|
+
className,
|
|
24
|
+
variant = "legend",
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
"legend",
|
|
29
|
+
{
|
|
30
|
+
"data-slot": "field-legend",
|
|
31
|
+
"data-variant": variant,
|
|
32
|
+
className: cn(
|
|
33
|
+
"mb-3 font-medium",
|
|
34
|
+
"data-[variant=legend]:text-base",
|
|
35
|
+
"data-[variant=label]:text-sm",
|
|
36
|
+
className
|
|
37
|
+
),
|
|
38
|
+
...props
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function CsFieldGroup({ className, ...props }) {
|
|
43
|
+
return /* @__PURE__ */ jsx(
|
|
44
|
+
"div",
|
|
45
|
+
{
|
|
46
|
+
"data-slot": "field-group",
|
|
47
|
+
className: cn(
|
|
48
|
+
"group/field-group @container/field-group flex w-full flex-col gap-(--spacing-component-gap-lg) data-[slot=checkbox-group]:gap-(--spacing-component-gap-md) *:data-[slot=field-group]:gap-(--spacing-component-gap-md)",
|
|
49
|
+
className
|
|
50
|
+
),
|
|
51
|
+
...props
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const csFieldVariants = cva(
|
|
56
|
+
"group/field flex w-full gap-(--input-common-group-gap) data-[invalid=true]:text-(--color-state-error-default)",
|
|
57
|
+
{
|
|
58
|
+
variants: {
|
|
59
|
+
orientation: {
|
|
60
|
+
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
|
|
61
|
+
horizontal: [
|
|
62
|
+
"flex-row items-center",
|
|
63
|
+
"[&>[data-slot=field-label]]:flex-auto",
|
|
64
|
+
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
|
|
65
|
+
],
|
|
66
|
+
responsive: [
|
|
67
|
+
"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
|
|
68
|
+
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
|
|
69
|
+
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
defaultVariants: {
|
|
74
|
+
orientation: "vertical"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
function CsField({
|
|
79
|
+
className,
|
|
80
|
+
orientation = "vertical",
|
|
81
|
+
...props
|
|
82
|
+
}) {
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
"div",
|
|
85
|
+
{
|
|
86
|
+
role: "group",
|
|
87
|
+
"data-slot": "field",
|
|
88
|
+
"data-orientation": orientation,
|
|
89
|
+
className: cn(csFieldVariants({ orientation }), className),
|
|
90
|
+
...props
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function CsFieldContent({ className, ...props }) {
|
|
95
|
+
return /* @__PURE__ */ jsx(
|
|
96
|
+
"div",
|
|
97
|
+
{
|
|
98
|
+
"data-slot": "field-content",
|
|
99
|
+
className: cn(
|
|
100
|
+
"group/field-content flex flex-1 flex-col gap-(--input-common-group-gap) leading-snug",
|
|
101
|
+
className
|
|
102
|
+
),
|
|
103
|
+
...props
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function CsFieldLabel({
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}) {
|
|
111
|
+
return /* @__PURE__ */ jsx(
|
|
112
|
+
CsLabel,
|
|
113
|
+
{
|
|
114
|
+
"data-slot": "field-label",
|
|
115
|
+
className,
|
|
116
|
+
...props
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
function CsFieldTitle({ className, ...props }) {
|
|
121
|
+
return /* @__PURE__ */ jsx(
|
|
122
|
+
"div",
|
|
123
|
+
{
|
|
124
|
+
"data-slot": "field-label",
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function CsFieldDescription({ className, ...props }) {
|
|
131
|
+
return /* @__PURE__ */ jsx(
|
|
132
|
+
"p",
|
|
133
|
+
{
|
|
134
|
+
"data-slot": "field-description",
|
|
135
|
+
className: cn("typo-productive-caption text-(--font-color-secondary-default)", className),
|
|
136
|
+
...props
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
function CsFieldSeparator({
|
|
141
|
+
children,
|
|
142
|
+
className,
|
|
143
|
+
...props
|
|
144
|
+
}) {
|
|
145
|
+
return /* @__PURE__ */ jsxs(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
"data-slot": "field-separator",
|
|
149
|
+
"data-content": !!children,
|
|
150
|
+
className: cn(
|
|
151
|
+
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
|
|
152
|
+
className
|
|
153
|
+
),
|
|
154
|
+
...props,
|
|
155
|
+
children: [
|
|
156
|
+
/* @__PURE__ */ jsx(CsSeparator, { className: "absolute inset-0 top-1/2" }),
|
|
157
|
+
children && /* @__PURE__ */ jsx(
|
|
158
|
+
"span",
|
|
159
|
+
{
|
|
160
|
+
className: "bg-background text-muted-foreground relative mx-auto block w-fit px-2",
|
|
161
|
+
"data-slot": "field-separator-content",
|
|
162
|
+
children
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
function CsFieldError({
|
|
170
|
+
className,
|
|
171
|
+
children,
|
|
172
|
+
errors,
|
|
173
|
+
...props
|
|
174
|
+
}) {
|
|
175
|
+
const content = useMemo(() => {
|
|
176
|
+
if (children) {
|
|
177
|
+
return children;
|
|
178
|
+
}
|
|
179
|
+
if (!errors?.length) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const uniqueErrors = [
|
|
183
|
+
...new Map(errors.map((error) => [error?.message, error])).values()
|
|
184
|
+
];
|
|
185
|
+
if (uniqueErrors?.length == 1) {
|
|
186
|
+
return uniqueErrors[0]?.message;
|
|
187
|
+
}
|
|
188
|
+
return /* @__PURE__ */ jsx("ul", { className: "flex list-disc flex-col gap-(--label-gap)", children: uniqueErrors.map(
|
|
189
|
+
(error, index) => error?.message && /* @__PURE__ */ jsx("li", { children: error.message }, index)
|
|
190
|
+
) });
|
|
191
|
+
}, [children, errors]);
|
|
192
|
+
if (!content) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return /* @__PURE__ */ jsx(
|
|
196
|
+
"div",
|
|
197
|
+
{
|
|
198
|
+
role: "alert",
|
|
199
|
+
"data-slot": "field-error",
|
|
200
|
+
className: cn("text-(--input-common-helpertext) typo-productive-caption", className),
|
|
201
|
+
...props,
|
|
202
|
+
children: content
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
CsField,
|
|
208
|
+
CsFieldContent,
|
|
209
|
+
CsFieldDescription,
|
|
210
|
+
CsFieldError,
|
|
211
|
+
CsFieldGroup,
|
|
212
|
+
CsFieldLabel,
|
|
213
|
+
CsFieldLegend,
|
|
214
|
+
CsFieldSeparator,
|
|
215
|
+
CsFieldSet,
|
|
216
|
+
CsFieldTitle
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=cs-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cs-field.js","sources":["../../../src/components/field/cs-field.tsx"],"sourcesContent":["\"use client\"\n\nimport { useMemo } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"../../lib/utils\"\nimport { CsLabel } from \"../label\"\nimport { CsSeparator } from \"../separator/cs-separator\"\n\n/**\n * CsField 스타일 시스템 (Design Token Reference)\n *\n * ## Figma Token → Props 매핑\n * | Figma 토큰 패턴 | 컴포넌트/Props |\n * |----------------|---------------|\n * | `input/common/group-gap` | `CsField`, `CsFieldContent` 내부 간격 |\n * | `input/common/helpertext` | `CsFieldError` 색상 |\n * | `spacing/component/gap-lg` | `CsFieldGroup`, `CsFieldSet` 간격 |\n * | `spacing/component/gap-md` | checkbox/radio 그룹 간격 |\n *\n * ## State 매핑\n * | UI 상태 | Token state | 적용 |\n * |---------|-------------|------|\n * | 유효성 실패 | - | `data-invalid=\"true\"` → 에러 색상 |\n *\n * ## Orientation 옵션\n * | 값 | 설명 |\n * |---|------|\n * | `vertical` | 라벨 위, 입력 아래 (기본) |\n * | `horizontal` | 라벨 좌측, 입력 우측 |\n * | `responsive` | 모바일: vertical, 데스크탑: horizontal |\n *\n * ## CSS Variables\n * ```css\n * --input-common-group-gap (필드 내부 간격)\n * --input-common-helpertext (에러 메시지 색상)\n * --label-gap (에러 리스트 간격)\n * --spacing-component-gap-lg | gap-md\n * ```\n *\n * ## 서브 컴포넌트 구조\n * ```\n * CsFieldSet (선택, fieldset 요소)\n * └── CsFieldLegend (선택)\n * CsFieldGroup\n * ├── CsField\n * │ ├── CsFieldLabel / CsFieldTitle\n * │ ├── CsFieldContent\n * │ │ ├── <입력 컴포넌트>\n * │ │ └── CsFieldError\n * │ └── CsFieldDescription (선택)\n * └── CsFieldSeparator (선택)\n * ```\n */\n\n/**\n * 폼 필드셋 컨테이너\n *\n * 의미론적으로 관련 필드를 그룹화하는 fieldset 요소.\n * checkbox/radio 그룹 포함 시 자동으로 간격 조정.\n *\n * @example\n * ```tsx\n * <CsFieldSet>\n * <CsFieldLegend>개인정보</CsFieldLegend>\n * <CsFieldGroup>...</CsFieldGroup>\n * </CsFieldSet>\n * ```\n */\nfunction CsFieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n return (\n <fieldset\n data-slot=\"field-set\"\n className={cn(\n \"flex flex-col gap-(--spacing-component-gap-lg)\",\n \"has-[>[data-slot=checkbox-group]]:gap-(--spacing-component-gap-md) has-[>[data-slot=radio-group]]:gap-(--spacing-component-gap-md)\",\n className\n )}\n {...props}\n />\n )\n}\n\n/**\n * CsFieldLegend Props\n */\ntype CsFieldLegendProps = React.ComponentProps<\"legend\"> & {\n /**\n * 레전드 스타일 변형\n * - `legend`: 큰 텍스트 (섹션 제목용)\n * - `label`: 작은 텍스트 (라벨 스타일)\n * @default 'legend'\n */\n variant?: \"legend\" | \"label\"\n}\n\n/**\n * 필드셋 레전드 (제목)\n *\n * CsFieldSet 내에서 그룹 제목으로 사용.\n */\nfunction CsFieldLegend({\n className,\n variant = \"legend\",\n ...props\n}: CsFieldLegendProps) {\n return (\n <legend\n data-slot=\"field-legend\"\n data-variant={variant}\n className={cn(\n \"mb-3 font-medium\",\n \"data-[variant=legend]:text-base\",\n \"data-[variant=label]:text-sm\",\n className\n )}\n {...props}\n />\n )\n}\n\n/**\n * 필드 그룹 컨테이너\n *\n * 여러 CsField를 수직으로 배치.\n * Container Query(@container) 지원으로 반응형 레이아웃 가능.\n *\n * @example\n * ```tsx\n * <CsFieldGroup>\n * <CsField>...</CsField>\n * <CsField>...</CsField>\n * </CsFieldGroup>\n * ```\n */\nfunction CsFieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n \"group/field-group @container/field-group flex w-full flex-col gap-(--spacing-component-gap-lg) data-[slot=checkbox-group]:gap-(--spacing-component-gap-md) *:data-[slot=field-group]:gap-(--spacing-component-gap-md)\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst csFieldVariants = cva(\n \"group/field flex w-full gap-(--input-common-group-gap) data-[invalid=true]:text-(--color-state-error-default)\",\n {\n variants: {\n orientation: {\n vertical: [\"flex-col [&>*]:w-full [&>.sr-only]:w-auto\"],\n horizontal: [\n \"flex-row items-center\",\n \"[&>[data-slot=field-label]]:flex-auto\",\n \"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n ],\n responsive: [\n \"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto\",\n \"@md/field-group:[&>[data-slot=field-label]]:flex-auto\",\n \"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n ],\n },\n },\n defaultVariants: {\n orientation: \"vertical\",\n },\n }\n)\n\n/**\n * CsField Props\n */\ntype CsFieldProps = React.ComponentProps<\"div\"> & {\n /**\n * 라벨과 입력 필드의 배치 방향\n * - `vertical`: 라벨 위, 입력 아래 (기본, 모바일 최적화)\n * - `horizontal`: 라벨 좌측, 입력 우측 (데스크탑 폼)\n * - `responsive`: 모바일은 vertical, 데스크탑은 horizontal\n * @default 'vertical'\n */\n orientation?: 'vertical' | 'horizontal' | 'responsive'\n}\n\n/**\n * CS Design System 폼 필드 컨테이너\n *\n * 라벨, 입력 필드, 에러 메시지를 하나의 단위로 묶어주는 컴포넌트.\n * 폼 레이아웃을 일관되게 유지하고, 라벨-입력 관계를 명확하게 합니다.\n * orientation으로 수직/수평 레이아웃을 제어합니다.\n *\n * ## 사용 시나리오\n * - 폼 입력 필드: 라벨 + 입력 + 에러 메시지 조합\n * - 수직 레이아웃: 모바일 폼, 좁은 공간 (`orientation=\"vertical\"`)\n * - 수평 레이아웃: 데스크탑 폼, 넓은 공간 (`orientation=\"horizontal\"`)\n * - 반응형 폼: 모바일은 수직, 데스크탑은 수평 (`orientation=\"responsive\"`)\n * - 에러 표시: `data-invalid=\"true\"`로 에러 상태 표시\n *\n * @example 기본 (수직)\n * ```tsx\n * <CsField>\n * <CsFieldLabel>이름</CsFieldLabel>\n * <CsFieldContent>\n * <CsInput />\n * </CsFieldContent>\n * </CsField>\n * ```\n *\n * @example 수평 레이아웃\n * ```tsx\n * <CsField orientation=\"horizontal\">\n * <CsFieldLabel>이메일</CsFieldLabel>\n * <CsFieldContent>\n * <CsInput type=\"email\" />\n * </CsFieldContent>\n * </CsField>\n * ```\n */\nfunction CsField({\n className,\n orientation = \"vertical\",\n ...props\n}: CsFieldProps) {\n return (\n <div\n role=\"group\"\n data-slot=\"field\"\n data-orientation={orientation}\n className={cn(csFieldVariants({ orientation }), className)}\n {...props}\n />\n )\n}\n\n/**\n * 필드 콘텐츠 영역\n *\n * 입력 컴포넌트와 에러 메시지를 담는 컨테이너.\n * 내부 요소 간 간격 자동 적용.\n */\nfunction CsFieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n \"group/field-content flex flex-1 flex-col gap-(--input-common-group-gap) leading-snug\",\n className\n )}\n {...props}\n />\n )\n}\n\n/**\n * 필드 라벨\n *\n * CsLabel 기반. 입력 필드와 연결된 라벨 요소.\n * htmlFor 속성으로 입력 필드와 연결.\n */\nfunction CsFieldLabel({\n className,\n ...props\n}: React.ComponentProps<typeof CsLabel>) {\n return (\n <CsLabel\n data-slot=\"field-label\"\n className={className}\n {...props}\n />\n )\n}\n\n/**\n * 필드 제목 (비라벨)\n *\n * label 요소 대신 div로 렌더링. checkbox/radio 그룹 등에서 사용.\n */\nfunction CsFieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-label\"\n className={className}\n {...props}\n />\n )\n}\n\n/**\n * 필드 설명 텍스트\n *\n * 입력 필드에 대한 추가 안내 텍스트.\n */\nfunction CsFieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <p\n data-slot=\"field-description\"\n className={cn(\"typo-productive-caption text-(--font-color-secondary-default)\", className)}\n {...props}\n />\n )\n}\n\n/**\n * 필드 구분선\n *\n * 필드 그룹 내에서 섹션을 구분하는 선.\n * children으로 텍스트 전달 시 구분선 중앙에 표시.\n *\n * @example\n * ```tsx\n * <CsFieldSeparator />\n * <CsFieldSeparator>또는</CsFieldSeparator>\n * ```\n */\nfunction CsFieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<\"div\"> & {\n children?: React.ReactNode\n}) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn(\n \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\n className\n )}\n {...props}\n >\n <CsSeparator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"bg-background text-muted-foreground relative mx-auto block w-fit px-2\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\n/**\n * CsFieldError Props\n */\ntype CsFieldErrorProps = React.ComponentProps<\"div\"> & {\n /**\n * 에러 객체 배열 (react-hook-form 등과 호환)\n * 중복 메시지 자동 제거, 단일 에러는 텍스트로, 다중 에러는 리스트로 표시\n */\n errors?: Array<{ message?: string } | undefined>\n}\n\n/**\n * 필드 에러 메시지\n *\n * 유효성 검사 실패 시 에러 메시지 표시.\n * errors prop 또는 children으로 내용 전달.\n *\n * @example react-hook-form 연동\n * ```tsx\n * <CsFieldError errors={[formState.errors.email]} />\n * ```\n *\n * @example 직접 메시지 전달\n * ```tsx\n * <CsFieldError>이메일 형식이 올바르지 않습니다.</CsFieldError>\n * ```\n */\nfunction CsFieldError({\n className,\n children,\n errors,\n ...props\n}: CsFieldErrorProps) {\n const content = useMemo(() => {\n if (children) {\n return children\n }\n\n if (!errors?.length) {\n return null\n }\n\n const uniqueErrors = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n\n if (uniqueErrors?.length == 1) {\n return uniqueErrors[0]?.message\n }\n\n return (\n <ul className=\"flex list-disc flex-col gap-(--label-gap)\">\n {uniqueErrors.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>\n )}\n </ul>\n )\n }, [children, errors])\n\n if (!content) {\n return null\n }\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n className={cn(\"text-(--input-common-helpertext) typo-productive-caption\", className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n CsField,\n CsFieldLabel,\n CsFieldDescription,\n CsFieldError,\n CsFieldGroup,\n CsFieldLegend,\n CsFieldSeparator,\n CsFieldSet,\n CsFieldContent,\n CsFieldTitle,\n type CsFieldProps,\n type CsFieldLegendProps,\n type CsFieldErrorProps,\n}\n"],"names":[],"mappings":"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"}
|