@j-solution/components 2.0.4 → 2.0.6

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 (49) hide show
  1. package/README.md +8 -7
  2. package/assets/jwms-portal-frontend-Cu-V5XAR.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/assets/styles/main.css +22 -0
  5. package/components/atoms/JCombo.vue.cjs +1 -1
  6. package/components/atoms/JCombo.vue.cjs.map +1 -1
  7. package/components/atoms/JCombo.vue.js +1 -1
  8. package/components/atoms/JCombo.vue.js.map +1 -1
  9. package/components/atoms/JEditor.vue.cjs +1 -1
  10. package/components/atoms/JEditor.vue.js +2 -2
  11. package/components/atoms/JEditor.vue2.cjs.map +1 -1
  12. package/components/atoms/JEditor.vue2.js.map +1 -1
  13. package/components/atoms/JSplitter.vue.cjs +1 -1
  14. package/components/atoms/JSplitter.vue.js +2 -2
  15. package/components/atoms/JSplitter.vue2.cjs +1 -1
  16. package/components/atoms/JSplitter.vue2.cjs.map +1 -1
  17. package/components/atoms/JSplitter.vue2.js +30 -28
  18. package/components/atoms/JSplitter.vue2.js.map +1 -1
  19. package/components/molecules/JFormField.vue.cjs +1 -1
  20. package/components/molecules/JFormField.vue.js +2 -2
  21. package/components/molecules/JFormField.vue2.cjs +1 -1
  22. package/components/molecules/JFormField.vue2.cjs.map +1 -1
  23. package/components/molecules/JFormField.vue2.js +80 -79
  24. package/components/molecules/JFormField.vue2.js.map +1 -1
  25. package/components/molecules/JTabs.vue.cjs +1 -1
  26. package/components/molecules/JTabs.vue.js +2 -2
  27. package/components/molecules/JTabs.vue2.cjs +1 -1
  28. package/components/molecules/JTabs.vue2.cjs.map +1 -1
  29. package/components/molecules/JTabs.vue2.js +62 -69
  30. package/components/molecules/JTabs.vue2.js.map +1 -1
  31. package/components/templates/JLayout.vue.cjs +6 -1
  32. package/components/templates/JLayout.vue.cjs.map +1 -1
  33. package/components/templates/JLayout.vue.js +10 -43
  34. package/components/templates/JLayout.vue.js.map +1 -1
  35. package/components/templates/JLayout.vue2.cjs +1 -1
  36. package/components/templates/JLayout.vue2.cjs.map +1 -1
  37. package/components/templates/JLayout.vue2.js +71 -2
  38. package/components/templates/JLayout.vue2.js.map +1 -1
  39. package/components/templates/JLayoutAdvanced.vue.cjs +1 -1
  40. package/components/templates/JLayoutAdvanced.vue.js +12 -12
  41. package/components/templates/JLayoutSimple.vue.cjs +1 -1
  42. package/components/templates/JLayoutSimple.vue.cjs.map +1 -1
  43. package/components/templates/JLayoutSimple.vue.js +32 -22
  44. package/components/templates/JLayoutSimple.vue.js.map +1 -1
  45. package/index.cjs +1 -1
  46. package/index.js +30 -28
  47. package/package.json +1 -1
  48. package/types/index.d.ts +22 -8
  49. package/assets/jwms-portal-frontend-B5GA5JuZ.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 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: Bordered/Card 스타일 (엔터프라이즈)\n // 상단 primary accent + 탭↔콘텐츠 시각적 연결\n default: {\n list: 'w-full justify-start gap-1 px-1 bg-muted/30 border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]',\n trigger: [\n 'relative px-3 py-1.5 text-xs font-medium',\n 'rounded-t-md rounded-b-none',\n 'border border-transparent border-b-0',\n 'transition-all duration-150',\n 'flex items-center gap-2',\n ].join(' '),\n active: [\n 'data-[state=active]:bg-background',\n 'data-[state=active]:text-foreground',\n 'data-[state=active]:font-medium',\n 'data-[state=active]:border-border',\n 'data-[state=active]:border-t-primary',\n 'data-[state=active]:border-t-2',\n 'data-[state=active]:shadow-sm',\n 'data-[state=active]:z-10',\n 'data-[state=active]:-mb-px',\n ].join(' '),\n inactive: [\n 'data-[state=inactive]:bg-muted/20',\n 'data-[state=inactive]:text-muted-foreground',\n 'data-[state=inactive]:border-border/40',\n 'data-[state=inactive]:hover:bg-muted/40',\n 'data-[state=inactive]:hover:text-foreground',\n ].join(' '),\n content: 'border border-border border-t-0 bg-background rounded-b-md',\n },\n\n // Minimal: Underline 스타일 (심플/사이드바용)\n // 하단 indicator 바만 표시, 테두리 없음\n minimal: {\n list: 'w-full justify-start gap-0.5 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]',\n trigger: [\n 'relative px-2.5 py-1 text-xs font-medium',\n 'rounded-none border-b-2 border-transparent',\n 'transition-all duration-150',\n 'flex items-center gap-2',\n ].join(' '),\n active: [\n 'data-[state=active]:text-primary',\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-border',\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
