@j-solution/components 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +7 -8
  2. package/assets/jwms-portal-frontend-CrtMPGot.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/components/atoms/JBadge.vue.cjs +1 -1
  5. package/components/atoms/JBadge.vue.cjs.map +1 -1
  6. package/components/atoms/JBadge.vue.js +4 -4
  7. package/components/atoms/JBadge.vue.js.map +1 -1
  8. package/components/atoms/JCombo.vue.cjs +1 -1
  9. package/components/atoms/JCombo.vue.cjs.map +1 -1
  10. package/components/atoms/JCombo.vue.js +1 -1
  11. package/components/atoms/JCombo.vue.js.map +1 -1
  12. package/components/atoms/JEditor.vue.cjs +1 -1
  13. package/components/atoms/JEditor.vue.js +2 -2
  14. package/components/atoms/JEditor.vue2.cjs.map +1 -1
  15. package/components/atoms/JEditor.vue2.js.map +1 -1
  16. package/components/atoms/JSplitter.vue.cjs +1 -1
  17. package/components/atoms/JSplitter.vue.js +1 -1
  18. package/components/atoms/JSplitter.vue2.cjs.map +1 -1
  19. package/components/atoms/JSplitter.vue2.js.map +1 -1
  20. package/components/molecules/JCard.vue.cjs +1 -1
  21. package/components/molecules/JCard.vue.cjs.map +1 -1
  22. package/components/molecules/JCard.vue.js +28 -25
  23. package/components/molecules/JCard.vue.js.map +1 -1
  24. package/components/molecules/JFormField.vue.cjs +1 -1
  25. package/components/molecules/JFormField.vue.js +2 -2
  26. package/components/molecules/JFormField.vue2.cjs +1 -1
  27. package/components/molecules/JFormField.vue2.cjs.map +1 -1
  28. package/components/molecules/JFormField.vue2.js +43 -43
  29. package/components/molecules/JFormField.vue2.js.map +1 -1
  30. package/components/molecules/JTabs.vue.cjs +1 -1
  31. package/components/molecules/JTabs.vue.js +2 -2
  32. package/components/molecules/JTabs.vue2.cjs +1 -1
  33. package/components/molecules/JTabs.vue2.cjs.map +1 -1
  34. package/components/molecules/JTabs.vue2.js +104 -60
  35. package/components/molecules/JTabs.vue2.js.map +1 -1
  36. package/components/shadcn/FieldDescription.vue.cjs +1 -1
  37. package/components/shadcn/FieldDescription.vue.cjs.map +1 -1
  38. package/components/shadcn/FieldDescription.vue.js +1 -1
  39. package/components/shadcn/FieldDescription.vue.js.map +1 -1
  40. package/components/shadcn/FieldError.vue.cjs +1 -1
  41. package/components/shadcn/FieldError.vue.cjs.map +1 -1
  42. package/components/shadcn/FieldError.vue.js +7 -7
  43. package/components/shadcn/FieldError.vue.js.map +1 -1
  44. package/package.json +1 -1
  45. package/types/index.d.ts +2 -0
  46. package/assets/jwms-portal-frontend-Ca90GVOc.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"JTabs.vue2.cjs","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n * \n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n * \n * @example\n * ```vue\n * <JTabs \n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n \n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n // reka-ui 이벤트가 작동하지 않을 경우 직접 처리\n // handleTabValueChange가 이미 처리했으면 중복 방지\n if (isHandlingEvent || tabId === internalActiveId.value) return\n \n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation() // 탭 클릭 이벤트 전파 방지\n emit('tabClose', tabId)\n}\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n tabPaddingClass: string\n tabTextSizeClass: string\n listPaddingClass: string\n}> = {\n default: {\n tabPaddingClass: 'px-2 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n minimal: {\n tabPaddingClass: 'px-1.5 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 탭 리스트 클래스\n * Tabs list classes\n */\nconst listClasses = computed(() => {\n return cn('w-full justify-start', preset.value.listPaddingClass, props.listClass)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"cn('!flex !items-center !gap-2', preset.tabPaddingClass, preset.tabTextSizeClass)\"\n >\n <!-- 탭 아이콘 (있을 경우) / Tab Icon -->\n <JIcon \n v-if=\"tab.icon\" \n :name=\"tab.icon\" \n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n \n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n \n <!-- 닫기 버튼 / Close Button (항상 표시) -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div class=\"flex-1 w-full overflow-auto\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col\"\n >\n <!-- 슬롯 우선 / Slot First -->\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <!-- 동적 컴포넌트 렌더링 / Dynamic Component Rendering -->\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n \n <!-- 기본 콘텐츠 / Default Content -->\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/**\n * 탭 리스트 스타일 - 하단 보더 제거\n * Tab list styles without bottom border\n */\n:deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n background: hsl(var(--background));\n padding: 0 0.25rem;\n gap: 0.25rem;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar) {\n height: 6px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(0, 0, 0, 0.3);\n}\n\n/**\n * 다크모드에서 스크롤바 스타일\n * Scrollbar styles in dark mode\n */\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n/**\n * 탭 버튼 스타일 - 명확한 구분\n * Tab button styles - clear distinction\n */\n:deep([role=\"tab\"]) {\n position: relative;\n padding: 0.25rem 0.5rem;\n min-height: 1.75rem;\n border-radius: 0.375rem 0.375rem 0 0;\n transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n border: 1px solid transparent;\n border-bottom: none;\n}\n\n/**\n * Minimal 스타일 탭 버튼 - JSidebarAdvanced와 높이 맞춤\n */\n:deep([role=\"tablist\"][class*=\"p-0.5\"] [role=\"tab\"]) {\n padding: 0.25rem 0.5rem;\n}\n\n/**\n * 비활성 탭 - 명확하게 구분\n * Inactive tabs - clear distinction\n */\n:deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--muted) / 0.2);\n color: hsl(var(--muted-foreground));\n border-top: 1px solid hsl(var(--border) / 0.4);\n border-left: 1px solid hsl(var(--border) / 0.4);\n border-right: 1px solid hsl(var(--border) / 0.4);\n}\n\n:deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted) / 0.4);\n color: hsl(var(--foreground));\n}\n\n/**\n * 다크모드에서 비활성 탭 - 다크 배경색 사용\n * Inactive tabs in dark mode - use dark background\n */\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--secondary));\n color: hsl(var(--secondary-foreground));\n border-top: 1px solid hsl(var(--border) / 0.5);\n border-left: 1px solid hsl(var(--border) / 0.5);\n border-right: 1px solid hsl(var(--border) / 0.5);\n}\n\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted));\n color: hsl(var(--muted-foreground));\n}\n\n/**\n * 활성 탭 - 강조된 스타일\n * Active tab - emphasized style\n */\n:deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--background));\n color: hsl(var(--foreground));\n font-weight: 500;\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n z-index: 1;\n}\n\n/**\n * 다크모드에서 활성 탭 - 배경색 명확히 구분\n * Active tab in dark mode - clear background distinction\n */\n.dark :deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--card));\n color: hsl(var(--card-foreground));\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","e","rootClasses","cn","STYLE_PRESETS","preset","listClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","_normalizeClass","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","X","_hoisted_3","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5"],"mappings":"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"}
