{"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, DynamicTab } 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  keepAlive: false,\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: Card Tab 스타일 — 탭 shelf 배경 + active 탭이 content와 동일한 배경으로 \"올라온\" 효과\n  default: {\n    list: 'w-full justify-start gap-0 px-1 pt-1 bg-muted/40 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-t-md -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]:bg-background',\n      'data-[state=active]:border data-[state=active]:border-border',\n      'data-[state=active]:border-b-background',\n      'data-[state=active]:shadow-none',\n    ].join(' '),\n    inactive: [\n      'data-[state=inactive]:text-muted-foreground',\n      'data-[state=inactive]:hover:bg-background/60',\n      'data-[state=inactive]:hover:text-foreground',\n    ].join(' '),\n    content: '',\n  },\n\n  // Minimal: 컴팩트 Card Tab (사이드바/중첩 탭용) — 가벼운 톤 + 분리감만 추가\n  minimal: {\n    list: 'w-full justify-start gap-0 px-0.5 pt-0.5 bg-muted/30 border-b border-border/60 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-t-sm -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]:bg-background',\n      'data-[state=active]:border data-[state=active]:border-border/60',\n      'data-[state=active]:border-b-background',\n      'data-[state=active]:shadow-none',\n    ].join(' '),\n    inactive: [\n      'data-[state=inactive]:text-muted-foreground',\n      'data-[state=inactive]:hover:bg-background/50',\n      'data-[state=inactive]:hover:text-foreground',\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\nconst shouldKeepAlive = (tab: DynamicTab) => tab.keepAlive ?? props.keepAlive\n</script>\n\n<template>\n  <Tabs\n    :model-value=\"internalActiveId\"\n    orientation=\"horizontal\"\n    :class=\"rootClasses\"\n    @update:model-value=\"handleTabValueChange\"\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        :class=\"triggerClasses\"\n        @click=\"handleTabClick(tab.id)\"\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        :force-mount=\"shouldKeepAlive(tab) || undefined\"\n        class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col data-[state=active]:animate-tab-fade-in data-[state=inactive]:hidden\"\n      >\n        <KeepAlive v-if=\"shouldKeepAlive(tab)\">\n          <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n            <component\n              :is=\"tab.component\"\n              v-if=\"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        </KeepAlive>\n        <template v-else>\n          <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n            <component\n              :is=\"tab.component\"\n              v-if=\"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        </template>\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","shouldKeepAlive","tab","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","TabsTrigger","$event","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","X","TabsContent","_KeepAlive","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6"],"mappings":"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"}