+ {"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,7 +1,7 @@
1
- import { defineComponent as P, computed as s, ref as D, watch as _, createBlock as u, openBlock as o, unref as v, normalizeClass as m, withCtx as f, createVNode as y, createElementVNode as g, 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";
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
3
  import { X as J } from "lucide-vue-next";
4
- import { cn as b } from "../../lib/utils.js";
4
+ import { cn as g } from "../../lib/utils.js";
5
5
  import R from "../atoms/JIcon.vue.js";
6
6
  import U from "../shadcn/Tabs.vue.js";
7
7
  import W from "../shadcn/TabsList.vue.js";
@@ -10,7 +10,7 @@ import Y from "../shadcn/TabsContent.vue.js";
10
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" }, ie = /* @__PURE__ */ P({
13
+ }, M = { class: "text-muted-foreground" }, se = /* @__PURE__ */ P({
14
14
  __name: "JTabs",
15
15
  props: {
16
16
  tabs: {},
@@ -21,71 +21,64 @@ const q = { class: "flex-1 truncate" }, G = ["aria-label", "onClick"], K = {
21
21
  },
22
22
  emits: ["tabChange", "tabClose", "update:activeTabId"],
23
23
  setup(j, { emit: E }) {
24
- const n = j, l = E, d = s(() => Array.isArray(n.tabs) ? n.tabs : []), a = D(
25
- n.activeTabId || (d.value.length > 0 ? d.value[0]?.id : "") || ""
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 i = !1;
28
- _(() => 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 }), _(d, (e) => {
31
- !n.activeTabId && e.length > 0 && !e.find((r) => r.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
33
  const S = (e) => {
34
- if (i) return;
34
+ if (s) return;
35
35
  const r = String(e);
36
- r !== a.value && (i = !0, a.value = r, l("update:activeTabId", r), l("tabChange", r), $(() => {
37
- i = !1;
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
- i || e === a.value || (i = !0, a.value = e, l("update:activeTabId", e), l("tabChange", e), $(() => {
41
- i = !1;
40
+ s || e === a.value || (s = !0, a.value = e, l("update:activeTabId", e), l("tabChange", e), $(() => {
41
+ s = !1;
42
42
  }));
43
- }, z = (e, r) => {
43
+ }, V = (e, r) => {
44
44
  e.stopPropagation(), l("tabClose", r);
45
45
  }, h = {
46
- // Default: Bordered/Card 스타일 (엔터프라이즈)
47
- // 상단 primary accent + 탭↔콘텐츠 시각적 연결
46
+ // Default: Modern Underline 스타일 (Linear/Stripe/GitHub 패턴)
47
+ // 하단 primary indicator + 텍스트 색상 전환 + 플랫 디자인
48
48
  default: {
49
- list: "w-full justify-start gap-1 px-1 bg-muted/30 border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]",
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
50
  trigger: [
51
- "relative px-3 py-1.5 text-xs font-medium",
52
- "rounded-t-md rounded-b-none",
53
- "border border-transparent border-b-0",
54
- "transition-all duration-150",
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",
55
54
  "flex items-center gap-2"
56
55
  ].join(" "),
57
56
  active: [
58
- "data-[state=active]:bg-background",
59
57
  "data-[state=active]:text-foreground",
60
- "data-[state=active]:font-medium",
61
- "data-[state=active]:border-border",
62
- "data-[state=active]:border-t-primary",
63
- "data-[state=active]:border-t-2",
64
- "data-[state=active]:shadow-sm",
65
- "data-[state=active]:z-10",
66
- "data-[state=active]:-mb-px"
58
+ "data-[state=active]:border-b-primary",
59
+ "data-[state=active]:bg-transparent",
60
+ "data-[state=active]:shadow-none"
67
61
  ].join(" "),
68
62
  inactive: [
69
- "data-[state=inactive]:bg-muted/20",
70
63
  "data-[state=inactive]:text-muted-foreground",
71
- "data-[state=inactive]:border-border/40",
72
- "data-[state=inactive]:hover:bg-muted/40",
73
- "data-[state=inactive]:hover:text-foreground"
64
+ "data-[state=inactive]:bg-transparent",
65
+ "data-[state=inactive]:hover:text-foreground",
66
+ "data-[state=inactive]:hover:border-b-muted-foreground/30"
74
67
  ].join(" "),
75
- content: "border border-border border-t-0 bg-background rounded-b-md"
68
+ content: ""
76
69
  },
77
- // Minimal: Underline 스타일 (심플/사이드바용)
78
- // 하단 indicator 바만 표시, 테두리 없음
70
+ // Minimal: 컴팩트 Underline (사이드바/중첩 탭용)
71
+ // 작은 패딩, 밀도 높은 배치
79
72
  minimal: {
80
- list: "w-full justify-start gap-0.5 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]",
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]",
81
74
  trigger: [
82
- "relative px-2.5 py-1 text-xs font-medium",
83
- "rounded-none border-b-2 border-transparent",
84
- "transition-all duration-150",
85
- "flex items-center gap-2"
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"
86
79
  ].join(" "),
87
80
  active: [
88
- "data-[state=active]:text-primary",
81
+ "data-[state=active]:text-foreground",
89
82
  "data-[state=active]:border-b-primary",
90
83
  "data-[state=active]:bg-transparent",
91
84
  "data-[state=active]:shadow-none"
@@ -94,46 +87,46 @@ const q = { class: "flex-1 truncate" }, G = ["aria-label", "onClick"], K = {
94
87
  "data-[state=inactive]:text-muted-foreground",
95
88
  "data-[state=inactive]:bg-transparent",
96
89
  "data-[state=inactive]:hover:text-foreground",
97
- "data-[state=inactive]:hover:border-b-border"
90
+ "data-[state=inactive]:hover:border-b-muted-foreground/30"
98
91
  ].join(" "),
99
92
  content: ""
100
93
  }
101
- }, c = s(() => h[n.styletype] ?? h.default), V = s(() => b("flex flex-col w-full h-full", n.class)), A = s(() => b(c.value.list, n.listClass)), B = s(() => b(
102
- c.value.trigger,
103
- c.value.active,
104
- c.value.inactive
105
- )), N = s(() => b("flex-1 w-full overflow-auto", c.value.content));
106
- return (e, r) => (o(), u(v(U), {
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), {
107
100
  "model-value": a.value,
108
101
  "onUpdate:modelValue": S,
109
102
  orientation: "horizontal",
110
- class: m(V.value)
103
+ class: f(z.value)
111
104
  }, {
112
- default: f(() => [
105
+ default: m(() => [
113
106
  y(v(W), {
114
- class: m(A.value)
107
+ class: f(A.value)
115
108
  }, {
116
- default: f(() => [
117
- (o(!0), p(C, null, k(d.value, (t) => (o(), u(v(X), {
109
+ default: m(() => [
110
+ (n(!0), p(C, null, k(c.value, (t) => (n(), u(v(X), {
118
111
  key: t.id,
119
112
  value: t.id,
120
113
  onClick: (x) => I(t.id),
121
- class: m(B.value)
114
+ class: f(B.value)
122
115
  }, {
123
- default: f(() => [
124
- t.icon ? (o(), u(R, {
116
+ default: m(() => [
117
+ t.icon ? (n(), u(R, {
125
118
  key: 0,
126
119
  name: t.icon,
127
120
  size: "sm",
128
121
  class: "flex-shrink-0"
129
122
  }, null, 8, ["name"])) : T("", !0),
130
- g("span", q, w(t.label), 1),
131
- t.closable ? (o(), p("button", {
123
+ b("span", q, w(t.label), 1),
124
+ t.closable ? (n(), p("button", {
132
125
  key: 1,
133
126
  type: "button",
134
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",
135
128
  "aria-label": `${t.label} 탭 닫기`,
136
- onClick: (x) => z(x, t.id)
129
+ onClick: (x) => V(x, t.id)
137
130
  }, [
138
131
  y(v(J), { class: "h-2.5 w-2.5" })
139
132
  ], 8, G)) : T("", !0)
@@ -143,21 +136,21 @@ const q = { class: "flex-1 truncate" }, G = ["aria-label", "onClick"], K = {
143
136
  ]),
144
137
  _: 1
145
138
  }, 8, ["class"]),
146
- g("div", {
147
- class: m(N.value)
139
+ b("div", {
140
+ class: f(N.value)
148
141
  }, [
149
- (o(!0), p(C, null, k(d.value, (t) => (o(), u(v(Y), {
142
+ (n(!0), p(C, null, k(c.value, (t) => (n(), u(v(Y), {
150
143
  key: `content-${t.id}`,
151
144
  value: t.id,
152
145
  class: "h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col data-[state=active]:animate-tab-fade-in"
153
146
  }, {
154
- default: f(() => [
147
+ default: m(() => [
155
148
  L(e.$slots, `content-${t.id}`, { tab: t }, () => [
156
- t.component ? (o(), u(F(t.component), H({
149
+ t.component ? (n(), u(F(t.component), H({
157
150
  key: 0,
158
151
  ref_for: !0
159
- }, t.props || {}), null, 16)) : (o(), p("div", K, [
160
- g("p", M, w(t.label) + " 콘텐츠", 1)
152
+ }, t.props || {}), null, 16)) : (n(), p("div", K, [
153
+ b("p", M, w(t.label) + " 콘텐츠", 1)
161
154
  ]))
162
155
  ], !0)
163
156
  ]),
@@ -170,6 +163,6 @@ const q = { class: "flex-1 truncate" }, G = ["aria-label", "onClick"], K = {
170
163
  }
171
164
  });
172
165
  export {
173
- ie as default
166
+ se as default
174
167
  };
175
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 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: Bordered/Card 스타일 (엔터프라이즈)\n // 상단 primary accent + 탭↔콘텐츠 시각적 연결\n default: {\n list: 'w-full justify-start gap-1 px-1 bg-muted/30 border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]',\n trigger: [\n 'relative px-3 py-1.5 text-xs font-medium',\n 'rounded-t-md rounded-b-none',\n 'border border-transparent border-b-0',\n 'transition-all duration-150',\n 'flex items-center gap-2',\n ].join(' '),\n active: [\n 'data-[state=active]:bg-background',\n 'data-[state=active]:text-foreground',\n 'data-[state=active]:font-medium',\n 'data-[state=active]:border-border',\n 'data-[state=active]:border-t-primary',\n 'data-[state=active]:border-t-2',\n 'data-[state=active]:shadow-sm',\n 'data-[state=active]:z-10',\n 'data-[state=active]:-mb-px',\n ].join(' '),\n inactive: [\n 'data-[state=inactive]:bg-muted/20',\n 'data-[state=inactive]:text-muted-foreground',\n 'data-[state=inactive]:border-border/40',\n 'data-[state=inactive]:hover:bg-muted/40',\n 'data-[state=inactive]:hover:text-foreground',\n ].join(' '),\n content: 'border border-border border-t-0 bg-background rounded-b-md',\n },\n\n // Minimal: Underline 스타일 (심플/사이드바용)\n // 하단 indicator 바만 표시, 테두리 없음\n minimal: {\n list: 'w-full justify-start gap-0.5 px-0 bg-transparent border-b border-border overflow-x-auto overflow-y-hidden h-auto min-h-[2rem]',\n trigger: [\n 'relative px-2.5 py-1 text-xs font-medium',\n 'rounded-none border-b-2 border-transparent',\n 'transition-all duration-150',\n 'flex items-center gap-2',\n ].join(' '),\n active: [\n 'data-[state=active]:text-primary',\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-border',\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
+ {"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,7 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),s=require("../../lib/utils.cjs"),c=e.defineComponent({__name:"JLayout",props:{styletype:{default:"default"},contentScroll:{type:Boolean,default:!0},class:{}},setup(r){const l=r,t={default:{containerClass:"flex flex-col h-screen w-full overflow-hidden",contentClass:"flex flex-1 overflow-hidden"},minimal:{containerClass:"flex flex-col h-screen w-full overflow-hidden",contentClass:"flex flex-1 overflow-hidden"}},n=e.computed(()=>t[l.styletype]??t.default),a=e.computed(()=>s.cn(n.value.contentClass,!l.contentScroll&&"overflow-hidden"));return(o,d)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(s.cn)(n.value.containerClass,l.class))},[e.renderSlot(o.$slots,"header"),e.createElementVNode("div",{class:e.normalizeClass(a.value)},[e.renderSlot(o.$slots,"sidebar"),e.createElementVNode("div",{class:e.normalizeClass(["flex-1 flex flex-col min-h-0",l.contentScroll?"overflow-auto":"overflow-hidden"])},[e.renderSlot(o.$slots,"content",{},()=>[e.renderSlot(o.$slots,"default")])],2)],2)],2))}});exports.default=c;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JLayout.vue2.cjs");;/* empty css */const t = (t_comp, t_opts) => {
2
+ const t_merged = t_comp.__vccOpts || t_comp;
3
+ for (const [t_key, t_val] of t_opts)
4
+ t_merged[t_key] = t_val;
5
+ return t_merged;
6
+ };,u=t(e.default,[["__scopeId","data-v-af519966"]]);exports.default=u;
2
7
  //# sourceMappingURL=JLayout.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JLayout.vue.cjs","sources":["../../../../src/components/templates/JLayout.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JLayout - 기본 레이아웃 컴포넌트 (templates)\n * Base Layout Component\n * \n * @description\n * 공통 레이아웃 구조를 제공하는 기본 컴포넌트입니다.\n * Header, Sidebar, Content 영역을 슬롯으로 제공하여 유연한 레이아웃 구성이 가능합니다.\n * \n * 레이아웃 구조:\n * - header: 상단 헤더 영역 (슬롯)\n * - sidebar: 사이드바 영역 (슬롯)\n * - content: 메인 콘텐츠 영역 (슬롯, 기본 슬롯도 지원)\n * \n * @example\n * ```vue\n * <JLayout styletype=\"default\" :content-scroll=\"true\">\n * <template #header>\n * <JHeader logo-text=\"JWMS Portal\" />\n * </template>\n * <template #sidebar>\n * <JSidebarSimple :menu-items=\"menuItems\" />\n * </template>\n * <template #content>\n * <div>메인 콘텐츠</div>\n * </template>\n * </JLayout>\n * ```\n * \n * @example 기본 슬롯 사용\n * ```vue\n * <JLayout>\n * <div>기본 콘텐츠</div>\n * </JLayout>\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 레이아웃 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n styletype: 'default',\n contentScroll: true,\n }\n)\n\n// JLayout은 사이드바 상태를 직접 관리하지 않고, 슬롯으로 전달\n// Preset 컴포넌트(JLayoutAdvanced, JLayoutSimple)에서 상태 관리\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n minimal: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n !props.contentScroll && 'overflow-hidden'\n )\n})\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 헤더 슬롯 -->\n <slot name=\"header\" />\n\n <!-- 메인 컨텐츠 영역 -->\n <div :class=\"contentClasses\">\n <!-- 사이드바 슬롯 (사이드바가 보일 때만 border 표시) -->\n <slot name=\"sidebar\" />\n\n <!-- 콘텐츠 슬롯 -->\n <div class=\"flex-1 flex flex-col min-h-0\" :class=\"props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\">\n <slot name=\"content\">\n <!-- 기본 슬롯도 지원 -->\n <slot />\n </slot>\n </div>\n </div>\n </div>\n</template>\n"],"names":["props","__props","STYLE_PRESETS","preset","computed","contentClasses","cn","_createElementBlock","_normalizeClass","_unref","_renderSlot","_ctx","_createElementVNode"],"mappings":"0SA0CA,MAAMA,EAAQC,EAqBRC,EAGD,CACH,QAAS,CACP,eAAgB,gDAChB,aAAc,6BAAA,EAEhB,QAAS,CACP,eAAgB,gDAChB,aAAc,6BAAA,CAChB,EAGIC,EAASC,EAAAA,SAAS,IACfF,EAAcF,EAAM,SAAS,GAAKE,EAAc,OACxD,EAEKG,EAAiBD,EAAAA,SAAS,IACvBE,EAAAA,GACLH,EAAO,MAAM,aACb,CAACH,EAAM,eAAiB,iBAAA,CAE3B,8BAICO,EAAAA,mBAiBM,MAAA,CAjBA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGN,EAAA,MAAO,eAAgBH,EAAM,KAAK,CAAA,CAAA,GAEhDU,aAAsBC,EAAA,OAAA,QAAA,EAGtBC,EAAAA,mBAWM,MAAA,CAXA,uBAAOP,EAAA,KAAc,CAAA,GAEzBK,aAAuBC,EAAA,OAAA,SAAA,EAGvBC,EAAAA,mBAKM,MAAA,CALD,MAAKJ,EAAAA,eAAA,CAAC,+BAAuCR,EAAM,cAAa,gBAAA,iBAAA,CAAA,CAAA,GACnEU,EAAAA,WAGOC,sBAHP,IAGO,CADLD,aAAQC,EAAA,OAAA,SAAA,CAAA"}
1
+ {"version":3,"file":"JLayout.vue.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,46 +1,13 @@
1
- import { defineComponent as u, computed as r, createElementBlock as i, openBlock as p, normalizeClass as t, unref as m, renderSlot as o, createElementVNode as a } from "vue";
2
- import { cn as c } from "../../lib/utils.js";
3
- const x = /* @__PURE__ */ u({
4
- __name: "JLayout",
5
- props: {
6
- styletype: { default: "default" },
7
- contentScroll: { type: Boolean, default: !0 },
8
- class: {}
9
- },
10
- setup(f) {
11
- const e = f, n = {
12
- default: {
13
- containerClass: "flex flex-col h-screen w-full overflow-hidden",
14
- contentClass: "flex flex-1 overflow-hidden"
15
- },
16
- minimal: {
17
- containerClass: "flex flex-col h-screen w-full overflow-hidden",
18
- contentClass: "flex flex-1 overflow-hidden"
19
- }
20
- }, s = r(() => n[e.styletype] ?? n.default), d = r(() => c(
21
- s.value.contentClass,
22
- !e.contentScroll && "overflow-hidden"
23
- ));
24
- return (l, v) => (p(), i("div", {
25
- class: t(m(c)(s.value.containerClass, e.class))
26
- }, [
27
- o(l.$slots, "header"),
28
- a("div", {
29
- class: t(d.value)
30
- }, [
31
- o(l.$slots, "sidebar"),
32
- a("div", {
33
- class: t(["flex-1 flex flex-col min-h-0", e.contentScroll ? "overflow-auto" : "overflow-hidden"])
34
- }, [
35
- o(l.$slots, "content", {}, () => [
36
- o(l.$slots, "default")
37
- ])
38
- ], 2)
39
- ], 2)
40
- ], 2));
41
- }
42
- });
1
+ import o from "./JLayout.vue2.js";
2
+ /* empty css */
3
+ const t = (t_comp, t_opts) => {
4
+ const t_merged = t_comp.__vccOpts || t_comp;
5
+ for (const [t_key, t_val] of t_opts)
6
+ t_merged[t_key] = t_val;
7
+ return t_merged;
8
+ };
9
+ const m = /* @__PURE__ */ t(o, [["__scopeId", "data-v-af519966"]]);
43
10
  export {
44
- x as default
11
+ m as default
45
12
  };
46
13
  //# sourceMappingURL=JLayout.vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JLayout.vue.js","sources":["../../../../src/components/templates/JLayout.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JLayout - 기본 레이아웃 컴포넌트 (templates)\n * Base Layout Component\n * \n * @description\n * 공통 레이아웃 구조를 제공하는 기본 컴포넌트입니다.\n * Header, Sidebar, Content 영역을 슬롯으로 제공하여 유연한 레이아웃 구성이 가능합니다.\n * \n * 레이아웃 구조:\n * - header: 상단 헤더 영역 (슬롯)\n * - sidebar: 사이드바 영역 (슬롯)\n * - content: 메인 콘텐츠 영역 (슬롯, 기본 슬롯도 지원)\n * \n * @example\n * ```vue\n * <JLayout styletype=\"default\" :content-scroll=\"true\">\n * <template #header>\n * <JHeader logo-text=\"JWMS Portal\" />\n * </template>\n * <template #sidebar>\n * <JSidebarSimple :menu-items=\"menuItems\" />\n * </template>\n * <template #content>\n * <div>메인 콘텐츠</div>\n * </template>\n * </JLayout>\n * ```\n * \n * @example 기본 슬롯 사용\n * ```vue\n * <JLayout>\n * <div>기본 콘텐츠</div>\n * </JLayout>\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 레이아웃 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n styletype: 'default',\n contentScroll: true,\n }\n)\n\n// JLayout은 사이드바 상태를 직접 관리하지 않고, 슬롯으로 전달\n// Preset 컴포넌트(JLayoutAdvanced, JLayoutSimple)에서 상태 관리\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n minimal: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n !props.contentScroll && 'overflow-hidden'\n )\n})\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 헤더 슬롯 -->\n <slot name=\"header\" />\n\n <!-- 메인 컨텐츠 영역 -->\n <div :class=\"contentClasses\">\n <!-- 사이드바 슬롯 (사이드바가 보일 때만 border 표시) -->\n <slot name=\"sidebar\" />\n\n <!-- 콘텐츠 슬롯 -->\n <div class=\"flex-1 flex flex-col min-h-0\" :class=\"props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\">\n <slot name=\"content\">\n <!-- 기본 슬롯도 지원 -->\n <slot />\n </slot>\n </div>\n </div>\n </div>\n</template>\n"],"names":["props","__props","STYLE_PRESETS","preset","computed","contentClasses","cn","_createElementBlock","_normalizeClass","_unref","_renderSlot","_ctx","_createElementVNode"],"mappings":";;;;;;;;;;AA0CA,UAAMA,IAAQC,GAqBRC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,MAEhB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB,GAGIC,IAASC,EAAS,MACfF,EAAcF,EAAM,SAAS,KAAKE,EAAc,OACxD,GAEKG,IAAiBD,EAAS,MACvBE;AAAA,MACLH,EAAO,MAAM;AAAA,MACb,CAACH,EAAM,iBAAiB;AAAA,IAAA,CAE3B;2BAICO,EAiBM,OAAA;AAAA,MAjBA,OAAKC,EAAEC,KAAGN,EAAA,MAAO,gBAAgBH,EAAM,KAAK,CAAA;AAAA,IAAA;MAEhDU,EAAsBC,EAAA,QAAA,QAAA;AAAA,MAGtBC,EAWM,OAAA;AAAA,QAXA,SAAOP,EAAA,KAAc;AAAA,MAAA;QAEzBK,EAAuBC,EAAA,QAAA,SAAA;AAAA,QAGvBC,EAKM,OAAA;AAAA,UALD,OAAKJ,EAAA,CAAC,gCAAuCR,EAAM,gBAAa,kBAAA,iBAAA,CAAA;AAAA,QAAA;UACnEU,EAGOC,yBAHP,MAGO;AAAA,YADLD,EAAQC,EAAA,QAAA,SAAA;AAAA,UAAA;;;;;;"}
1
+ {"version":3,"file":"JLayout.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JLayout.vue.cjs");exports.default=e.default;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),s=require("../../lib/utils.cjs"),m=require("../../composables/useBreakpoint.cjs"),v={key:0,class:"fixed inset-y-0 left-0 z-50 w-[280px] max-w-[80vw] shadow-xl"},p=e.defineComponent({__name:"JLayout",props:{styletype:{default:"default"},contentScroll:{type:Boolean,default:!0},class:{},sidebarOverlay:{type:Boolean,default:!1},sidebarOpen:{type:Boolean,default:!1}},emits:["backdrop-click"],setup(o,{emit:r}){const t=o,c=r,{isMobile:d}=m.useBreakpoint(),i=e.computed(()=>t.sidebarOverlay||d.value),n={default:{containerClass:"flex flex-col h-screen w-full overflow-hidden",contentClass:"flex flex-1 overflow-hidden"},minimal:{containerClass:"flex flex-col h-screen w-full overflow-hidden",contentClass:"flex flex-1 overflow-hidden"}},a=e.computed(()=>n[t.styletype]??n.default),u=e.computed(()=>s.cn(a.value.contentClass,!t.contentScroll&&"overflow-hidden")),f=()=>{c("backdrop-click")};return(l,k)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(s.cn)(a.value.containerClass,t.class))},[e.renderSlot(l.$slots,"header",{},void 0,!0),e.createElementVNode("div",{class:e.normalizeClass(u.value)},[i.value?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createVNode(e.Transition,{name:"j-sidebar-backdrop"},{default:e.withCtx(()=>[o.sidebarOpen?(e.openBlock(),e.createElementBlock("div",{key:0,class:"fixed inset-0 z-40 bg-black/40",onClick:f})):e.createCommentVNode("",!0)]),_:1}),e.createVNode(e.Transition,{name:"j-sidebar-drawer"},{default:e.withCtx(()=>[o.sidebarOpen?(e.openBlock(),e.createElementBlock("div",v,[e.renderSlot(l.$slots,"sidebar",{},void 0,!0)])):e.createCommentVNode("",!0)]),_:3})],64)):e.renderSlot(l.$slots,"sidebar",{key:1},void 0,!0),e.createElementVNode("div",{class:e.normalizeClass(["flex-1 flex flex-col min-h-0",t.contentScroll?"overflow-auto":"overflow-hidden"])},[e.renderSlot(l.$slots,"content",{},()=>[e.renderSlot(l.$slots,"default",{},void 0,!0)],!0)],2)],2)],2))}});exports.default=p;
2
2
  //# sourceMappingURL=JLayout.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JLayout.vue2.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"JLayout.vue2.cjs","sources":["../../../../src/components/templates/JLayout.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { useBreakpoint } from '@/composables/useBreakpoint'\n\n/**\n * JLayout - 기본 레이아웃 컴포넌트 (templates)\n * Base Layout Component\n *\n * @description\n * 공통 레이아웃 구조를 제공하는 기본 컴포넌트입니다.\n * Header, Sidebar, Content 영역을 슬롯으로 제공하여 유연한 레이아웃 구성이 가능합니다.\n * 모바일에서는 사이드바가 오버레이 드로어로 전환됩니다.\n *\n * 레이아웃 구조:\n * - header: 상단 헤더 영역 (슬롯)\n * - sidebar: 사이드바 영역 (슬롯)\n * - content: 메인 콘텐츠 영역 (슬롯, 기본 슬롯도 지원)\n *\n * @example\n * ```vue\n * <JLayout styletype=\"default\" :content-scroll=\"true\">\n * <template #header>\n * <JHeader logo-text=\"JWMS Portal\" />\n * </template>\n * <template #sidebar>\n * <JSidebarSimple :menu-items=\"menuItems\" />\n * </template>\n * <template #content>\n * <div>메인 콘텐츠</div>\n * </template>\n * </JLayout>\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 레이아웃 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n /** 모바일에서 사이드바를 오버레이 드로어로 표시 */\n sidebarOverlay?: boolean\n /** 사이드바 오버레이 열림 상태 (sidebarOverlay=true일 때만 사용) */\n sidebarOpen?: boolean\n }>(),\n {\n styletype: 'default',\n contentScroll: true,\n sidebarOverlay: false,\n sidebarOpen: false,\n }\n)\n\nconst emit = defineEmits<{\n 'backdrop-click': []\n}>()\n\nconst { isMobile } = useBreakpoint()\n\n// 실제 오버레이 모드: prop으로 명시하거나 모바일 자동 감지\nconst isOverlayMode = computed(() => props.sidebarOverlay || isMobile.value)\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n minimal: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n !props.contentScroll && 'overflow-hidden'\n )\n})\n\nconst handleBackdropClick = () => {\n emit('backdrop-click')\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 헤더 슬롯 -->\n <slot name=\"header\" />\n\n <!-- 메인 컨텐츠 영역 -->\n <div :class=\"contentClasses\">\n <!-- 사이드바: 데스크톱은 인라인, 모바일은 오버레이 -->\n <template v-if=\"isOverlayMode\">\n <!-- 모바일 오버레이 드로어 -->\n <Transition name=\"j-sidebar-backdrop\">\n <div\n v-if=\"sidebarOpen\"\n class=\"fixed inset-0 z-40 bg-black/40\"\n @click=\"handleBackdropClick\"\n />\n </Transition>\n <Transition name=\"j-sidebar-drawer\">\n <div\n v-if=\"sidebarOpen\"\n class=\"fixed inset-y-0 left-0 z-50 w-[280px] max-w-[80vw] shadow-xl\"\n >\n <slot name=\"sidebar\" />\n </div>\n </Transition>\n </template>\n <template v-else>\n <!-- 데스크톱 인라인 사이드바 -->\n <slot name=\"sidebar\" />\n </template>\n\n <!-- 콘텐츠 슬롯 -->\n <div class=\"flex-1 flex flex-col min-h-0\" :class=\"props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\">\n <slot name=\"content\">\n <!-- 기본 슬롯도 지원 -->\n <slot />\n </slot>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* 사이드바 드로어 슬라이드 애니메이션 */\n.j-sidebar-drawer-enter-active,\n.j-sidebar-drawer-leave-active {\n transition: transform 0.25s ease;\n}\n.j-sidebar-drawer-enter-from,\n.j-sidebar-drawer-leave-to {\n transform: translateX(-100%);\n}\n\n/* 백드롭 페이드 애니메이션 */\n.j-sidebar-backdrop-enter-active,\n.j-sidebar-backdrop-leave-active {\n transition: opacity 0.25s ease;\n}\n.j-sidebar-backdrop-enter-from,\n.j-sidebar-backdrop-leave-to {\n opacity: 0;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isMobile","useBreakpoint","isOverlayMode","computed","STYLE_PRESETS","preset","contentClasses","cn","handleBackdropClick","_createElementBlock","_normalizeClass","_unref","_renderSlot","_ctx","_createElementVNode","_Fragment","_createVNode","_Transition","_openBlock","_hoisted_1"],"mappings":"2hBAqCA,MAAMA,EAAQC,EAqBRC,EAAOC,EAIP,CAAE,SAAAC,CAAA,EAAaC,gBAAA,EAGfC,EAAgBC,EAAAA,SAAS,IAAMP,EAAM,gBAAkBI,EAAS,KAAK,EAKrEI,EAGD,CACH,QAAS,CACP,eAAgB,gDAChB,aAAc,6BAAA,EAEhB,QAAS,CACP,eAAgB,gDAChB,aAAc,6BAAA,CAChB,EAGIC,EAASF,EAAAA,SAAS,IACfC,EAAcR,EAAM,SAAS,GAAKQ,EAAc,OACxD,EAEKE,EAAiBH,EAAAA,SAAS,IACvBI,EAAAA,GACLF,EAAO,MAAM,aACb,CAACT,EAAM,eAAiB,iBAAA,CAE3B,EAEKY,EAAsB,IAAM,CAChCV,EAAK,gBAAgB,CACvB,8BAIEW,EAAAA,mBAsCM,MAAA,CAtCA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGN,EAAA,MAAO,eAAgBT,EAAM,KAAK,CAAA,CAAA,GAEhDgB,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,EAGtBC,EAAAA,mBAgCM,MAAA,CAhCA,uBAAOR,EAAA,KAAc,CAAA,GAETJ,EAAA,qBAAhBO,EAAAA,mBAiBWM,EAAAA,SAAA,CAAA,IAAA,GAAA,CAfTC,EAAAA,YAMaC,EAAAA,WAAA,CAND,KAAK,sBAAoB,mBACnC,IAIE,CAHMpB,EAAA,2BADRY,EAAAA,mBAIE,MAAA,OAFA,MAAM,iCACL,QAAOD,CAAA,wCAGZQ,EAAAA,YAOaC,EAAAA,WAAA,CAPD,KAAK,oBAAkB,mBACjC,IAKM,CAJEpB,EAAA,aADRqB,EAAAA,UAAA,EAAAT,EAAAA,mBAKM,MALNU,EAKM,CADJP,EAAAA,WAAuBC,EAAA,OAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA,8CAM3BD,aAAuBC,EAAA,OAAA,UAAA,CAAA,IAAA,GAAA,OAAA,EAAA,EAIzBC,EAAAA,mBAKM,MAAA,CALD,MAAKJ,EAAAA,eAAA,CAAC,+BAAuCd,EAAM,cAAa,gBAAA,iBAAA,CAAA,CAAA,GACnEgB,EAAAA,WAGOC,sBAHP,IAGO,CADLD,EAAAA,WAAQC,EAAA,OAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA"}
@@ -1,5 +1,74 @@
1
- import f from "./JLayout.vue.js";
1
+ import { defineComponent as C, computed as n, createElementBlock as o, openBlock as s, normalizeClass as r, unref as B, renderSlot as t, createElementVNode as c, Fragment as S, createVNode as f, Transition as u, withCtx as m, createCommentVNode as v } from "vue";
2
+ import { cn as p } from "../../lib/utils.js";
3
+ import { useBreakpoint as O } from "../../composables/useBreakpoint.js";
4
+ const E = {
5
+ key: 0,
6
+ class: "fixed inset-y-0 left-0 z-50 w-[280px] max-w-[80vw] shadow-xl"
7
+ }, V = /* @__PURE__ */ C({
8
+ __name: "JLayout",
9
+ props: {
10
+ styletype: { default: "default" },
11
+ contentScroll: { type: Boolean, default: !0 },
12
+ class: {},
13
+ sidebarOverlay: { type: Boolean, default: !1 },
14
+ sidebarOpen: { type: Boolean, default: !1 }
15
+ },
16
+ emits: ["backdrop-click"],
17
+ setup(a, { emit: k }) {
18
+ const e = a, y = k, { isMobile: b } = O(), h = n(() => e.sidebarOverlay || b.value), d = {
19
+ default: {
20
+ containerClass: "flex flex-col h-screen w-full overflow-hidden",
21
+ contentClass: "flex flex-1 overflow-hidden"
22
+ },
23
+ minimal: {
24
+ containerClass: "flex flex-col h-screen w-full overflow-hidden",
25
+ contentClass: "flex flex-1 overflow-hidden"
26
+ }
27
+ }, i = n(() => d[e.styletype] ?? d.default), w = n(() => p(
28
+ i.value.contentClass,
29
+ !e.contentScroll && "overflow-hidden"
30
+ )), x = () => {
31
+ y("backdrop-click");
32
+ };
33
+ return (l, $) => (s(), o("div", {
34
+ class: r(B(p)(i.value.containerClass, e.class))
35
+ }, [
36
+ t(l.$slots, "header", {}, void 0, !0),
37
+ c("div", {
38
+ class: r(w.value)
39
+ }, [
40
+ h.value ? (s(), o(S, { key: 0 }, [
41
+ f(u, { name: "j-sidebar-backdrop" }, {
42
+ default: m(() => [
43
+ a.sidebarOpen ? (s(), o("div", {
44
+ key: 0,
45
+ class: "fixed inset-0 z-40 bg-black/40",
46
+ onClick: x
47
+ })) : v("", !0)
48
+ ]),
49
+ _: 1
50
+ }),
51
+ f(u, { name: "j-sidebar-drawer" }, {
52
+ default: m(() => [
53
+ a.sidebarOpen ? (s(), o("div", E, [
54
+ t(l.$slots, "sidebar", {}, void 0, !0)
55
+ ])) : v("", !0)
56
+ ]),
57
+ _: 3
58
+ })
59
+ ], 64)) : t(l.$slots, "sidebar", { key: 1 }, void 0, !0),
60
+ c("div", {
61
+ class: r(["flex-1 flex flex-col min-h-0", e.contentScroll ? "overflow-auto" : "overflow-hidden"])
62
+ }, [
63
+ t(l.$slots, "content", {}, () => [
64
+ t(l.$slots, "default", {}, void 0, !0)
65
+ ], !0)
66
+ ], 2)
67
+ ], 2)
68
+ ], 2));
69
+ }
70
+ });
2
71
  export {
3
- f as default
72
+ V as default
4
73
  };
5
74
  //# sourceMappingURL=JLayout.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JLayout.vue2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"JLayout.vue2.js","sources":["../../../../src/components/templates/JLayout.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { useBreakpoint } from '@/composables/useBreakpoint'\n\n/**\n * JLayout - 기본 레이아웃 컴포넌트 (templates)\n * Base Layout Component\n *\n * @description\n * 공통 레이아웃 구조를 제공하는 기본 컴포넌트입니다.\n * Header, Sidebar, Content 영역을 슬롯으로 제공하여 유연한 레이아웃 구성이 가능합니다.\n * 모바일에서는 사이드바가 오버레이 드로어로 전환됩니다.\n *\n * 레이아웃 구조:\n * - header: 상단 헤더 영역 (슬롯)\n * - sidebar: 사이드바 영역 (슬롯)\n * - content: 메인 콘텐츠 영역 (슬롯, 기본 슬롯도 지원)\n *\n * @example\n * ```vue\n * <JLayout styletype=\"default\" :content-scroll=\"true\">\n * <template #header>\n * <JHeader logo-text=\"JWMS Portal\" />\n * </template>\n * <template #sidebar>\n * <JSidebarSimple :menu-items=\"menuItems\" />\n * </template>\n * <template #content>\n * <div>메인 콘텐츠</div>\n * </template>\n * </JLayout>\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(\n defineProps<{\n /** 레이아웃 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n /** 모바일에서 사이드바를 오버레이 드로어로 표시 */\n sidebarOverlay?: boolean\n /** 사이드바 오버레이 열림 상태 (sidebarOverlay=true일 때만 사용) */\n sidebarOpen?: boolean\n }>(),\n {\n styletype: 'default',\n contentScroll: true,\n sidebarOverlay: false,\n sidebarOpen: false,\n }\n)\n\nconst emit = defineEmits<{\n 'backdrop-click': []\n}>()\n\nconst { isMobile } = useBreakpoint()\n\n// 실제 오버레이 모드: prop으로 명시하거나 모바일 자동 감지\nconst isOverlayMode = computed(() => props.sidebarOverlay || isMobile.value)\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n minimal: {\n containerClass: 'flex flex-col h-screen w-full overflow-hidden',\n contentClass: 'flex flex-1 overflow-hidden',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n !props.contentScroll && 'overflow-hidden'\n )\n})\n\nconst handleBackdropClick = () => {\n emit('backdrop-click')\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 헤더 슬롯 -->\n <slot name=\"header\" />\n\n <!-- 메인 컨텐츠 영역 -->\n <div :class=\"contentClasses\">\n <!-- 사이드바: 데스크톱은 인라인, 모바일은 오버레이 -->\n <template v-if=\"isOverlayMode\">\n <!-- 모바일 오버레이 드로어 -->\n <Transition name=\"j-sidebar-backdrop\">\n <div\n v-if=\"sidebarOpen\"\n class=\"fixed inset-0 z-40 bg-black/40\"\n @click=\"handleBackdropClick\"\n />\n </Transition>\n <Transition name=\"j-sidebar-drawer\">\n <div\n v-if=\"sidebarOpen\"\n class=\"fixed inset-y-0 left-0 z-50 w-[280px] max-w-[80vw] shadow-xl\"\n >\n <slot name=\"sidebar\" />\n </div>\n </Transition>\n </template>\n <template v-else>\n <!-- 데스크톱 인라인 사이드바 -->\n <slot name=\"sidebar\" />\n </template>\n\n <!-- 콘텐츠 슬롯 -->\n <div class=\"flex-1 flex flex-col min-h-0\" :class=\"props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\">\n <slot name=\"content\">\n <!-- 기본 슬롯도 지원 -->\n <slot />\n </slot>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* 사이드바 드로어 슬라이드 애니메이션 */\n.j-sidebar-drawer-enter-active,\n.j-sidebar-drawer-leave-active {\n transition: transform 0.25s ease;\n}\n.j-sidebar-drawer-enter-from,\n.j-sidebar-drawer-leave-to {\n transform: translateX(-100%);\n}\n\n/* 백드롭 페이드 애니메이션 */\n.j-sidebar-backdrop-enter-active,\n.j-sidebar-backdrop-leave-active {\n transition: opacity 0.25s ease;\n}\n.j-sidebar-backdrop-enter-from,\n.j-sidebar-backdrop-leave-to {\n opacity: 0;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isMobile","useBreakpoint","isOverlayMode","computed","STYLE_PRESETS","preset","contentClasses","cn","handleBackdropClick","_createElementBlock","_normalizeClass","_unref","_renderSlot","_ctx","_createElementVNode","_Fragment","_createVNode","_Transition","_openBlock","_hoisted_1"],"mappings":";;;;;;;;;;;;;;;;;AAqCA,UAAMA,IAAQC,GAqBRC,IAAOC,GAIP,EAAE,UAAAC,EAAA,IAAaC,EAAA,GAGfC,IAAgBC,EAAS,MAAMP,EAAM,kBAAkBI,EAAS,KAAK,GAKrEI,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,MAEhB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB,GAGIC,IAASF,EAAS,MACfC,EAAcR,EAAM,SAAS,KAAKQ,EAAc,OACxD,GAEKE,IAAiBH,EAAS,MACvBI;AAAA,MACLF,EAAO,MAAM;AAAA,MACb,CAACT,EAAM,iBAAiB;AAAA,IAAA,CAE3B,GAEKY,IAAsB,MAAM;AAChC,MAAAV,EAAK,gBAAgB;AAAA,IACvB;2BAIEW,EAsCM,OAAA;AAAA,MAtCA,OAAKC,EAAEC,KAAGN,EAAA,MAAO,gBAAgBT,EAAM,KAAK,CAAA;AAAA,IAAA;MAEhDgB,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,MAGtBC,EAgCM,OAAA;AAAA,QAhCA,SAAOR,EAAA,KAAc;AAAA,MAAA;QAETJ,EAAA,cAAhBO,EAiBWM,GAAA,EAAA,KAAA,KAAA;AAAA,UAfTC,EAMaC,GAAA,EAND,MAAK,wBAAoB;AAAA,uBACnC,MAIE;AAAA,cAHMpB,EAAA,oBADRY,EAIE,OAAA;AAAA;gBAFA,OAAM;AAAA,gBACL,SAAOD;AAAA,cAAA;;;;UAGZQ,EAOaC,GAAA,EAPD,MAAK,sBAAkB;AAAA,uBACjC,MAKM;AAAA,cAJEpB,EAAA,eADRqB,EAAA,GAAAT,EAKM,OALNU,GAKM;AAAA,gBADJP,EAAuBC,EAAA,QAAA,WAAA,CAAA,GAAA,QAAA,EAAA;AAAA,cAAA;;;;kBAM3BD,EAAuBC,EAAA,QAAA,WAAA,EAAA,KAAA,KAAA,QAAA,EAAA;AAAA,QAIzBC,EAKM,OAAA;AAAA,UALD,OAAKJ,EAAA,CAAC,gCAAuCd,EAAM,gBAAa,kBAAA,iBAAA,CAAA;AAAA,QAAA;UACnEgB,EAGOC,yBAHP,MAGO;AAAA,YADLD,EAAQC,EAAA,QAAA,WAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;;;;"}