1
+ {"version":3,"file":"JTabs.vue2.cjs","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n *\n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n *\n * @example\n * ```vue\n * <JTabs\n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n\n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n if (isHandlingEvent || tabId === internalActiveId.value) return\n\n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation()\n emit('tabClose', tabId)\n}\n\n// ── Style Presets ──────────────────────────────────────────────────\nconst STYLE_PRESETS: Record<StyleType, {\n list: string\n trigger: string\n active: string\n inactive: string\n content: string\n}> = {\n // Default: Modern Underline 스타일 (Linear/Stripe/GitHub 패턴)\n // 하단 primary indicator + 텍스트 색상 전환 + 플랫 디자인\n default: {\n list: 'w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]',\n trigger: [\n 'relative px-3 py-2 text-xs font-medium',\n 'rounded-none border-b-2 border-transparent -mb-px',\n 'transition-all duration-200',\n 'flex items-center gap-2',\n ].join(' '),\n active: [\n 'data-[state=active]:text-foreground',\n 'data-[state=active]:border-b-primary',\n 'data-[state=active]:bg-transparent',\n 'data-[state=active]:shadow-none',\n ].join(' '),\n inactive: [\n 'data-[state=inactive]:text-muted-foreground',\n 'data-[state=inactive]:bg-transparent',\n 'data-[state=inactive]:hover:text-foreground',\n 'data-[state=inactive]:hover:border-b-muted-foreground/30',\n ].join(' '),\n content: '',\n },\n\n // Minimal: 컴팩트 Underline (사이드바/중첩 탭용)\n // 더 작은 패딩, 밀도 높은 배치\n minimal: {\n list: 'w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[1.75rem]',\n trigger: [\n 'relative px-2 py-1.5 text-xs font-medium',\n 'rounded-none border-b-2 border-transparent -mb-px',\n 'transition-all duration-200',\n 'flex items-center gap-1.5',\n ].join(' '),\n active: [\n 'data-[state=active]:text-foreground',\n 'data-[state=active]:border-b-primary',\n 'data-[state=active]:bg-transparent',\n 'data-[state=active]:shadow-none',\n ].join(' '),\n inactive: [\n 'data-[state=inactive]:text-muted-foreground',\n 'data-[state=inactive]:bg-transparent',\n 'data-[state=inactive]:hover:text-foreground',\n 'data-[state=inactive]:hover:border-b-muted-foreground/30',\n ].join(' '),\n content: '',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n// ── Computed Classes ──────────────────────────────────────────────\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\nconst listClasses = computed(() => {\n return cn(preset.value.list, props.listClass)\n})\n\nconst triggerClasses = computed(() => {\n return cn(\n preset.value.trigger,\n preset.value.active,\n preset.value.inactive,\n )\n})\n\nconst contentWrapperClasses = computed(() => {\n return cn('flex-1 w-full overflow-auto', preset.value.content)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"triggerClasses\"\n >\n <!-- 탭 아이콘 / Tab Icon -->\n <JIcon\n v-if=\"tab.icon\"\n :name=\"tab.icon\"\n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n\n <!-- 닫기 버튼 / Close Button -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div :class=\"contentWrapperClasses\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col data-[state=active]:animate-tab-fade-in\"\n >\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/* ── 스크롤바: Tailwind으로 불가능한 pseudo-element만 `:deep()` 사용 ── */\n:deep([role=\"tablist\"]) {\n scrollbar-width: thin;\n scrollbar-color: hsl(var(--border)) transparent;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar) {\n height: 4px;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar-thumb) {\n background-color: hsl(var(--border));\n border-radius: 2px;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar-thumb:hover) {\n background-color: hsl(var(--muted-foreground) / 0.4);\n}\n\n/* ── 콘텐츠 전환 애니메이션 ── */\n@keyframes tab-fade-in {\n from {\n opacity: 0;\n transform: translateY(2px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n:deep(.animate-tab-fade-in) {\n animation: tab-fade-in 0.15s ease-out;\n}\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","e","STYLE_PRESETS","preset","rootClasses","cn","listClasses","triggerClasses","contentWrapperClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","X","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_3","_hoisted_4"],"mappings":"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"}
@@ -1,16 +1,16 @@
1
- import { defineComponent as B, computed as d, ref as N, watch as g, createBlock as u, openBlock as s, unref as i, normalizeClass as v, withCtx as f, createVNode as y, createElementVNode as _, createElementBlock as m, Fragment as k, renderList as T, createCommentVNode as b, toDisplayString as $, renderSlot as j, resolveDynamicComponent as D, mergeProps as L, nextTick as P } from "vue";
1
+ import { defineComponent as P, computed as i, ref as D, watch as _, createBlock as u, openBlock as n, unref as v, normalizeClass as f, withCtx as m, createVNode as y, createElementVNode as b, createElementBlock as p, Fragment as C, renderList as k, createCommentVNode as T, toDisplayString as w, renderSlot as L, resolveDynamicComponent as F, mergeProps as H, nextTick as $ } from "vue";
2
2
  import "../shadcn/index.js";
3
- import { X as F } from "lucide-vue-next";
4
- import { cn as C } from "../../lib/utils.js";
5
- import H from "../atoms/JIcon.vue.js";
6
- import J from "../shadcn/Tabs.vue.js";
7
- import R from "../shadcn/TabsList.vue.js";
8
- import U from "../shadcn/TabsTrigger.vue.js";
9
- import X from "../shadcn/TabsContent.vue.js";
10
- const Y = { class: "flex-1 truncate" }, q = ["aria-label", "onClick"], G = { class: "flex-1 w-full overflow-auto" }, K = {
3
+ import { X as J } from "lucide-vue-next";
4
+ import { cn as g } from "../../lib/utils.js";
5
+ import R from "../atoms/JIcon.vue.js";
6
+ import U from "../shadcn/Tabs.vue.js";
7
+ import W from "../shadcn/TabsList.vue.js";
8
+ import X from "../shadcn/TabsTrigger.vue.js";
9
+ import Y from "../shadcn/TabsContent.vue.js";
10
+ const q = { class: "flex-1 truncate" }, G = ["aria-label", "onClick"], K = {
11
11
  key: 1,
12
12
  class: "p-4"
13
- }, M = { class: "text-muted-foreground" }, ne = /* @__PURE__ */ B({
13
+ }, M = { class: "text-muted-foreground" }, se = /* @__PURE__ */ P({
14
14
  __name: "JTabs",
15
15
  props: {
16
16
  tabs: {},
@@ -20,105 +20,149 @@ const Y = { class: "flex-1 truncate" }, q = ["aria-label", "onClick"], G = { cla
20
20
  styletype: { default: "default" }
21
21
  },
22
22
  emits: ["tabChange", "tabClose", "update:activeTabId"],
23
- setup(S, { emit: z }) {
24
- const n = S, r = z, c = d(() => Array.isArray(n.tabs) ? n.tabs : []), a = N(
25
- n.activeTabId || (c.value.length > 0 ? c.value[0]?.id : "") || ""
23
+ setup(j, { emit: E }) {
24
+ const o = j, l = E, c = i(() => Array.isArray(o.tabs) ? o.tabs : []), a = D(
25
+ o.activeTabId || (c.value.length > 0 ? c.value[0]?.id : "") || ""
26
26
  );
27
- let o = !1;
28
- g(() => n.activeTabId, (e) => {
27
+ let s = !1;
28
+ _(() => o.activeTabId, (e) => {
29
29
  e !== void 0 && e !== a.value && (a.value = e);
30
- }, { immediate: !0 }), g(c, (e) => {
31
- !n.activeTabId && e.length > 0 && !e.find((l) => l.id === a.value) && e[0] && (a.value = e[0].id);
30
+ }, { immediate: !0 }), _(c, (e) => {
31
+ !o.activeTabId && e.length > 0 && !e.find((r) => r.id === a.value) && e[0] && (a.value = e[0].id);
32
32
  });
33
- const E = (e) => {
34
- if (o) return;
35
- const l = String(e);
36
- l !== a.value && (o = !0, a.value = l, r("update:activeTabId", l), r("tabChange", l), P(() => {
37
- o = !1;
33
+ const S = (e) => {
34
+ if (s) return;
35
+ const r = String(e);
36
+ r !== a.value && (s = !0, a.value = r, l("update:activeTabId", r), l("tabChange", r), $(() => {
37
+ s = !1;
38
38
  }));
39
39
  }, I = (e) => {
40
- o || e === a.value || (o = !0, a.value = e, r("update:activeTabId", e), r("tabChange", e), P(() => {
41
- o = !1;
40
+ s || e === a.value || (s = !0, a.value = e, l("update:activeTabId", e), l("tabChange", e), $(() => {
41
+ s = !1;
42
42
  }));
43
- }, V = (e, l) => {
44
- e.stopPropagation(), r("tabClose", l);
45
- }, w = d(() => C("flex flex-col w-full h-full", n.class)), h = {
43
+ }, V = (e, r) => {
44
+ e.stopPropagation(), l("tabClose", r);
45
+ }, h = {
46
+ // Default: Modern Underline 스타일 (Linear/Stripe/GitHub 패턴)
47
+ // 하단 primary indicator + 텍스트 색상 전환 + 플랫 디자인
46
48
  default: {
47
- tabPaddingClass: "px-2 py-1",
48
- tabTextSizeClass: "text-xs",
49
- listPaddingClass: "px-1 py-1"
49
+ list: "w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]",
50
+ trigger: [
51
+ "relative px-3 py-2 text-xs font-medium",
52
+ "rounded-none border-b-2 border-transparent -mb-px",
53
+ "transition-all duration-200",
54
+ "flex items-center gap-2"
55
+ ].join(" "),
56
+ active: [
57
+ "data-[state=active]:text-foreground",
58
+ "data-[state=active]:border-b-primary",
59
+ "data-[state=active]:bg-transparent",
60
+ "data-[state=active]:shadow-none"
61
+ ].join(" "),
62
+ inactive: [
63
+ "data-[state=inactive]:text-muted-foreground",
64
+ "data-[state=inactive]:bg-transparent",
65
+ "data-[state=inactive]:hover:text-foreground",
66
+ "data-[state=inactive]:hover:border-b-muted-foreground/30"
67
+ ].join(" "),
68
+ content: ""
50
69
  },
70
+ // Minimal: 컴팩트 Underline (사이드바/중첩 탭용)
71
+ // 더 작은 패딩, 밀도 높은 배치
51
72
  minimal: {
52
- tabPaddingClass: "px-1.5 py-1",
53
- tabTextSizeClass: "text-xs",
54
- listPaddingClass: "px-1 py-1"
73
+ list: "w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[1.75rem]",
74
+ trigger: [
75
+ "relative px-2 py-1.5 text-xs font-medium",
76
+ "rounded-none border-b-2 border-transparent -mb-px",
77
+ "transition-all duration-200",
78
+ "flex items-center gap-1.5"
79
+ ].join(" "),
80
+ active: [
81
+ "data-[state=active]:text-foreground",
82
+ "data-[state=active]:border-b-primary",
83
+ "data-[state=active]:bg-transparent",
84
+ "data-[state=active]:shadow-none"
85
+ ].join(" "),
86
+ inactive: [
87
+ "data-[state=inactive]:text-muted-foreground",
88
+ "data-[state=inactive]:bg-transparent",
89
+ "data-[state=inactive]:hover:text-foreground",
90
+ "data-[state=inactive]:hover:border-b-muted-foreground/30"
91
+ ].join(" "),
92
+ content: ""
55
93
  }
56
- }, p = d(() => h[n.styletype] ?? h.default), A = d(() => C("w-full justify-start", p.value.listPaddingClass, n.listClass));
57
- return (e, l) => (s(), u(i(J), {
94
+ }, d = i(() => h[o.styletype] ?? h.default), z = i(() => g("flex flex-col w-full h-full", o.class)), A = i(() => g(d.value.list, o.listClass)), B = i(() => g(
95
+ d.value.trigger,
96
+ d.value.active,
97
+ d.value.inactive
98
+ )), N = i(() => g("flex-1 w-full overflow-auto", d.value.content));
99
+ return (e, r) => (n(), u(v(U), {
58
100
  "model-value": a.value,
59
- "onUpdate:modelValue": E,
101
+ "onUpdate:modelValue": S,
60
102
  orientation: "horizontal",
61
- class: v(w.value)
103
+ class: f(z.value)
62
104
  }, {
63
- default: f(() => [
64
- y(i(R), {
65
- class: v(A.value)
105
+ default: m(() => [
106
+ y(v(W), {
107
+ class: f(A.value)
66
108
  }, {
67
- default: f(() => [
68
- (s(!0), m(k, null, T(c.value, (t) => (s(), u(i(U), {
109
+ default: m(() => [
110
+ (n(!0), p(C, null, k(c.value, (t) => (n(), u(v(X), {
69
111
  key: t.id,
70
112
  value: t.id,
71
113
  onClick: (x) => I(t.id),
72
- class: v(i(C)("!flex !items-center !gap-2", p.value.tabPaddingClass, p.value.tabTextSizeClass))
114
+ class: f(B.value)
73
115
  }, {
74
- default: f(() => [
75
- t.icon ? (s(), u(H, {
116
+ default: m(() => [
117
+ t.icon ? (n(), u(R, {
76
118
  key: 0,
77
119
  name: t.icon,
78
120
  size: "sm",
79
121
  class: "flex-shrink-0"
80
- }, null, 8, ["name"])) : b("", !0),
81
- _("span", Y, $(t.label), 1),
82
- t.closable ? (s(), m("button", {
122
+ }, null, 8, ["name"])) : T("", !0),
123
+ b("span", q, w(t.label), 1),
124
+ t.closable ? (n(), p("button", {
83
125
  key: 1,
84
126
  type: "button",
85
127
  class: "flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center",
86
128
  "aria-label": `${t.label} 탭 닫기`,
87
129
  onClick: (x) => V(x, t.id)
88
130
  }, [
89
- y(i(F), { class: "h-2.5 w-2.5" })
90
- ], 8, q)) : b("", !0)
131
+ y(v(J), { class: "h-2.5 w-2.5" })
132
+ ], 8, G)) : T("", !0)
91
133
  ]),
92
134
  _: 2
93
135
  }, 1032, ["value", "onClick", "class"]))), 128))
94
136
  ]),
95
137
  _: 1
96
138
  }, 8, ["class"]),
97
- _("div", G, [
98
- (s(!0), m(k, null, T(c.value, (t) => (s(), u(i(X), {
139
+ b("div", {
140
+ class: f(N.value)
141
+ }, [
142
+ (n(!0), p(C, null, k(c.value, (t) => (n(), u(v(Y), {
99
143
  key: `content-${t.id}`,
100
144
  value: t.id,
101
- class: "h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col"
145
+ class: "h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col data-[state=active]:animate-tab-fade-in"
102
146
  }, {
103
- default: f(() => [
104
- j(e.$slots, `content-${t.id}`, { tab: t }, () => [
105
- t.component ? (s(), u(D(t.component), L({
147
+ default: m(() => [
148
+ L(e.$slots, `content-${t.id}`, { tab: t }, () => [
149
+ t.component ? (n(), u(F(t.component), H({
106
150
  key: 0,
107
151
  ref_for: !0
108
- }, t.props || {}), null, 16)) : (s(), m("div", K, [
109
- _("p", M, $(t.label) + " 콘텐츠", 1)
152
+ }, t.props || {}), null, 16)) : (n(), p("div", K, [
153
+ b("p", M, w(t.label) + " 콘텐츠", 1)
110
154
  ]))
111
155
  ], !0)
112
156
  ]),
113
157
  _: 2
114
158
  }, 1032, ["value"]))), 128))
115
- ])
159
+ ], 2)
116
160
  ]),
117
161
  _: 3
118
162
  }, 8, ["model-value", "class"]));
119
163
  }
120
164
  });
121
165
  export {
122
- ne as default
166
+ se as default
123
167
  };
124
168
  //# sourceMappingURL=JTabs.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JTabs.vue2.js","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n * \n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n * \n * @example\n * ```vue\n * <JTabs \n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n \n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n // reka-ui 이벤트가 작동하지 않을 경우 직접 처리\n // handleTabValueChange가 이미 처리했으면 중복 방지\n if (isHandlingEvent || tabId === internalActiveId.value) return\n \n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation() // 탭 클릭 이벤트 전파 방지\n emit('tabClose', tabId)\n}\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n tabPaddingClass: string\n tabTextSizeClass: string\n listPaddingClass: string\n}> = {\n default: {\n tabPaddingClass: 'px-2 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n minimal: {\n tabPaddingClass: 'px-1.5 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 탭 리스트 클래스\n * Tabs list classes\n */\nconst listClasses = computed(() => {\n return cn('w-full justify-start', preset.value.listPaddingClass, props.listClass)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"cn('!flex !items-center !gap-2', preset.tabPaddingClass, preset.tabTextSizeClass)\"\n >\n <!-- 탭 아이콘 (있을 경우) / Tab Icon -->\n <JIcon \n v-if=\"tab.icon\" \n :name=\"tab.icon\" \n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n \n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n \n <!-- 닫기 버튼 / Close Button (항상 표시) -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div class=\"flex-1 w-full overflow-auto\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col\"\n >\n <!-- 슬롯 우선 / Slot First -->\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <!-- 동적 컴포넌트 렌더링 / Dynamic Component Rendering -->\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n \n <!-- 기본 콘텐츠 / Default Content -->\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/**\n * 탭 리스트 스타일 - 하단 보더 제거\n * Tab list styles without bottom border\n */\n:deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n background: hsl(var(--background));\n padding: 0 0.25rem;\n gap: 0.25rem;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar) {\n height: 6px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(0, 0, 0, 0.3);\n}\n\n/**\n * 다크모드에서 스크롤바 스타일\n * Scrollbar styles in dark mode\n */\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n/**\n * 탭 버튼 스타일 - 명확한 구분\n * Tab button styles - clear distinction\n */\n:deep([role=\"tab\"]) {\n position: relative;\n padding: 0.25rem 0.5rem;\n min-height: 1.75rem;\n border-radius: 0.375rem 0.375rem 0 0;\n transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n border: 1px solid transparent;\n border-bottom: none;\n}\n\n/**\n * Minimal 스타일 탭 버튼 - JSidebarAdvanced와 높이 맞춤\n */\n:deep([role=\"tablist\"][class*=\"p-0.5\"] [role=\"tab\"]) {\n padding: 0.25rem 0.5rem;\n}\n\n/**\n * 비활성 탭 - 명확하게 구분\n * Inactive tabs - clear distinction\n */\n:deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--muted) / 0.2);\n color: hsl(var(--muted-foreground));\n border-top: 1px solid hsl(var(--border) / 0.4);\n border-left: 1px solid hsl(var(--border) / 0.4);\n border-right: 1px solid hsl(var(--border) / 0.4);\n}\n\n:deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted) / 0.4);\n color: hsl(var(--foreground));\n}\n\n/**\n * 다크모드에서 비활성 탭 - 다크 배경색 사용\n * Inactive tabs in dark mode - use dark background\n */\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--secondary));\n color: hsl(var(--secondary-foreground));\n border-top: 1px solid hsl(var(--border) / 0.5);\n border-left: 1px solid hsl(var(--border) / 0.5);\n border-right: 1px solid hsl(var(--border) / 0.5);\n}\n\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted));\n color: hsl(var(--muted-foreground));\n}\n\n/**\n * 활성 탭 - 강조된 스타일\n * Active tab - emphasized style\n */\n:deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--background));\n color: hsl(var(--foreground));\n font-weight: 500;\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n z-index: 1;\n}\n\n/**\n * 다크모드에서 활성 탭 - 배경색 명확히 구분\n * Active tab in dark mode - clear background distinction\n */\n.dark :deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--card));\n color: hsl(var(--card-foreground));\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","rootClasses","cn","STYLE_PRESETS","preset","listClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","_normalizeClass","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","e","X","_hoisted_3","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5"],"mappings":"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"}
1
+ {"version":3,"file":"JTabs.vue2.js","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n *\n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n *\n * @example\n * ```vue\n * <JTabs\n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n\n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n if (isHandlingEvent || tabId === internalActiveId.value) return\n\n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation()\n emit('tabClose', tabId)\n}\n\n// ── Style Presets ──────────────────────────────────────────────────\nconst STYLE_PRESETS: Record<StyleType, {\n list: string\n trigger: string\n active: string\n inactive: string\n content: string\n}> = {\n // Default: Modern Underline 스타일 (Linear/Stripe/GitHub 패턴)\n // 하단 primary indicator + 텍스트 색상 전환 + 플랫 디자인\n default: {\n list: 'w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]',\n trigger: [\n 'relative px-3 py-2 text-xs font-medium',\n 'rounded-none border-b-2 border-transparent -mb-px',\n 'transition-all duration-200',\n 'flex items-center gap-2',\n ].join(' '),\n active: [\n 'data-[state=active]:text-foreground',\n 'data-[state=active]:border-b-primary',\n 'data-[state=active]:bg-transparent',\n 'data-[state=active]:shadow-none',\n ].join(' '),\n inactive: [\n 'data-[state=inactive]:text-muted-foreground',\n 'data-[state=inactive]:bg-transparent',\n 'data-[state=inactive]:hover:text-foreground',\n 'data-[state=inactive]:hover:border-b-muted-foreground/30',\n ].join(' '),\n content: '',\n },\n\n // Minimal: 컴팩트 Underline (사이드바/중첩 탭용)\n // 더 작은 패딩, 밀도 높은 배치\n minimal: {\n list: 'w-full justify-start gap-0 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[1.75rem]',\n trigger: [\n 'relative px-2 py-1.5 text-xs font-medium',\n 'rounded-none border-b-2 border-transparent -mb-px',\n 'transition-all duration-200',\n 'flex items-center gap-1.5',\n ].join(' '),\n active: [\n 'data-[state=active]:text-foreground',\n 'data-[state=active]:border-b-primary',\n 'data-[state=active]:bg-transparent',\n 'data-[state=active]:shadow-none',\n ].join(' '),\n inactive: [\n 'data-[state=inactive]:text-muted-foreground',\n 'data-[state=inactive]:bg-transparent',\n 'data-[state=inactive]:hover:text-foreground',\n 'data-[state=inactive]:hover:border-b-muted-foreground/30',\n ].join(' '),\n content: '',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n// ── Computed Classes ──────────────────────────────────────────────\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\nconst listClasses = computed(() => {\n return cn(preset.value.list, props.listClass)\n})\n\nconst triggerClasses = computed(() => {\n return cn(\n preset.value.trigger,\n preset.value.active,\n preset.value.inactive,\n )\n})\n\nconst contentWrapperClasses = computed(() => {\n return cn('flex-1 w-full overflow-auto', preset.value.content)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"triggerClasses\"\n >\n <!-- 탭 아이콘 / Tab Icon -->\n <JIcon\n v-if=\"tab.icon\"\n :name=\"tab.icon\"\n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n\n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n\n <!-- 닫기 버튼 / Close Button -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div :class=\"contentWrapperClasses\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col data-[state=active]:animate-tab-fade-in\"\n >\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/* ── 스크롤바: Tailwind으로 불가능한 pseudo-element만 `:deep()` 사용 ── */\n:deep([role=\"tablist\"]) {\n scrollbar-width: thin;\n scrollbar-color: hsl(var(--border)) transparent;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar) {\n height: 4px;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar-thumb) {\n background-color: hsl(var(--border));\n border-radius: 2px;\n}\n\n:deep([role=\"tablist\"]::-webkit-scrollbar-thumb:hover) {\n background-color: hsl(var(--muted-foreground) / 0.4);\n}\n\n/* ── 콘텐츠 전환 애니메이션 ── */\n@keyframes tab-fade-in {\n from {\n opacity: 0;\n transform: translateY(2px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n:deep(.animate-tab-fade-in) {\n animation: tab-fade-in 0.15s ease-out;\n}\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","STYLE_PRESETS","preset","rootClasses","cn","listClasses","triggerClasses","contentWrapperClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","e","X","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_3","_hoisted_4"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),r=require("../../lib/utils.cjs"),o=e.defineComponent({__name:"FieldDescription",props:{class:{}},setup(t){const n=t;return(a,l)=>(e.openBlock(),e.createElementBlock("p",{"data-slot":"field-description",class:e.normalizeClass(e.unref(r.cn)("text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance","last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5","[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",n.class))},[e.renderSlot(a.$slots,"default")],2))}});exports.default=o;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),r=require("../../lib/utils.cjs"),o=e.defineComponent({__name:"FieldDescription",props:{class:{}},setup(t){const n=t;return(a,l)=>(e.openBlock(),e.createElementBlock("p",{"data-slot":"field-description",class:e.normalizeClass(e.unref(r.cn)("text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance","last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5","[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",n.class))},[e.renderSlot(a.$slots,"default")],2))}});exports.default=o;
2
2
  //# sourceMappingURL=FieldDescription.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"FieldDescription.vue.cjs","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":"6OAIA,MAAMA,EAAQC,8BAMZC,EAAAA,mBAUI,IAAA,CATF,YAAU,oBACT,uBAAOC,EAAAA,MAAAC,IAAA,6PAA8RJ,EAAM,KAAA,KAO5SK,aAAQC,EAAA,OAAA,SAAA,CAAA"}
1
+ {"version":3,"file":"FieldDescription.vue.cjs","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":"6OAIA,MAAMA,EAAQC,8BAMZC,EAAAA,mBAUI,IAAA,CATF,YAAU,oBACT,uBAAOC,EAAAA,MAAAC,IAAA,6PAA8RJ,EAAM,KAAA,KAO5SK,aAAQC,EAAA,OAAA,SAAA,CAAA"}
@@ -10,7 +10,7 @@ const f = /* @__PURE__ */ n({
10
10
  return (a, m) => (r(), o("p", {
11
11
  "data-slot": "field-description",
12
12
  class: l(s(d)(
13
- "text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
13
+ "text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
14
14
  "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
15
15
  "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
16
16
  t.class
@@ -1 +1 @@
1
- {"version":3,"file":"FieldDescription.vue.js","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":";;;;;;;;AAIA,UAAMA,IAAQC;2BAMZC,EAUI,KAAA;AAAA,MATF,aAAU;AAAA,MACT,SAAOC,EAAAC,CAAA;AAAA;;;QAA8RJ,EAAM;AAAA,MAAA;;MAO5SK,EAAQC,EAAA,QAAA,SAAA;AAAA,IAAA;;;"}
1
+ {"version":3,"file":"FieldDescription.vue.js","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":";;;;;;;;AAIA,UAAMA,IAAQC;2BAMZC,EAUI,KAAA;AAAA,MATF,aAAU;AAAA,MACT,SAAOC,EAAAC,CAAA;AAAA;;;QAA8RJ,EAAM;AAAA,MAAA;;MAO5SK,EAAQC,EAAA,QAAA,SAAA;AAAA,IAAA;;;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),a=require("../../lib/utils.cjs"),c={key:2,class:"ml-4 flex list-disc flex-col gap-1"},u=e.defineComponent({__name:"FieldError",props:{class:{},errors:{}},setup(o){const r=o,t=e.computed(()=>!r.errors||r.errors.length===0?null:r.errors.length===1&&r.errors[0]?.message?r.errors[0].message:r.errors.some(l=>l?.message)?r.errors:null);return(l,i)=>l.$slots.default||t.value?(e.openBlock(),e.createElementBlock("div",{key:0,role:"alert","data-slot":"field-error",class:e.normalizeClass(e.unref(a.cn)("text-destructive text-sm font-normal",r.class))},[l.$slots.default?e.renderSlot(l.$slots,"default",{key:0}):typeof t.value=="string"?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createTextVNode(e.toDisplayString(t.value),1)],64)):Array.isArray(t.value)?(e.openBlock(),e.createElementBlock("ul",c,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value,(s,n)=>(e.openBlock(),e.createElementBlock("li",{key:n},e.toDisplayString(s?.message),1))),128))])):e.createCommentVNode("",!0)],2)):e.createCommentVNode("",!0)}});exports.default=u;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),a=require("../../lib/utils.cjs"),c={key:2,class:"ml-4 flex list-disc flex-col gap-1"},u=e.defineComponent({__name:"FieldError",props:{class:{},errors:{}},setup(o){const r=o,t=e.computed(()=>!r.errors||r.errors.length===0?null:r.errors.length===1&&r.errors[0]?.message?r.errors[0].message:r.errors.some(l=>l?.message)?r.errors:null);return(l,i)=>l.$slots.default||t.value?(e.openBlock(),e.createElementBlock("div",{key:0,role:"alert","data-slot":"field-error",class:e.normalizeClass(e.unref(a.cn)("text-destructive text-xs font-normal",r.class))},[l.$slots.default?e.renderSlot(l.$slots,"default",{key:0}):typeof t.value=="string"?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createTextVNode(e.toDisplayString(t.value),1)],64)):Array.isArray(t.value)?(e.openBlock(),e.createElementBlock("ul",c,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value,(s,n)=>(e.openBlock(),e.createElementBlock("li",{key:n},e.toDisplayString(s?.message),1))),128))])):e.createCommentVNode("",!0)],2)):e.createCommentVNode("",!0)}});exports.default=u;
2
2
  //# sourceMappingURL=FieldError.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"FieldError.vue.cjs","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-sm font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":"sSAKA,MAAMA,EAAQC,EAKRC,EAAUC,EAAAA,SAAS,IACnB,CAACH,EAAM,QAAUA,EAAM,OAAO,SAAW,EACpC,KAELA,EAAM,OAAO,SAAW,GAAKA,EAAM,OAAO,CAAC,GAAG,QACzCA,EAAM,OAAO,CAAC,EAAE,QAGlBA,EAAM,OAAO,KAAKI,GAAKA,GAAG,OAAO,EACpCJ,EAAM,OACN,IACL,eAKSK,EAAAA,OAAO,SAAWH,EAAA,qBAD1BI,EAAAA,mBAiBM,MAAA,OAfJ,KAAK,QACL,YAAU,cACT,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,uCAAyCT,EAAM,KAAK,CAAA,CAAA,GAElDK,EAAAA,OAAO,QAAnBK,aAA8BC,EAAA,OAAA,UAAA,CAAA,IAAA,CAAA,CAAA,SAEFT,EAAA,OAAO,wBAAnCI,EAAAA,mBAEWM,WAAA,CAAA,IAAA,GAAA,qCADNV,EAAA,KAAO,EAAA,CAAA,CAAA,OAGG,MAAM,QAAQA,EAAA,KAAO,GAApCW,YAAA,EAAAP,qBAIK,KAJLQ,EAIK,EAHHD,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAEKM,WAAA,KAAAG,EAAAA,WAFwBb,EAAA,MAAO,CAAxBc,EAAOC,KAAnBJ,YAAA,EAAAP,qBAEK,MAFkC,IAAKW,CAAA,EAAKC,kBAC5CF,GAAO,OAAO,EAAA,CAAA"}
1
+ {"version":3,"file":"FieldError.vue.cjs","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-xs font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":"sSAKA,MAAMA,EAAQC,EAKRC,EAAUC,EAAAA,SAAS,IACnB,CAACH,EAAM,QAAUA,EAAM,OAAO,SAAW,EACpC,KAELA,EAAM,OAAO,SAAW,GAAKA,EAAM,OAAO,CAAC,GAAG,QACzCA,EAAM,OAAO,CAAC,EAAE,QAGlBA,EAAM,OAAO,KAAKI,GAAKA,GAAG,OAAO,EACpCJ,EAAM,OACN,IACL,eAKSK,EAAAA,OAAO,SAAWH,EAAA,qBAD1BI,EAAAA,mBAiBM,MAAA,OAfJ,KAAK,QACL,YAAU,cACT,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,uCAAyCT,EAAM,KAAK,CAAA,CAAA,GAElDK,EAAAA,OAAO,QAAnBK,aAA8BC,EAAA,OAAA,UAAA,CAAA,IAAA,CAAA,CAAA,SAEFT,EAAA,OAAO,wBAAnCI,EAAAA,mBAEWM,WAAA,CAAA,IAAA,GAAA,qCADNV,EAAA,KAAO,EAAA,CAAA,CAAA,OAGG,MAAM,QAAQA,EAAA,KAAO,GAApCW,YAAA,EAAAP,qBAIK,KAJLQ,EAIK,EAHHD,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAEKM,WAAA,KAAAG,EAAAA,WAFwBb,EAAA,MAAO,CAAxBc,EAAOC,KAAnBJ,YAAA,EAAAP,qBAEK,MAFkC,IAAKW,CAAA,EAAKC,kBAC5CF,GAAO,OAAO,EAAA,CAAA"}
@@ -1,9 +1,9 @@
1
- import { defineComponent as i, computed as d, createElementBlock as s, createCommentVNode as l, openBlock as o, normalizeClass as f, unref as p, renderSlot as g, Fragment as a, createTextVNode as y, toDisplayString as n, renderList as k } from "vue";
1
+ import { defineComponent as m, computed as d, createElementBlock as s, createCommentVNode as l, openBlock as o, normalizeClass as f, unref as p, renderSlot as g, Fragment as a, createTextVNode as y, toDisplayString as n, renderList as k } from "vue";
2
2
  import { cn as v } from "../../lib/utils.js";
3
- const _ = {
3
+ const x = {
4
4
  key: 2,
5
5
  class: "ml-4 flex list-disc flex-col gap-1"
6
- }, $ = /* @__PURE__ */ i({
6
+ }, $ = /* @__PURE__ */ m({
7
7
  __name: "FieldError",
8
8
  props: {
9
9
  class: {},
@@ -11,16 +11,16 @@ const _ = {
11
11
  },
12
12
  setup(u) {
13
13
  const e = u, r = d(() => !e.errors || e.errors.length === 0 ? null : e.errors.length === 1 && e.errors[0]?.message ? e.errors[0].message : e.errors.some((t) => t?.message) ? e.errors : null);
14
- return (t, h) => t.$slots.default || r.value ? (o(), s("div", {
14
+ return (t, _) => t.$slots.default || r.value ? (o(), s("div", {
15
15
  key: 0,
16
16
  role: "alert",
17
17
  "data-slot": "field-error",
18
- class: f(p(v)("text-destructive text-sm font-normal", e.class))
18
+ class: f(p(v)("text-destructive text-xs font-normal", e.class))
19
19
  }, [
20
20
  t.$slots.default ? g(t.$slots, "default", { key: 0 }) : typeof r.value == "string" ? (o(), s(a, { key: 1 }, [
21
21
  y(n(r.value), 1)
22
- ], 64)) : Array.isArray(r.value) ? (o(), s("ul", _, [
23
- (o(!0), s(a, null, k(r.value, (m, c) => (o(), s("li", { key: c }, n(m?.message), 1))), 128))
22
+ ], 64)) : Array.isArray(r.value) ? (o(), s("ul", x, [
23
+ (o(!0), s(a, null, k(r.value, (c, i) => (o(), s("li", { key: i }, n(c?.message), 1))), 128))
24
24
  ])) : l("", !0)
25
25
  ], 2)) : l("", !0);
26
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FieldError.vue.js","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-sm font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":";;;;;;;;;;;;AAKA,UAAMA,IAAQC,GAKRC,IAAUC,EAAS,MACnB,CAACH,EAAM,UAAUA,EAAM,OAAO,WAAW,IACpC,OAELA,EAAM,OAAO,WAAW,KAAKA,EAAM,OAAO,CAAC,GAAG,UACzCA,EAAM,OAAO,CAAC,EAAE,UAGlBA,EAAM,OAAO,KAAK,CAAAI,MAAKA,GAAG,OAAO,IACpCJ,EAAM,SACN,IACL;qBAKSK,EAAAA,OAAO,WAAWH,EAAA,cAD1BI,EAiBM,OAAA;AAAA;MAfJ,MAAK;AAAA,MACL,aAAU;AAAA,MACT,OAAKC,EAAEC,EAAAC,CAAA,EAAE,wCAAyCT,EAAM,KAAK,CAAA;AAAA,IAAA;MAElDK,EAAAA,OAAO,UAAnBK,EAA8BC,EAAA,QAAA,WAAA,EAAA,KAAA,EAAA,CAAA,WAEFT,EAAA,SAAO,iBAAnCI,EAEWM,GAAA,EAAA,KAAA,KAAA;AAAA,YADNV,EAAA,KAAO,GAAA,CAAA;AAAA,MAAA,UAGG,MAAM,QAAQA,EAAA,KAAO,KAApCW,EAAA,GAAAP,EAIK,MAJLQ,GAIK;AAAA,SAHHD,EAAA,EAAA,GAAAP,EAEKM,GAAA,MAAAG,EAFwBb,EAAA,OAAO,CAAxBc,GAAOC,OAAnBJ,EAAA,GAAAP,EAEK,QAFkC,KAAKW,EAAA,GAAKC,EAC5CF,GAAO,OAAO,GAAA,CAAA;;;;;"}
1
+ {"version":3,"file":"FieldError.vue.js","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-xs font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":";;;;;;;;;;;;AAKA,UAAMA,IAAQC,GAKRC,IAAUC,EAAS,MACnB,CAACH,EAAM,UAAUA,EAAM,OAAO,WAAW,IACpC,OAELA,EAAM,OAAO,WAAW,KAAKA,EAAM,OAAO,CAAC,GAAG,UACzCA,EAAM,OAAO,CAAC,EAAE,UAGlBA,EAAM,OAAO,KAAK,CAAAI,MAAKA,GAAG,OAAO,IACpCJ,EAAM,SACN,IACL;qBAKSK,EAAAA,OAAO,WAAWH,EAAA,cAD1BI,EAiBM,OAAA;AAAA;MAfJ,MAAK;AAAA,MACL,aAAU;AAAA,MACT,OAAKC,EAAEC,EAAAC,CAAA,EAAE,wCAAyCT,EAAM,KAAK,CAAA;AAAA,IAAA;MAElDK,EAAAA,OAAO,UAAnBK,EAA8BC,EAAA,QAAA,WAAA,EAAA,KAAA,EAAA,CAAA,WAEFT,EAAA,SAAO,iBAAnCI,EAEWM,GAAA,EAAA,KAAA,KAAA;AAAA,YADNV,EAAA,KAAO,GAAA,CAAA;AAAA,MAAA,UAGG,MAAM,QAAQA,EAAA,KAAO,KAApCW,EAAA,GAAAP,EAIK,MAJLQ,GAIK;AAAA,SAHHD,EAAA,EAAA,GAAAP,EAEKM,GAAA,MAAAG,EAFwBb,EAAA,OAAO,CAAxBc,GAAOC,OAAnBJ,EAAA,GAAAP,EAEK,QAFkC,KAAKW,EAAA,GAAKC,EAC5CF,GAAO,OAAO,GAAA,CAAA;;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@j-solution/components",
3
3
  "description": "Vue 3 Atomic Design component kit for enterprise dashboards",
4
- "version": "2.0.3",
4
+ "version": "2.0.5",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
7
7
  "module": "./index.js",
package/types/index.d.ts CHANGED
@@ -761,6 +761,8 @@ declare type __VLS_Props_28 = {
761
761
  footer?: string;
762
762
  /** 카드 variant (패턴 4) */
763
763
  variant?: CardVariant;
764
+ /** CardContent가 남은 세로 공간을 채우도록 flex column 적용 */
765
+ fillContent?: boolean;
764
766
  };
765
767
 
766
768
  declare type __VLS_Props_29 = {