@j-solution/components 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -22
- package/README.md +370 -81
- package/UPDATE_GUIDE.md +441 -441
- package/USAGE_GUIDE.md +1 -1
- package/assets/jwms-portal-frontend-BqyV9oqF.css +1 -0
- package/assets/styles/j-components.css +1 -1
- package/assets/styles/main.css +29 -0
- package/components/atoms/JToast.vue.cjs +2 -0
- package/components/atoms/JToast.vue.cjs.map +1 -0
- package/components/atoms/JToast.vue.js +37 -0
- package/components/atoms/JToast.vue.js.map +1 -0
- package/components/atoms/JToast.vue2.cjs +2 -0
- package/components/atoms/JToast.vue2.cjs.map +1 -0
- package/components/atoms/JToast.vue2.js +5 -0
- package/components/atoms/JToast.vue2.js.map +1 -0
- package/components/molecules/JAccordion.vue.cjs.map +1 -1
- package/components/molecules/JAccordion.vue.js.map +1 -1
- package/components/molecules/JButtonGroup.vue.cjs.map +1 -1
- package/components/molecules/JButtonGroup.vue.js.map +1 -1
- package/components/organisms/JHeader.vue.cjs.map +1 -1
- package/components/organisms/JHeader.vue.js.map +1 -1
- package/components/organisms/JSearchPanel.vue.cjs +1 -1
- package/components/organisms/JSearchPanel.vue.js +2 -2
- package/components/organisms/JSearchPanel.vue2.cjs.map +1 -1
- package/components/organisms/JSearchPanel.vue2.js.map +1 -1
- package/components/shadcn/Toaster.vue.cjs +2 -0
- package/components/shadcn/Toaster.vue.cjs.map +1 -0
- package/components/shadcn/Toaster.vue.js +36 -0
- package/components/shadcn/Toaster.vue.js.map +1 -0
- package/components/shadcn/Toaster.vue2.cjs +2 -0
- package/components/shadcn/Toaster.vue2.cjs.map +1 -0
- package/components/shadcn/Toaster.vue2.js +5 -0
- package/components/shadcn/Toaster.vue2.js.map +1 -0
- package/components/shadcn/index.cjs +1 -1
- package/components/shadcn/index.cjs.map +1 -1
- package/components/shadcn/index.js +1 -0
- package/components/shadcn/index.js.map +1 -1
- package/index.cjs +1 -1
- package/index.js +89 -86
- package/package.json +2 -1
- package/types/index.d.ts +8 -0
- package/assets/jwms-portal-frontend-DjoLgoaO.css +0 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--radius: 0.5rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* 기본 테마 변수는 themes.css에서 :root에 정의됨 */
|
|
11
|
+
|
|
12
|
+
/* 다크 모드는 테마 클래스와 함께 작동 */
|
|
13
|
+
.dark {
|
|
14
|
+
/* 다크 모드 변수는 각 테마에서 정의됨 */
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@layer base {
|
|
19
|
+
/* 전역 border 스타일 적용 */
|
|
20
|
+
* {
|
|
21
|
+
@apply border-border;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
@apply bg-background text-foreground;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");const r=require("../shadcn/Toaster.vue.cjs"),s=e.defineComponent({__name:"JToast",props:{id:{},invert:{type:Boolean},theme:{},position:{default:"top-center"},closeButtonPosition:{},hotkey:{},richColors:{type:Boolean,default:!0},expand:{type:Boolean,default:!0},duration:{},gap:{},visibleToasts:{},closeButton:{type:Boolean,default:!1},toastOptions:{},class:{},style:{},offset:{},mobileOffset:{},dir:{},swipeDirections:{},icons:{},containerAriaLabel:{}},setup(t){const o=t;return(n,a)=>(e.openBlock(),e.createBlock(e.unref(r.default),e.normalizeProps(e.guardReactiveProps(o)),null,16))}});exports.default=s;
|
|
2
|
+
//# sourceMappingURL=JToast.vue.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JToast.vue.cjs","sources":["../../../../src/components/atoms/JToast.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { Toaster } from '@/components/shadcn'\r\nimport type { ToasterProps } from 'vue-sonner'\r\n\r\nconst props = withDefaults(\r\n defineProps<ToasterProps>(),\r\n {\r\n position: 'top-center',\r\n expand: true,\r\n richColors: true,\r\n closeButton: false,\r\n },\r\n)\r\n</script>\r\n\r\n<template>\r\n <Toaster v-bind=\"props\" />\r\n</template>\r\n\r\n"],"names":["props","__props","_openBlock","_createBlock","_unref"],"mappings":"gnBAIA,MAAMA,EAAQC,gBAYZC,YAAA,EAAAC,cAA0BC,EAAAA,uDAATJ,CAAK,CAAA,EAAA,KAAA,EAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineComponent as t, createBlock as n, openBlock as s, unref as a, normalizeProps as r, guardReactiveProps as i } from "vue";
|
|
2
|
+
import "../shadcn/index.js";
|
|
3
|
+
import l from "../shadcn/Toaster.vue.js";
|
|
4
|
+
const d = /* @__PURE__ */ t({
|
|
5
|
+
__name: "JToast",
|
|
6
|
+
props: {
|
|
7
|
+
id: {},
|
|
8
|
+
invert: { type: Boolean },
|
|
9
|
+
theme: {},
|
|
10
|
+
position: { default: "top-center" },
|
|
11
|
+
closeButtonPosition: {},
|
|
12
|
+
hotkey: {},
|
|
13
|
+
richColors: { type: Boolean, default: !0 },
|
|
14
|
+
expand: { type: Boolean, default: !0 },
|
|
15
|
+
duration: {},
|
|
16
|
+
gap: {},
|
|
17
|
+
visibleToasts: {},
|
|
18
|
+
closeButton: { type: Boolean, default: !1 },
|
|
19
|
+
toastOptions: {},
|
|
20
|
+
class: {},
|
|
21
|
+
style: {},
|
|
22
|
+
offset: {},
|
|
23
|
+
mobileOffset: {},
|
|
24
|
+
dir: {},
|
|
25
|
+
swipeDirections: {},
|
|
26
|
+
icons: {},
|
|
27
|
+
containerAriaLabel: {}
|
|
28
|
+
},
|
|
29
|
+
setup(e) {
|
|
30
|
+
const o = e;
|
|
31
|
+
return (p, c) => (s(), n(a(l), r(i(o)), null, 16));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
export {
|
|
35
|
+
d as default
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=JToast.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JToast.vue.js","sources":["../../../../src/components/atoms/JToast.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { Toaster } from '@/components/shadcn'\r\nimport type { ToasterProps } from 'vue-sonner'\r\n\r\nconst props = withDefaults(\r\n defineProps<ToasterProps>(),\r\n {\r\n position: 'top-center',\r\n expand: true,\r\n richColors: true,\r\n closeButton: false,\r\n },\r\n)\r\n</script>\r\n\r\n<template>\r\n <Toaster v-bind=\"props\" />\r\n</template>\r\n\r\n"],"names":["props","__props","_openBlock","_createBlock","_unref"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,UAAMA,IAAQC;sBAYZC,EAAA,GAAAC,EAA0BC,UAATJ,CAAK,CAAA,GAAA,MAAA,EAAA;AAAA;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JToast.vue2.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JToast.vue2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JAccordion.vue.cjs","sources":["../../../../src/components/molecules/JAccordion.vue"],"sourcesContent":["<script setup lang=\"ts\">\
|
|
1
|
+
{"version":3,"file":"JAccordion.vue.cjs","sources":["../../../../src/components/molecules/JAccordion.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/shadcn'\nimport { cn } from \"@/lib/utils\"\n\nexport interface AccordionItem {\n /** 아이템 고유값 */\n value: string\n /** 트리거(제목) 텍스트 */\n title?: string\n /** 내용 텍스트 */\n content?: string\n /** 비활성화 여부 */\n disabled?: boolean\n}\n\n// 타입 안정성을 위한 Conditional Props 정의\ntype SingleAccordionProps = {\n /** Accordion 자체의 클래스 */\n class?: HTMLAttributes[\"class\"]\n /** Accordion 아이템 배열 */\n items: AccordionItem[]\n /** Accordion 타입 - single: 하나씩만 열림 */\n type?: \"single\"\n /** collapsible 여부 (type이 single일 때만 적용) */\n collapsible?: boolean\n /** 기본 선택된 값 (single 모드) */\n defaultValue?: string\n}\n\ntype MultipleAccordionProps = {\n /** Accordion 자체의 클래스 */\n class?: HTMLAttributes[\"class\"]\n /** Accordion 아이템 배열 */\n items: AccordionItem[]\n /** Accordion 타입 - multiple: 여러 개 열림 */\n type: \"multiple\"\n /** 기본 선택된 값들 (multiple 모드) */\n defaultValue?: string[]\n /** collapsible 여부 (type이 multiple일 때는 무시됨) */\n collapsible?: never\n}\n\ntype AccordionProps = SingleAccordionProps | MultipleAccordionProps\n\nconst props = withDefaults(defineProps<AccordionProps>(), {\n type: \"single\",\n collapsible: true,\n items: () => []\n})\n\nconst emit = defineEmits<{\n valueChange: [value: string | string[] | undefined]\n}>()\n</script>\n\n<template>\n <Accordion \n :type=\"props.type\" \n :collapsible=\"props.type === 'multiple' ? undefined : props.collapsible\"\n :defaultValue=\"props.defaultValue\"\n :class=\"cn('w-full', props.class)\"\n @update:model-value=\"(value: string | string[] | undefined) => emit('valueChange', value)\"\n >\n <AccordionItem \n v-for=\"item in props.items\" \n :key=\"item.value\"\n :value=\"item.value\"\n :disabled=\"item.disabled\"\n >\n <AccordionTrigger>\n <slot name=\"trigger\" :item=\"item\">\n {{ item.title }}\n </slot>\n </AccordionTrigger>\n <AccordionContent>\n <slot name=\"content\" :item=\"item\">\n {{ item.content }}\n </slot>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n</template>\n\n"],"names":["props","__props","emit","__emit","_createBlock","_unref","Accordion","_normalizeClass","cn","_cache","value","_openBlock","_createElementBlock","_Fragment","_renderList","item","AccordionItem","_createVNode","AccordionTrigger","_renderSlot","_ctx","_createTextVNode","_toDisplayString","AccordionContent"],"mappings":"skBA6CA,MAAMA,EAAQC,EAMRC,EAAOC,8BAMXC,EAAAA,YAwBYC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CAvBT,KAAMN,EAAM,KACZ,YAAaA,EAAM,kBAAsB,OAAYA,EAAM,YAC3D,aAAcA,EAAM,aACpB,MAAKO,EAAAA,eAAEF,QAAAG,EAAAA,EAAA,EAAE,SAAWR,EAAM,KAAK,CAAA,EAC/B,sBAAkBS,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAGC,GAAyCR,gBAAoBQ,CAAK,EAAA,qBAGtF,IAA2B,EAD7BC,YAAA,EAAA,EAAAC,EAAAA,mBAgBgBC,EAAAA,SAAA,KAAAC,EAAAA,WAfCd,EAAM,MAAde,kBADTX,EAAAA,YAgBgBC,EAAAA,MAAAW,EAAAA,OAAA,EAAA,CAdb,IAAKD,EAAK,MACV,MAAOA,EAAK,MACZ,SAAUA,EAAK,QAAA,qBAEhB,IAImB,CAJnBE,EAAAA,YAImBZ,EAAAA,MAAAa,SAAA,EAAA,KAAA,mBAHjB,IAEO,CAFPC,EAAAA,WAEOC,EAAA,OAAA,UAAA,CAFe,KAAAL,CAAA,EAAtB,IAEO,CADFM,EAAAA,gBAAAC,EAAAA,gBAAAP,EAAK,KAAK,EAAA,CAAA,CAAA,gBAGjBE,EAAAA,YAImBZ,EAAAA,MAAAkB,SAAA,EAAA,KAAA,mBAHjB,IAEO,CAFPJ,EAAAA,WAEOC,EAAA,OAAA,UAAA,CAFe,KAAAL,CAAA,EAAtB,IAEO,CADFM,EAAAA,gBAAAC,EAAAA,gBAAAP,EAAK,OAAO,EAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JAccordion.vue.js","sources":["../../../../src/components/molecules/JAccordion.vue"],"sourcesContent":["<script setup lang=\"ts\">\
|
|
1
|
+
{"version":3,"file":"JAccordion.vue.js","sources":["../../../../src/components/molecules/JAccordion.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/shadcn'\nimport { cn } from \"@/lib/utils\"\n\nexport interface AccordionItem {\n /** 아이템 고유값 */\n value: string\n /** 트리거(제목) 텍스트 */\n title?: string\n /** 내용 텍스트 */\n content?: string\n /** 비활성화 여부 */\n disabled?: boolean\n}\n\n// 타입 안정성을 위한 Conditional Props 정의\ntype SingleAccordionProps = {\n /** Accordion 자체의 클래스 */\n class?: HTMLAttributes[\"class\"]\n /** Accordion 아이템 배열 */\n items: AccordionItem[]\n /** Accordion 타입 - single: 하나씩만 열림 */\n type?: \"single\"\n /** collapsible 여부 (type이 single일 때만 적용) */\n collapsible?: boolean\n /** 기본 선택된 값 (single 모드) */\n defaultValue?: string\n}\n\ntype MultipleAccordionProps = {\n /** Accordion 자체의 클래스 */\n class?: HTMLAttributes[\"class\"]\n /** Accordion 아이템 배열 */\n items: AccordionItem[]\n /** Accordion 타입 - multiple: 여러 개 열림 */\n type: \"multiple\"\n /** 기본 선택된 값들 (multiple 모드) */\n defaultValue?: string[]\n /** collapsible 여부 (type이 multiple일 때는 무시됨) */\n collapsible?: never\n}\n\ntype AccordionProps = SingleAccordionProps | MultipleAccordionProps\n\nconst props = withDefaults(defineProps<AccordionProps>(), {\n type: \"single\",\n collapsible: true,\n items: () => []\n})\n\nconst emit = defineEmits<{\n valueChange: [value: string | string[] | undefined]\n}>()\n</script>\n\n<template>\n <Accordion \n :type=\"props.type\" \n :collapsible=\"props.type === 'multiple' ? undefined : props.collapsible\"\n :defaultValue=\"props.defaultValue\"\n :class=\"cn('w-full', props.class)\"\n @update:model-value=\"(value: string | string[] | undefined) => emit('valueChange', value)\"\n >\n <AccordionItem \n v-for=\"item in props.items\" \n :key=\"item.value\"\n :value=\"item.value\"\n :disabled=\"item.disabled\"\n >\n <AccordionTrigger>\n <slot name=\"trigger\" :item=\"item\">\n {{ item.title }}\n </slot>\n </AccordionTrigger>\n <AccordionContent>\n <slot name=\"content\" :item=\"item\">\n {{ item.content }}\n </slot>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n</template>\n\n"],"names":["props","__props","emit","__emit","_createBlock","_unref","Accordion","_normalizeClass","cn","_cache","value","_openBlock","_createElementBlock","_Fragment","_renderList","item","AccordionItem","_createVNode","AccordionTrigger","_renderSlot","_ctx","_createTextVNode","_toDisplayString","AccordionContent"],"mappings":";;;;;;;;;;;;;;;;;;AA6CA,UAAMA,IAAQC,GAMRC,IAAOC;2BAMXC,EAwBYC,EAAAC,CAAA,GAAA;AAAA,MAvBT,MAAMN,EAAM;AAAA,MACZ,aAAaA,EAAM,sBAAsB,SAAYA,EAAM;AAAA,MAC3D,cAAcA,EAAM;AAAA,MACpB,OAAKO,EAAEF,EAAAG,CAAA,EAAE,UAAWR,EAAM,KAAK,CAAA;AAAA,MAC/B,uBAAkBS,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGC,MAAyCR,iBAAoBQ,CAAK;AAAA,IAAA;iBAGtF,MAA2B;AAAA,SAD7BC,EAAA,EAAA,GAAAC,EAgBgBC,GAAA,MAAAC,EAfCd,EAAM,QAAde,YADTX,EAgBgBC,EAAAW,CAAA,GAAA;AAAA,UAdb,KAAKD,EAAK;AAAA,UACV,OAAOA,EAAK;AAAA,UACZ,UAAUA,EAAK;AAAA,QAAA;qBAEhB,MAImB;AAAA,YAJnBE,EAImBZ,EAAAa,CAAA,GAAA,MAAA;AAAA,yBAHjB,MAEO;AAAA,gBAFPC,EAEOC,EAAA,QAAA,WAAA,EAFe,MAAAL,EAAA,GAAtB,MAEO;AAAA,kBADFM,EAAAC,EAAAP,EAAK,KAAK,GAAA,CAAA;AAAA,gBAAA;;;;YAGjBE,EAImBZ,EAAAkB,CAAA,GAAA,MAAA;AAAA,yBAHjB,MAEO;AAAA,gBAFPJ,EAEOC,EAAA,QAAA,WAAA,EAFe,MAAAL,EAAA,GAAtB,MAEO;AAAA,kBADFM,EAAAC,EAAAP,EAAK,OAAO,GAAA,CAAA;AAAA,gBAAA;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JButtonGroup.vue.cjs","sources":["../../../../src/components/molecules/JButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\
|
|
1
|
+
{"version":3,"file":"JButtonGroup.vue.cjs","sources":["../../../../src/components/molecules/JButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { h, useSlots, computed, cloneVNode, type VNode } from 'vue'\nimport type { ButtonGroupVariants } from '@/components/shadcn/button-group-variants'\nimport { ButtonGroup, ButtonGroupSeparator } from '@/components/shadcn'\n\n// -------------------------\n// props 및 기본값 선언\nconst props = withDefaults(\n defineProps<{\n orientation?: ButtonGroupVariants['orientation'] // 수평/수직\n showButtonSeparators?: boolean // 버튼 사이 separator 표시 여부 (기본값: true)\n }>(),\n {\n orientation: 'horizontal',\n showButtonSeparators: true\n }\n)\n\n// useSlots로 하위 슬롯 노드 접근\nconst slots = useSlots()\n\n// 버튼 사이 separator 표시 여부\nconst shouldShowButtonSeparators = computed(() => props.showButtonSeparators)\n\n// 버튼의 styletype이 outline인지 확인\nconst isButtonOutline = (child: any): boolean => {\n if (!child || typeof child !== 'object') return false\n \n // props에서 styletype 확인\n const styletype = child.props?.styletype || child.props?.['styletype']\n return styletype === 'outline'\n}\n\n// slot으로 받은 버튼 리스트에 버튼 사이마다 separator를 자동 삽입\n// outline 버튼의 양옆에는 구분선을 넣지 않음 (outline은 자체 경계가 있어 구분선이 중복됨)\n// outline 버튼이 양옆에 구분선이 없을 때는 왼쪽 border를 복원해야 함\nconst slotWithSeparators = computed(() => {\n const children = slots.default?.() || []\n if (!shouldShowButtonSeparators.value) return children\n \n const result: VNode[] = []\n children.forEach((child: VNode, idx: number) => {\n if (idx > 0) {\n // 이전 버튼과 현재 버튼 확인\n const prevChild = children[idx - 1]\n const isPrevOutline = isButtonOutline(prevChild)\n const isCurrentOutline = isButtonOutline(child)\n \n // outline 버튼이 포함되지 않을 때만 구분선 추가\n // (outline 버튼은 자체 경계가 있어 양옆에 구분선이 필요 없음)\n if (!(isPrevOutline || isCurrentOutline)) {\n result.push(h(ButtonGroupSeparator, {\n orientation: props.orientation === 'vertical' ? 'horizontal' : 'vertical'\n }))\n }\n }\n \n // outline 버튼이 양옆에 구분선이 없을 때 왼쪽 border 복원\n let childToAdd = child\n if (isButtonOutline(child)) {\n const prevChild = idx > 0 ? children[idx - 1] : null\n const isPrevOutline = prevChild ? isButtonOutline(prevChild) : false\n \n // 이전 버튼이 outline이 아니고 (구분선이 없음), 현재 버튼이 첫 번째가 아니면 왼쪽 border 복원\n if (!isPrevOutline && idx > 0) {\n // VNode를 복제하면서 class 추가\n const existingClass = child.props?.class || child.props?.['class'] || ''\n let newClass: string | Array<string | object> | object\n \n if (typeof existingClass === 'string') {\n // 문자열인 경우: 공백으로 구분하여 추가\n newClass = `${existingClass} !border-l`\n } else if (Array.isArray(existingClass)) {\n // 배열인 경우: 새 클래스를 배열에 추가\n newClass = [...existingClass, '!border-l']\n } else if (existingClass && typeof existingClass === 'object') {\n // 객체인 경우: 객체에 border-l 클래스 추가\n newClass = { ...existingClass, '!border-l': true }\n } else {\n // 기타 경우: 배열로 변환\n newClass = [existingClass, '!border-l'].filter(Boolean)\n }\n \n // cloneVNode를 사용하여 VNode를 복제하고 props 수정\n childToAdd = cloneVNode(child, {\n ...child.props,\n class: newClass\n })\n }\n }\n \n result.push(childToAdd)\n })\n return result\n})\n</script>\n\n<template>\n <!-- 바깥 ButtonGroup: 버튼 모음 내부 그룹을 수직/수평으로 감쌈 -->\n <ButtonGroup :orientation=\"props.orientation ?? 'horizontal'\">\n <!-- 내부 ButtonGroup: 버튼들 렌더 + 필요시 버튼 사이 separator 자동 삽입 -->\n <ButtonGroup :orientation=\"props.orientation ?? 'horizontal'\">\n <template v-if=\"shouldShowButtonSeparators\">\n <template v-for=\"(vnode, _idx) in slotWithSeparators\" :key=\"_idx\">\n <component :is=\"vnode\" />\n </template>\n </template>\n <slot v-else />\n </ButtonGroup>\n </ButtonGroup>\n</template>\n\n\n"],"names":["props","__props","slots","useSlots","shouldShowButtonSeparators","computed","isButtonOutline","child","slotWithSeparators","children","result","idx","prevChild","isPrevOutline","isCurrentOutline","h","ButtonGroupSeparator","childToAdd","existingClass","newClass","cloneVNode","_createBlock","_unref","ButtonGroup","_createVNode","_openBlock","_createElementBlock","_Fragment","_renderList","vnode","_idx","_resolveDynamicComponent","_renderSlot","_ctx"],"mappings":"qZAOA,MAAMA,EAAQC,EAYRC,EAAQC,EAAAA,SAAA,EAGRC,EAA6BC,EAAAA,SAAS,IAAML,EAAM,oBAAoB,EAGtEM,EAAmBC,GACnB,CAACA,GAAS,OAAOA,GAAU,SAAiB,IAG9BA,EAAM,OAAO,WAAaA,EAAM,OAAQ,aACrC,UAMjBC,EAAqBH,EAAAA,SAAS,IAAM,CACxC,MAAMI,EAAWP,EAAM,UAAA,GAAe,CAAA,EACtC,GAAI,CAACE,EAA2B,MAAO,OAAOK,EAE9C,MAAMC,EAAkB,CAAA,EACxB,OAAAD,EAAS,QAAQ,CAACF,EAAcI,IAAgB,CAC9C,GAAIA,EAAM,EAAG,CAEX,MAAMC,EAAYH,EAASE,EAAM,CAAC,EAC5BE,EAAgBP,EAAgBM,CAAS,EACzCE,EAAmBR,EAAgBC,CAAK,EAIxCM,GAAiBC,GACrBJ,EAAO,KAAKK,EAAAA,EAAEC,UAAsB,CAClC,YAAahB,EAAM,cAAgB,WAAa,aAAe,UAAA,CAChE,CAAC,CAEN,CAGA,IAAIiB,EAAaV,EACjB,GAAID,EAAgBC,CAAK,EAAG,CAC1B,MAAMK,EAAYD,EAAM,EAAIF,EAASE,EAAM,CAAC,EAAI,KAIhD,GAAI,EAHkBC,EAAYN,EAAgBM,CAAS,EAAI,KAGzCD,EAAM,EAAG,CAE7B,MAAMO,EAAgBX,EAAM,OAAO,OAASA,EAAM,OAAQ,OAAY,GACtE,IAAIY,EAEA,OAAOD,GAAkB,SAE3BC,EAAW,GAAGD,CAAa,aAClB,MAAM,QAAQA,CAAa,EAEpCC,EAAW,CAAC,GAAGD,EAAe,WAAW,EAChCA,GAAiB,OAAOA,GAAkB,SAEnDC,EAAW,CAAE,GAAGD,EAAe,YAAa,EAAA,EAG5CC,EAAW,CAACD,EAAe,WAAW,EAAE,OAAO,OAAO,EAIxDD,EAAaG,EAAAA,WAAWb,EAAO,CAC7B,GAAGA,EAAM,MACT,MAAOY,CAAA,CACR,CACH,CACF,CAEAT,EAAO,KAAKO,CAAU,CACxB,CAAC,EACMP,CACT,CAAC,8BAKCW,EAAAA,YAUcC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CAVA,YAAavB,EAAM,aAAW,YAAA,qBAE1C,IAOc,CAPdwB,cAOcF,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CAPA,YAAavB,EAAM,aAAW,YAAA,qBAC1C,IAIW,CAJKI,EAAA,OACdqB,EAAAA,UAAA,EAAA,EAAAC,EAAAA,mBAEWC,EAAAA,SAAA,CAAA,IAAA,CAAA,EAAAC,EAAAA,WAFuBpB,EAAA,MAAkB,CAAlCqB,EAAOC,mBACvBT,EAAAA,YAAyBU,EAAAA,wBAATF,CAAK,EAAA,CAAA,IADqCC,EAAI,UAIlEE,aAAeC,EAAA,OAAA,UAAA,CAAA,IAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JButtonGroup.vue.js","sources":["../../../../src/components/molecules/JButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\
|
|
1
|
+
{"version":3,"file":"JButtonGroup.vue.js","sources":["../../../../src/components/molecules/JButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { h, useSlots, computed, cloneVNode, type VNode } from 'vue'\nimport type { ButtonGroupVariants } from '@/components/shadcn/button-group-variants'\nimport { ButtonGroup, ButtonGroupSeparator } from '@/components/shadcn'\n\n// -------------------------\n// props 및 기본값 선언\nconst props = withDefaults(\n defineProps<{\n orientation?: ButtonGroupVariants['orientation'] // 수평/수직\n showButtonSeparators?: boolean // 버튼 사이 separator 표시 여부 (기본값: true)\n }>(),\n {\n orientation: 'horizontal',\n showButtonSeparators: true\n }\n)\n\n// useSlots로 하위 슬롯 노드 접근\nconst slots = useSlots()\n\n// 버튼 사이 separator 표시 여부\nconst shouldShowButtonSeparators = computed(() => props.showButtonSeparators)\n\n// 버튼의 styletype이 outline인지 확인\nconst isButtonOutline = (child: any): boolean => {\n if (!child || typeof child !== 'object') return false\n \n // props에서 styletype 확인\n const styletype = child.props?.styletype || child.props?.['styletype']\n return styletype === 'outline'\n}\n\n// slot으로 받은 버튼 리스트에 버튼 사이마다 separator를 자동 삽입\n// outline 버튼의 양옆에는 구분선을 넣지 않음 (outline은 자체 경계가 있어 구분선이 중복됨)\n// outline 버튼이 양옆에 구분선이 없을 때는 왼쪽 border를 복원해야 함\nconst slotWithSeparators = computed(() => {\n const children = slots.default?.() || []\n if (!shouldShowButtonSeparators.value) return children\n \n const result: VNode[] = []\n children.forEach((child: VNode, idx: number) => {\n if (idx > 0) {\n // 이전 버튼과 현재 버튼 확인\n const prevChild = children[idx - 1]\n const isPrevOutline = isButtonOutline(prevChild)\n const isCurrentOutline = isButtonOutline(child)\n \n // outline 버튼이 포함되지 않을 때만 구분선 추가\n // (outline 버튼은 자체 경계가 있어 양옆에 구분선이 필요 없음)\n if (!(isPrevOutline || isCurrentOutline)) {\n result.push(h(ButtonGroupSeparator, {\n orientation: props.orientation === 'vertical' ? 'horizontal' : 'vertical'\n }))\n }\n }\n \n // outline 버튼이 양옆에 구분선이 없을 때 왼쪽 border 복원\n let childToAdd = child\n if (isButtonOutline(child)) {\n const prevChild = idx > 0 ? children[idx - 1] : null\n const isPrevOutline = prevChild ? isButtonOutline(prevChild) : false\n \n // 이전 버튼이 outline이 아니고 (구분선이 없음), 현재 버튼이 첫 번째가 아니면 왼쪽 border 복원\n if (!isPrevOutline && idx > 0) {\n // VNode를 복제하면서 class 추가\n const existingClass = child.props?.class || child.props?.['class'] || ''\n let newClass: string | Array<string | object> | object\n \n if (typeof existingClass === 'string') {\n // 문자열인 경우: 공백으로 구분하여 추가\n newClass = `${existingClass} !border-l`\n } else if (Array.isArray(existingClass)) {\n // 배열인 경우: 새 클래스를 배열에 추가\n newClass = [...existingClass, '!border-l']\n } else if (existingClass && typeof existingClass === 'object') {\n // 객체인 경우: 객체에 border-l 클래스 추가\n newClass = { ...existingClass, '!border-l': true }\n } else {\n // 기타 경우: 배열로 변환\n newClass = [existingClass, '!border-l'].filter(Boolean)\n }\n \n // cloneVNode를 사용하여 VNode를 복제하고 props 수정\n childToAdd = cloneVNode(child, {\n ...child.props,\n class: newClass\n })\n }\n }\n \n result.push(childToAdd)\n })\n return result\n})\n</script>\n\n<template>\n <!-- 바깥 ButtonGroup: 버튼 모음 내부 그룹을 수직/수평으로 감쌈 -->\n <ButtonGroup :orientation=\"props.orientation ?? 'horizontal'\">\n <!-- 내부 ButtonGroup: 버튼들 렌더 + 필요시 버튼 사이 separator 자동 삽입 -->\n <ButtonGroup :orientation=\"props.orientation ?? 'horizontal'\">\n <template v-if=\"shouldShowButtonSeparators\">\n <template v-for=\"(vnode, _idx) in slotWithSeparators\" :key=\"_idx\">\n <component :is=\"vnode\" />\n </template>\n </template>\n <slot v-else />\n </ButtonGroup>\n </ButtonGroup>\n</template>\n\n\n"],"names":["props","__props","slots","useSlots","shouldShowButtonSeparators","computed","isButtonOutline","child","slotWithSeparators","children","result","idx","prevChild","isPrevOutline","isCurrentOutline","h","ButtonGroupSeparator","childToAdd","existingClass","newClass","cloneVNode","_createBlock","_unref","ButtonGroup","_createVNode","_openBlock","_createElementBlock","_Fragment","_renderList","vnode","_idx","_resolveDynamicComponent","_renderSlot","_ctx"],"mappings":";;;;;;;;;;;AAOA,UAAMA,IAAQC,GAYRC,IAAQC,EAAA,GAGRC,IAA6BC,EAAS,MAAML,EAAM,oBAAoB,GAGtEM,IAAkB,CAACC,MACnB,CAACA,KAAS,OAAOA,KAAU,WAAiB,MAG9BA,EAAM,OAAO,aAAaA,EAAM,OAAQ,eACrC,WAMjBC,IAAqBH,EAAS,MAAM;AACxC,YAAMI,IAAWP,EAAM,UAAA,KAAe,CAAA;AACtC,UAAI,CAACE,EAA2B,MAAO,QAAOK;AAE9C,YAAMC,IAAkB,CAAA;AACxB,aAAAD,EAAS,QAAQ,CAACF,GAAcI,MAAgB;AAC9C,YAAIA,IAAM,GAAG;AAEX,gBAAMC,IAAYH,EAASE,IAAM,CAAC,GAC5BE,IAAgBP,EAAgBM,CAAS,GACzCE,IAAmBR,EAAgBC,CAAK;AAI9C,UAAMM,KAAiBC,KACrBJ,EAAO,KAAKK,EAAEC,GAAsB;AAAA,YAClC,aAAahB,EAAM,gBAAgB,aAAa,eAAe;AAAA,UAAA,CAChE,CAAC;AAAA,QAEN;AAGA,YAAIiB,IAAaV;AACjB,YAAID,EAAgBC,CAAK,GAAG;AAC1B,gBAAMK,IAAYD,IAAM,IAAIF,EAASE,IAAM,CAAC,IAAI;AAIhD,cAAI,EAHkBC,IAAYN,EAAgBM,CAAS,IAAI,OAGzCD,IAAM,GAAG;AAE7B,kBAAMO,IAAgBX,EAAM,OAAO,SAASA,EAAM,OAAQ,SAAY;AACtE,gBAAIY;AAEJ,YAAI,OAAOD,KAAkB,WAE3BC,IAAW,GAAGD,CAAa,eAClB,MAAM,QAAQA,CAAa,IAEpCC,IAAW,CAAC,GAAGD,GAAe,WAAW,IAChCA,KAAiB,OAAOA,KAAkB,WAEnDC,IAAW,EAAE,GAAGD,GAAe,aAAa,GAAA,IAG5CC,IAAW,CAACD,GAAe,WAAW,EAAE,OAAO,OAAO,GAIxDD,IAAaG,EAAWb,GAAO;AAAA,cAC7B,GAAGA,EAAM;AAAA,cACT,OAAOY;AAAA,YAAA,CACR;AAAA,UACH;AAAA,QACF;AAEA,QAAAT,EAAO,KAAKO,CAAU;AAAA,MACxB,CAAC,GACMP;AAAA,IACT,CAAC;2BAKCW,EAUcC,EAAAC,CAAA,GAAA;AAAA,MAVA,aAAavB,EAAM,eAAW;AAAA,IAAA;iBAE1C,MAOc;AAAA,QAPdwB,EAOcF,EAAAC,CAAA,GAAA;AAAA,UAPA,aAAavB,EAAM,eAAW;AAAA,QAAA;qBAC1C,MAIW;AAAA,YAJKI,EAAA,SACdqB,EAAA,EAAA,GAAAC,EAEWC,GAAA,EAAA,KAAA,EAAA,GAAAC,EAFuBpB,EAAA,OAAkB,CAAlCqB,GAAOC,YACvBT,EAAyBU,EAATF,CAAK,GAAA,EAAA,KADqCC,GAAI,aAIlEE,EAAeC,EAAA,QAAA,WAAA,EAAA,KAAA,EAAA,CAAA;AAAA,UAAA;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JHeader.vue.cjs","sources":["../../../../src/components/organisms/JHeader.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, ref, onMounted, onUnmounted } from 'vue'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JAvatar from '@/components/atoms/JAvatar.vue'\r\nimport JButton from '@/components/atoms/JButton.vue'\r\nimport JPopover from '@/components/atoms/JPopover.vue'\r\nimport { cn } from '@/lib/utils'\r\nimport type { ContextMenuItem, ContextMenuGroup } from '@/types/context-menu.types'\r\nimport {\r\n detectThemeClasses,\r\n ensureDefaultTheme,\r\n validateThemeExists,\r\n getDefaultTheme,\r\n getStoredTheme,\r\n setStoredTheme,\r\n applyTheme,\r\n} from '@/lib/theme-utils'\r\n\r\n// Fallback 로고 이미지 import (logo prop이 없거나 로드 실패 시 사용)\r\nimport logoFallback from '@/assets/images/logo-fallback.png'\r\n\r\n/**\r\n * JHeader - 상단 헤더 컴포넌트 (organisms)\r\n * Header Component\r\n * \r\n * @description\r\n * 애플리케이션의 상단 헤더 영역을 담당하는 컴포넌트입니다.\r\n * 로고, 네비게이션, 검색, 알림, 사용자 메뉴 등을 포함할 수 있습니다.\r\n * \r\n * @example\r\n * ```vue\r\n * <JHeader\r\n * logo=\"/logo.png\"\r\n * :user-menu-items=\"userMenuItems\"\r\n * @logo-click=\"handleLogoClick\"\r\n * />\r\n * ```\r\n */\r\n\r\nexport type HeaderNavItem = {\r\n /** 라벨 */\r\n label: string\r\n /** 링크 URL */\r\n href?: string\r\n /** 아이콘 */\r\n icon?: string\r\n /** 활성 상태 */\r\n active?: boolean\r\n /** 클릭 핸들러 */\r\n onClick?: () => void\r\n}\r\n\r\nexport type NotificationItem = {\r\n /** 알림 ID */\r\n id: string\r\n /** 알림 제목 */\r\n title: string\r\n /** 알림 내용 */\r\n message?: string\r\n /** 알림 시간 */\r\n time?: string\r\n /** 읽음 상태 */\r\n read?: boolean\r\n /** 아이콘 */\r\n icon?: string\r\n /** 클릭 핸들러 */\r\n onClick?: () => void\r\n}\r\n\r\ntype StyleType =\r\n | 'default' // 기본 스타일\r\n | 'minimal' // 최소 스타일\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 로고 이미지 URL */\r\n logo?: string\r\n /** 로고 텍스트 (기본값, 이미지가 없을 때) */\r\n logoText?: string\r\n /** 네비게이션 아이템 목록 */\r\n navItems?: HeaderNavItem[]\r\n /** 알림 표시 여부 */\r\n showNotifications?: boolean\r\n /** 알림 목록 */\r\n notifications?: NotificationItem[]\r\n /** 사용자 아바타 이미지 */\r\n userAvatar?: string\r\n /** 사용자 이름 */\r\n userName?: string\r\n /** 로그인 상태 */\r\n isLoggedIn?: boolean\r\n /** 사용자 이메일 */\r\n userEmail?: string\r\n /** 사용자 ID */\r\n userId?: string\r\n /** 스타일 타입 */\r\n styletype?: StyleType\r\n /** 사이드바 토글 버튼 표시 여부 */\r\n showSidebarToggle?: boolean\r\n /** 사이드바 열림 상태 */\r\n isSidebarOpen?: boolean\r\n /** 테마 선택기 표시 여부 */\r\n showThemeSelector?: boolean\r\n /** 초기 테마 (기본값: 'default' 또는 저장된 테마) */\r\n defaultTheme?: string\r\n /** 선택 가능한 테마 목록 (지정하지 않으면 모든 감지된 테마 사용) */\r\n availableThemes?: string[]\r\n }>(),\r\n {\r\n logoText: 'JWMS Portal', // 기본값: 텍스트 로고\r\n showNotifications: false,\r\n notifications: () => [],\r\n styletype: 'default',\r\n showSidebarToggle: true,\r\n isSidebarOpen: true,\r\n showThemeSelector: true,\r\n defaultTheme: undefined,\r\n availableThemes: undefined,\r\n }\r\n)\r\n\r\nconst emit = defineEmits<{\r\n /** 로고 클릭 이벤트 */\r\n logoClick: []\r\n /** 네비게이션 아이템 클릭 이벤트 */\r\n navClick: [item: HeaderNavItem, index: number]\r\n /** 알림 클릭 이벤트 */\r\n notificationClick: [item: NotificationItem]\r\n /** 사용자 메뉴 아이템 선택 이벤트 */\r\n userMenuSelect: [itemId: string]\r\n /** 사이드바 토글 이벤트 */\r\n sidebarToggle: []\r\n /** 로그인 버튼 클릭 이벤트 */\r\n login: []\r\n}>()\r\n\r\n/**\r\n * 스타일 프리셋\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, {\r\n containerClass: string\r\n navItemClass: string\r\n navItemActiveClass: string\r\n}> = {\r\n default: {\r\n containerClass: 'h-14 px-4 border-b border-border bg-background',\r\n navItemClass: 'text-sm text-muted-foreground hover:text-foreground transition-colors px-3 py-2 rounded-md hover:bg-accent',\r\n navItemActiveClass: 'text-foreground font-medium bg-accent',\r\n },\r\n minimal: {\r\n containerClass: 'h-12 px-3 border-b border-border bg-background',\r\n navItemClass: 'text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1 rounded-md hover:bg-accent',\r\n navItemActiveClass: 'text-foreground font-medium bg-accent',\r\n },\r\n}\r\n\r\nconst preset = computed(() => {\r\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\r\n})\r\n\r\n/**\r\n * 로고 이미지 로드 에러 상태\r\n * 0: 에러 없음, 1: 첫 번째 이미지(logo) 에러, 2: fallback 이미지도 에러\r\n */\r\nconst logoImageError = ref(0)\r\n\r\n/**\r\n * 로고 이미지 경로 계산\r\n */\r\nconst logoImageSrc = computed(() => {\r\n // props.logo가 있으면 사용\r\n if (props.logo) {\r\n return props.logo\r\n }\r\n return undefined\r\n})\r\n\r\n/**\r\n * 실제 표시할 로고 이미지 경로\r\n * 1. props.logo가 있으면 사용\r\n * 2. logo 에러 시 fallback 이미지\r\n * 3. fallback도 에러면 undefined (텍스트로 대체)\r\n */\r\nconst displayLogoImage = computed(() => {\r\n // logo prop이 없으면 이미지 표시 안함 (텍스트 사용)\r\n if (!props.logo) {\r\n return undefined\r\n }\r\n \r\n // logo 에러가 발생했으면 fallback 사용\r\n if (logoImageError.value >= 1 && logoFallback) {\r\n return logoFallback\r\n }\r\n \r\n // 정상적으로 logo 사용\r\n return logoImageSrc.value\r\n})\r\n\r\n/**\r\n * 로고 이미지 로드 에러 핸들러\r\n */\r\nconst handleLogoError = () => {\r\n // 첫 번째 이미지(logo) 에러\r\n if (logoImageError.value === 0) {\r\n logoImageError.value = 1\r\n }\r\n // fallback 이미지도 에러면 2로 설정 (텍스트로 대체)\r\n else if (logoImageError.value === 1) {\r\n logoImageError.value = 2\r\n }\r\n}\r\n\r\n/**\r\n * 읽지 않은 알림 개수\r\n */\r\nconst unreadCount = computed(() => {\r\n return props.notifications?.filter(n => !n.read).length || 0\r\n})\r\n\r\n/**\r\n * 고정 사용자 메뉴 아이템\r\n */\r\nconst fixedUserMenuItems: ContextMenuItem[] = [\r\n { id: 'profile', label: '프로필', icon: 'user' },\r\n { id: 'settings', label: '설정', icon: 'settings' },\r\n { id: 'separator', label: '', separator: true },\r\n { id: 'logout', label: '로그아웃', icon: 'logOut' },\r\n]\r\n\r\n/**\r\n * 사용자 메뉴 아이템을 ContextMenuGroup 형식으로 변환\r\n */\r\nconst userMenuGroups = computed<ContextMenuGroup[]>(() => {\r\n return [{\r\n items: fixedUserMenuItems\r\n }]\r\n})\r\n\r\n/**\r\n * 로고 클릭 핸들러\r\n */\r\nconst handleLogoClick = () => {\r\n emit('logoClick')\r\n}\r\n\r\n/**\r\n * 네비게이션 아이템 클릭 핸들러\r\n */\r\nconst handleNavClick = (item: HeaderNavItem, index: number) => {\r\n item.onClick?.()\r\n emit('navClick', item, index)\r\n}\r\n\r\n/**\r\n * 알림 클릭 핸들러\r\n */\r\nconst handleNotificationClick = (item: NotificationItem) => {\r\n item.onClick?.()\r\n emit('notificationClick', item)\r\n}\r\n\r\n/**\r\n * 사용자 메뉴 선택 핸들러\r\n */\r\nconst handleUserMenuSelect = (itemId: string) => {\r\n emit('userMenuSelect', itemId)\r\n}\r\n\r\n/**\r\n * 로그인 버튼 클릭 핸들러\r\n */\r\nconst handleLogin = () => {\r\n emit('login')\r\n}\r\n\r\n/**\r\n * 사이드바 토글 핸들러\r\n */\r\nconst handleSidebarToggle = () => {\r\n emit('sidebarToggle')\r\n}\r\n\r\n/**\r\n * tweakcn 테마 타입 정의 (동적 감지 지원)\r\n */\r\ntype ThemeName = string\r\n\r\n/**\r\n * 다크모드 상태를 추적하는 반응형 변수\r\n * - false: 라이트 모드\r\n * - true: 다크 모드\r\n * 초기값은 false(라이트 모드)이며, 컴포넌트 마운트 시 실제 테마로 업데이트됩니다.\r\n */\r\nconst isDarkMode = ref(false)\r\n\r\n/**\r\n * 현재 선택된 tweakcn 테마 (디폴트 테마로 초기화)\r\n */\r\nconst currentTheme = ref<ThemeName>(getDefaultTheme())\r\n\r\n/**\r\n * 테마 옵션 목록 (동적으로 감지)\r\n */\r\nconst themeOptions = ref<ThemeName[]>([])\r\n\r\n/**\r\n * 내부 업데이트 플래그\r\n * - true: 컴포넌트 내부에서 테마를 변경 중 (MutationObserver가 무시해야 함)\r\n * - false: 외부에서 테마가 변경됨 (MutationObserver가 동기화해야 함)\r\n */\r\nlet isInternalUpdate = false\r\n\r\n/**\r\n * MutationObserver 인스턴스\r\n */\r\nlet themeObserver: MutationObserver | null = null\r\nlet styleObserver: MutationObserver | null = null\r\n\r\n/**\r\n * Storybook 환경인지 확인하는 함수\r\n * \r\n * @returns Storybook 환경이면 true, 아니면 false\r\n */\r\nconst isStorybook = (): boolean => {\r\n if (typeof window === 'undefined') return false\r\n return !!(\r\n window.location?.href?.includes('storybook') ||\r\n (window as any).__STORYBOOK_GLOBALS__\r\n )\r\n}\r\n\r\n/**\r\n * 테마 목록을 동적으로 감지하고 업데이트합니다.\r\n */\r\nconst updateThemeOptions = () => {\r\n const detectedThemes = detectThemeClasses()\r\n const ensuredThemes = ensureDefaultTheme(detectedThemes)\r\n \r\n // availableThemes prop이 지정되면 필터링\r\n if (props.availableThemes && props.availableThemes.length > 0) {\r\n const filtered = ensuredThemes.filter(theme => props.availableThemes!.includes(theme))\r\n // default는 항상 포함\r\n themeOptions.value = ensureDefaultTheme(filtered)\r\n } else {\r\n themeOptions.value = ensuredThemes\r\n }\r\n}\r\n\r\n/**\r\n * localStorage에서 저장된 다크모드 테마를 읽어오는 함수\r\n * \r\n * 우선순위:\r\n * 1. localStorage에 저장된 'theme' 값 (사용자가 이전에 선택한 테마)\r\n * 2. 시스템 설정 (OS의 다크모드 설정)\r\n * 3. 기본값 'light' (라이트 모드)\r\n * \r\n * @returns 'light' | 'dark' - 적용할 테마\r\n */\r\nconst getStoredDarkModeTheme = (): 'light' | 'dark' => {\r\n if (typeof window === 'undefined') return 'light'\r\n \r\n const stored = localStorage.getItem('theme')\r\n if (stored === 'dark' || stored === 'light') {\r\n return stored\r\n }\r\n \r\n if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {\r\n return 'dark'\r\n }\r\n \r\n return 'light'\r\n}\r\n\r\n/**\r\n * 다크모드 테마를 HTML 문서에 적용하는 함수\r\n * \r\n * @param theme - 적용할 테마 ('light' 또는 'dark')\r\n */\r\nconst applyDarkModeTheme = (theme: 'light' | 'dark') => {\r\n isInternalUpdate = true\r\n \r\n const root = document.documentElement\r\n \r\n if (theme === 'dark') {\r\n root.classList.add('dark')\r\n isDarkMode.value = true\r\n } else {\r\n root.classList.remove('dark')\r\n isDarkMode.value = false\r\n }\r\n \r\n localStorage.setItem('theme', theme)\r\n \r\n isInternalUpdate = false\r\n}\r\n\r\n/**\r\n * 테마 토글 버튼 클릭 시 호출되는 핸들러 함수\r\n */\r\nconst handleThemeToggle = () => {\r\n const newTheme = isDarkMode.value ? 'light' : 'dark'\r\n applyDarkModeTheme(newTheme)\r\n}\r\n\r\n/**\r\n * 저장된 tweakcn 테마를 가져와서 적용합니다.\r\n */\r\nconst getStoredTweakcnTheme = (): ThemeName => {\r\n // defaultTheme prop이 지정되면 우선 사용\r\n if (props.defaultTheme) {\r\n const validated = validateThemeExists(props.defaultTheme)\r\n return validated\r\n }\r\n \r\n const stored = getStoredTheme('tweakcn-theme')\r\n if (!stored) {\r\n return getDefaultTheme()\r\n }\r\n \r\n // 저장된 테마가 유효한지 검증\r\n const validated = validateThemeExists(stored)\r\n return validated\r\n}\r\n\r\n/**\r\n * tweakcn 테마를 HTML 문서에 적용하는 함수\r\n */\r\nconst applyTweakcnTheme = (theme: ThemeName): boolean => {\r\n isInternalUpdate = true\r\n \r\n const validatedTheme = validateThemeExists(theme)\r\n const success = applyTheme(validatedTheme)\r\n \r\n if (success) {\r\n currentTheme.value = validatedTheme\r\n setStoredTheme(validatedTheme, 'tweakcn-theme')\r\n \r\n // 스토리북 환경에서만 전역 상태 동기화\r\n if (isStorybook()) {\r\n try {\r\n const storybookGlobals = (window as any).__STORYBOOK_GLOBALS__\r\n if (storybookGlobals) {\r\n storybookGlobals.theme = validatedTheme\r\n }\r\n } catch (e) {\r\n // 무시\r\n }\r\n }\r\n } else {\r\n // 실패 시 디폴트 테마로 폴백\r\n const defaultTheme = getDefaultTheme()\r\n applyTheme(defaultTheme)\r\n currentTheme.value = defaultTheme\r\n setStoredTheme(defaultTheme, 'tweakcn-theme')\r\n }\r\n \r\n isInternalUpdate = false\r\n return success\r\n}\r\n\r\n/**\r\n * 테마 변경 핸들러\r\n */\r\nconst handleThemeChange = (theme: ThemeName | string | number) => {\r\n applyTweakcnTheme(String(theme))\r\n}\r\n\r\n/**\r\n * 현재 테마에 따라 표시할 아이콘을 계산하는 computed 속성\r\n */\r\nconst themeIcon = computed(() => {\r\n return isDarkMode.value ? 'sun' : 'moon'\r\n})\r\n\r\n/**\r\n * 초기화: 저장된 테마 적용 및 테마 목록 감지\r\n */\r\nonMounted(() => {\r\n // 테마 목록 초기 감지\r\n updateThemeOptions()\r\n \r\n // 저장된 다크모드 테마 적용\r\n const darkModeTheme = getStoredDarkModeTheme()\r\n applyDarkModeTheme(darkModeTheme)\r\n \r\n // 저장된 tweakcn 테마 적용\r\n const storedTheme = getStoredTweakcnTheme()\r\n applyTweakcnTheme(storedTheme)\r\n \r\n // HTML의 dark 클래스 변경을 감지하여 헤더 컴포넌트 상태를 동기화\r\n const root = document.documentElement\r\n \r\n // 현재 dark 클래스 상태로 초기화\r\n isDarkMode.value = root.classList.contains('dark')\r\n \r\n // MutationObserver로 동적 테마 클래스 감지\r\n themeObserver = new MutationObserver(() => {\r\n if (isInternalUpdate) return\r\n \r\n // 다크모드 클래스 변경 감지\r\n const hasDarkClass = root.classList.contains('dark')\r\n if (hasDarkClass !== isDarkMode.value) {\r\n isDarkMode.value = hasDarkClass\r\n localStorage.setItem('theme', hasDarkClass ? 'dark' : 'light')\r\n }\r\n \r\n // 테마 목록 업데이트\r\n updateThemeOptions()\r\n \r\n // 현재 적용된 tweakcn 테마 확인\r\n const currentThemeClass = Array.from(root.classList).find(cls => cls.startsWith('theme-'))\r\n if (currentThemeClass) {\r\n const themeName = currentThemeClass.replace('theme-', '')\r\n if (themeOptions.value.includes(themeName)) {\r\n currentTheme.value = themeName\r\n }\r\n }\r\n })\r\n \r\n // document.documentElement의 클래스 변경 감지\r\n themeObserver.observe(document.documentElement, {\r\n attributes: true,\r\n attributeFilter: ['class'],\r\n })\r\n \r\n // 스타일시트 변경 감지 (동적으로 추가된 테마 감지)\r\n styleObserver = new MutationObserver(() => {\r\n updateThemeOptions()\r\n })\r\n \r\n styleObserver.observe(document.head, {\r\n childList: true,\r\n subtree: true,\r\n })\r\n \r\n // 스토리북 환경에서만 다크모드 및 테마 변경 감지 (추가 보완)\r\n let intervalId: ReturnType<typeof setInterval> | null = null\r\n \r\n if (isStorybook()) {\r\n const checkStorybookGlobals = () => {\r\n try {\r\n const storybookGlobals = (window as any).__STORYBOOK_GLOBALS__\r\n if (storybookGlobals) {\r\n // 다크모드 동기화\r\n if (typeof storybookGlobals.darkMode !== 'undefined') {\r\n const storybookDarkMode = storybookGlobals.darkMode\r\n if (storybookDarkMode !== isDarkMode.value) {\r\n applyDarkModeTheme(storybookDarkMode ? 'dark' : 'light')\r\n }\r\n }\r\n \r\n // tweakcn 테마 동기화\r\n if (storybookGlobals.theme && storybookGlobals.theme !== currentTheme.value) {\r\n const storybookTheme = String(storybookGlobals.theme)\r\n if (themeOptions.value.includes(storybookTheme)) {\r\n applyTweakcnTheme(storybookTheme)\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n // 무시\r\n }\r\n }\r\n \r\n // 초기 확인\r\n checkStorybookGlobals()\r\n \r\n // 주기적으로 확인 (스토리북이 직접 이벤트를 제공하지 않는 경우)\r\n intervalId = setInterval(checkStorybookGlobals, 500)\r\n }\r\n \r\n // 컴포넌트 언마운트 시 정리\r\n onUnmounted(() => {\r\n if (themeObserver) {\r\n themeObserver.disconnect()\r\n themeObserver = null\r\n }\r\n if (styleObserver) {\r\n styleObserver.disconnect()\r\n styleObserver = null\r\n }\r\n if (intervalId) {\r\n clearInterval(intervalId)\r\n }\r\n })\r\n})\r\n</script>\r\n\r\n<template>\r\n <header :class=\"cn('flex items-center justify-between w-full', preset.containerClass)\">\r\n <!-- 왼쪽: 햄버거 메뉴 + 로고 + 네비게이션 -->\r\n <div class=\"flex items-center gap-6 flex-1\">\r\n <!-- 햄버거 메뉴 버튼 (사이드바 토글) -->\r\n <JButton\r\n v-if=\"showSidebarToggle\"\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n class=\"flex-shrink-0\"\r\n aria-label=\"사이드바 토글\"\r\n @click=\"handleSidebarToggle\"\r\n >\r\n <JIcon name=\"menu\" size=\"md\" />\r\n </JButton>\r\n\r\n <!-- 로고 -->\r\n <div\r\n v-if=\"displayLogoImage || logoText\"\r\n class=\"flex items-center cursor-pointer\"\r\n @click=\"handleLogoClick\"\r\n >\r\n <!-- 로고 이미지 (logo prop이 있고 에러가 2 미만일 때) -->\r\n <img\r\n v-if=\"displayLogoImage && logoImageError < 2\"\r\n :src=\"displayLogoImage\"\r\n alt=\"로고\"\r\n class=\"h-8 w-auto\"\r\n @error=\"handleLogoError\"\r\n />\r\n <!-- 로고 텍스트 (기본 또는 모든 이미지 실패 시) -->\r\n <span\r\n v-else-if=\"logoText\"\r\n class=\"text-lg font-bold text-foreground\"\r\n >\r\n {{ logoText }}\r\n </span>\r\n </div>\r\n\r\n <!-- 네비게이션 -->\r\n <nav\r\n v-if=\"navItems && navItems.length > 0\"\r\n class=\"flex items-center gap-1\"\r\n >\r\n <JButton\r\n v-for=\"(item, index) in navItems\"\r\n :key=\"index\"\r\n variant=\"ghost\"\r\n :class=\"cn(\r\n preset.navItemClass,\r\n item.active && preset.navItemActiveClass\r\n )\"\r\n @click=\"handleNavClick(item, index)\"\r\n >\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n size=\"sm\"\r\n class=\"mr-1.5\"\r\n />\r\n {{ item.label }}\r\n </JButton>\r\n </nav>\r\n </div>\r\n\r\n <!-- 오른쪽: 테마 선택기 + 알림 + 다크모드 토글 + 사용자 메뉴 -->\r\n <div class=\"flex items-center gap-3\">\r\n <!-- tweakcn 테마 선택기 -->\r\n <JPopover\r\n v-if=\"showThemeSelector\"\r\n position=\"bottom\"\r\n align=\"end\"\r\n styletype=\"default-sm\"\r\n >\r\n <template #trigger>\r\n <JButton\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n aria-label=\"테마 선택\"\r\n >\r\n <JIcon name=\"palette\" size=\"md\" />\r\n </JButton>\r\n </template>\r\n <div class=\"p-2 min-w-[200px]\">\r\n <div class=\"text-xs font-medium text-muted-foreground px-2 py-1.5 mb-1\">\r\n 테마 선택\r\n </div>\r\n <div class=\"space-y-1\">\r\n <button\r\n v-for=\"theme in themeOptions\"\r\n :key=\"theme\"\r\n :class=\"cn(\r\n 'w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-md transition-colors',\r\n 'hover:bg-accent hover:text-accent-foreground',\r\n 'border-0 outline-none text-left',\r\n currentTheme === theme && 'bg-accent text-accent-foreground font-medium'\r\n )\"\r\n @click=\"handleThemeChange(theme)\"\r\n >\r\n <JIcon\r\n v-if=\"currentTheme === theme\"\r\n name=\"check\"\r\n size=\"sm\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span v-else class=\"w-4\" />\r\n <span class=\"flex-1 capitalize\">{{ theme }}</span>\r\n </button>\r\n </div>\r\n </div>\r\n </JPopover>\r\n\r\n <!-- 알림 -->\r\n <JPopover\r\n v-if=\"showNotifications\"\r\n position=\"bottom\"\r\n align=\"end\"\r\n styletype=\"default-sm\"\r\n >\r\n <template #trigger>\r\n <JButton\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n class=\"relative\"\r\n aria-label=\"알림\"\r\n >\r\n <JIcon name=\"circleAlert\" size=\"md\" />\r\n <span\r\n v-if=\"unreadCount > 0\"\r\n class=\"absolute top-0 right-0 h-4 w-4 rounded-full bg-destructive text-destructive-foreground text-xs flex items-center justify-center\"\r\n >\r\n {{ unreadCount > 9 ? '9+' : unreadCount }}\r\n </span>\r\n </JButton>\r\n </template>\r\n <div class=\"p-2\">\r\n <div\r\n v-if=\"notifications && notifications.length > 0\"\r\n class=\"max-h-96 overflow-y-auto space-y-1\"\r\n >\r\n <div\r\n v-for=\"notification in notifications\"\r\n :key=\"notification.id\"\r\n :class=\"cn(\r\n 'p-3 rounded-md cursor-pointer transition-colors',\r\n !notification.read ? 'bg-accent' : 'hover:bg-accent/50'\r\n )\"\r\n @click=\"handleNotificationClick(notification)\"\r\n >\r\n <div class=\"flex items-start gap-2\">\r\n <JIcon\r\n v-if=\"notification.icon\"\r\n :name=\"notification.icon\"\r\n size=\"sm\"\r\n class=\"mt-0.5 flex-shrink-0\"\r\n />\r\n <div class=\"flex-1 min-w-0\">\r\n <p class=\"text-sm font-medium text-foreground\">\r\n {{ notification.title }}\r\n </p>\r\n <p\r\n v-if=\"notification.message\"\r\n class=\"text-xs text-muted-foreground mt-1\"\r\n >\r\n {{ notification.message }}\r\n </p>\r\n <p\r\n v-if=\"notification.time\"\r\n class=\"text-xs text-muted-foreground/60 mt-1\"\r\n >\r\n {{ notification.time }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n v-else\r\n class=\"p-4 text-center text-sm text-muted-foreground\"\r\n >\r\n 알림이 없습니다.\r\n </div>\r\n </div>\r\n </JPopover>\r\n\r\n <!-- 다크모드 토글 버튼 -->\r\n <JButton\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n :aria-label=\"isDarkMode ? '라이트 모드로 전환' : '다크 모드로 전환'\"\r\n @click=\"handleThemeToggle\"\r\n >\r\n <JIcon :name=\"themeIcon\" size=\"md\" />\r\n </JButton>\r\n\r\n <!-- 사용자 메뉴 (userName이 있으면 표시) -->\r\n <JPopover\r\n v-if=\"userName\"\r\n position=\"bottom\"\r\n align=\"end\"\r\n styletype=\"default-sm\"\r\n >\r\n <template #trigger>\r\n <div class=\"flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity\">\r\n <JAvatar\r\n :src=\"userAvatar\"\r\n :fallback=\"userName ? userName[0] : 'U'\"\r\n size=\"sm\"\r\n />\r\n <span\r\n class=\"text-sm text-foreground hidden sm:inline\"\r\n >\r\n {{ userName }}\r\n </span>\r\n <JIcon name=\"chevronDown\" size=\"sm\" class=\"text-muted-foreground hidden sm:inline\" />\r\n </div>\r\n </template>\r\n <div class=\"w-full rounded-md overflow-hidden\">\r\n <!-- 프로필 정보 영역 -->\r\n <div class=\"px-3 py-2 border-b border-border\">\r\n <p class=\"text-sm font-medium text-foreground\">{{ userName }}</p>\r\n <p\r\n v-if=\"userEmail\"\r\n class=\"text-xs text-muted-foreground mt-0.5\"\r\n >\r\n {{ userEmail }}\r\n </p>\r\n </div>\r\n \r\n <!-- 메뉴 아이템 -->\r\n <template v-for=\"(group, groupIndex) in userMenuGroups\" :key=\"groupIndex\">\r\n <template v-for=\"(item, itemIndex) in group.items\" :key=\"item.id\">\r\n <button\r\n v-if=\"!item.separator\"\r\n :disabled=\"item.disabled\"\r\n :class=\"cn(\r\n 'w-full flex items-center gap-2 px-3 py-1.5 text-sm transition-colors',\r\n item.id === 'logout' \r\n ? 'text-destructive hover:bg-destructive/10 hover:text-destructive' \r\n : 'hover:bg-accent hover:text-accent-foreground',\r\n 'disabled:opacity-50 disabled:pointer-events-none',\r\n 'border-0 outline-none',\r\n group.items[itemIndex + 1]?.separator && 'border-b-0'\r\n )\"\r\n @click=\"handleUserMenuSelect(item.id)\"\r\n >\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0',\r\n item.id === 'logout' && 'text-destructive'\r\n )\"\r\n />\r\n <span class=\"flex-1 text-left truncate\">{{ item.label }}</span>\r\n </button>\r\n <div\r\n v-else-if=\"item.separator && itemIndex > 0\"\r\n class=\"h-px bg-muted my-1\"\r\n />\r\n </template>\r\n <div\r\n v-if=\"groupIndex < userMenuGroups.length - 1\"\r\n class=\"h-px bg-muted my-1\"\r\n />\r\n </template>\r\n </div>\r\n </JPopover>\r\n\r\n <!-- 로그인 버튼 (userName이 없으면 표시) -->\r\n <JButton\r\n v-else\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n aria-label=\"로그인\"\r\n @click=\"handleLogin\"\r\n >\r\n <JIcon name=\"logIn\" size=\"sm\" class=\"sm:mr-1.5\" />\r\n <span class=\"hidden sm:inline\">로그인</span>\r\n </JButton>\r\n\r\n <!-- 커스텀 슬롯 -->\r\n <slot name=\"actions\" />\r\n </div>\r\n </header>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","logoImageError","ref","logoImageSrc","displayLogoImage","logoFallback","handleLogoError","unreadCount","n","fixedUserMenuItems","userMenuGroups","handleLogoClick","handleNavClick","item","index","handleNotificationClick","handleUserMenuSelect","itemId","handleLogin","handleSidebarToggle","isDarkMode","currentTheme","getDefaultTheme","themeOptions","isInternalUpdate","themeObserver","styleObserver","isStorybook","updateThemeOptions","detectedThemes","detectThemeClasses","ensuredThemes","ensureDefaultTheme","filtered","theme","getStoredDarkModeTheme","stored","applyDarkModeTheme","root","handleThemeToggle","newTheme","getStoredTweakcnTheme","validateThemeExists","getStoredTheme","applyTweakcnTheme","validatedTheme","success","applyTheme","setStoredTheme","storybookGlobals","defaultTheme","handleThemeChange","themeIcon","onMounted","darkModeTheme","storedTheme","hasDarkClass","currentThemeClass","cls","themeName","intervalId","checkStorybookGlobals","storybookDarkMode","storybookTheme","onUnmounted","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_createBlock","JButton","_createVNode","JIcon","_hoisted_3","_toDisplayString","_openBlock","_hoisted_4","_Fragment","_renderList","$event","_hoisted_5","JPopover","_hoisted_6","_cache","_hoisted_7","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","notification","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","JAvatar","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","group","groupIndex","itemIndex","_hoisted_29","_hoisted_28","_hoisted_30","_renderSlot","_ctx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JHeader.vue.cjs","sources":["../../../../src/components/organisms/JHeader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, onMounted, onUnmounted } from 'vue'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JAvatar from '@/components/atoms/JAvatar.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JPopover from '@/components/atoms/JPopover.vue'\nimport { cn } from '@/lib/utils'\nimport type { ContextMenuItem, ContextMenuGroup } from '@/types/context-menu.types'\nimport {\n detectThemeClasses,\n ensureDefaultTheme,\n validateThemeExists,\n getDefaultTheme,\n getStoredTheme,\n setStoredTheme,\n applyTheme,\n} from '@/lib/theme-utils'\n\n// Fallback 로고 이미지 import (logo prop이 없거나 로드 실패 시 사용)\nimport logoFallback from '@/assets/images/logo-fallback.png'\n\n/**\n * JHeader - 상단 헤더 컴포넌트 (organisms)\n * Header Component\n * \n * @description\n * 애플리케이션의 상단 헤더 영역을 담당하는 컴포넌트입니다.\n * 로고, 네비게이션, 검색, 알림, 사용자 메뉴 등을 포함할 수 있습니다.\n * \n * @example\n * ```vue\n * <JHeader\n * logo=\"/logo.png\"\n * :user-menu-items=\"userMenuItems\"\n * @logo-click=\"handleLogoClick\"\n * />\n * ```\n */\n\nexport type HeaderNavItem = {\n /** 라벨 */\n label: string\n /** 링크 URL */\n href?: string\n /** 아이콘 */\n icon?: string\n /** 활성 상태 */\n active?: boolean\n /** 클릭 핸들러 */\n onClick?: () => void\n}\n\nexport type NotificationItem = {\n /** 알림 ID */\n id: string\n /** 알림 제목 */\n title: string\n /** 알림 내용 */\n message?: string\n /** 알림 시간 */\n time?: string\n /** 읽음 상태 */\n read?: boolean\n /** 아이콘 */\n icon?: string\n /** 클릭 핸들러 */\n onClick?: () => void\n}\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 로고 이미지 URL */\n logo?: string\n /** 로고 텍스트 (기본값, 이미지가 없을 때) */\n logoText?: string\n /** 네비게이션 아이템 목록 */\n navItems?: HeaderNavItem[]\n /** 알림 표시 여부 */\n showNotifications?: boolean\n /** 알림 목록 */\n notifications?: NotificationItem[]\n /** 사용자 아바타 이미지 */\n userAvatar?: string\n /** 사용자 이름 */\n userName?: string\n /** 로그인 상태 */\n isLoggedIn?: boolean\n /** 사용자 이메일 */\n userEmail?: string\n /** 사용자 ID */\n userId?: string\n /** 스타일 타입 */\n styletype?: StyleType\n /** 사이드바 토글 버튼 표시 여부 */\n showSidebarToggle?: boolean\n /** 사이드바 열림 상태 */\n isSidebarOpen?: boolean\n /** 테마 선택기 표시 여부 */\n showThemeSelector?: boolean\n /** 초기 테마 (기본값: 'default' 또는 저장된 테마) */\n defaultTheme?: string\n /** 선택 가능한 테마 목록 (지정하지 않으면 모든 감지된 테마 사용) */\n availableThemes?: string[]\n }>(),\n {\n logoText: 'JWMS Portal', // 기본값: 텍스트 로고\n showNotifications: false,\n notifications: () => [],\n styletype: 'default',\n showSidebarToggle: true,\n isSidebarOpen: true,\n showThemeSelector: true,\n defaultTheme: undefined,\n availableThemes: undefined,\n }\n)\n\nconst emit = defineEmits<{\n /** 로고 클릭 이벤트 */\n logoClick: []\n /** 네비게이션 아이템 클릭 이벤트 */\n navClick: [item: HeaderNavItem, index: number]\n /** 알림 클릭 이벤트 */\n notificationClick: [item: NotificationItem]\n /** 사용자 메뉴 아이템 선택 이벤트 */\n userMenuSelect: [itemId: string]\n /** 사이드바 토글 이벤트 */\n sidebarToggle: []\n /** 로그인 버튼 클릭 이벤트 */\n login: []\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n navItemClass: string\n navItemActiveClass: string\n}> = {\n default: {\n containerClass: 'h-14 px-4 border-b border-border bg-background',\n navItemClass: 'text-sm text-muted-foreground hover:text-foreground transition-colors px-3 py-2 rounded-md hover:bg-accent',\n navItemActiveClass: 'text-foreground font-medium bg-accent',\n },\n minimal: {\n containerClass: 'h-12 px-3 border-b border-border bg-background',\n navItemClass: 'text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1 rounded-md hover:bg-accent',\n navItemActiveClass: 'text-foreground font-medium bg-accent',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 로고 이미지 로드 에러 상태\n * 0: 에러 없음, 1: 첫 번째 이미지(logo) 에러, 2: fallback 이미지도 에러\n */\nconst logoImageError = ref(0)\n\n/**\n * 로고 이미지 경로 계산\n */\nconst logoImageSrc = computed(() => {\n // props.logo가 있으면 사용\n if (props.logo) {\n return props.logo\n }\n return undefined\n})\n\n/**\n * 실제 표시할 로고 이미지 경로\n * 1. props.logo가 있으면 사용\n * 2. logo 에러 시 fallback 이미지\n * 3. fallback도 에러면 undefined (텍스트로 대체)\n */\nconst displayLogoImage = computed(() => {\n // logo prop이 없으면 이미지 표시 안함 (텍스트 사용)\n if (!props.logo) {\n return undefined\n }\n \n // logo 에러가 발생했으면 fallback 사용\n if (logoImageError.value >= 1 && logoFallback) {\n return logoFallback\n }\n \n // 정상적으로 logo 사용\n return logoImageSrc.value\n})\n\n/**\n * 로고 이미지 로드 에러 핸들러\n */\nconst handleLogoError = () => {\n // 첫 번째 이미지(logo) 에러\n if (logoImageError.value === 0) {\n logoImageError.value = 1\n }\n // fallback 이미지도 에러면 2로 설정 (텍스트로 대체)\n else if (logoImageError.value === 1) {\n logoImageError.value = 2\n }\n}\n\n/**\n * 읽지 않은 알림 개수\n */\nconst unreadCount = computed(() => {\n return props.notifications?.filter(n => !n.read).length || 0\n})\n\n/**\n * 고정 사용자 메뉴 아이템\n */\nconst fixedUserMenuItems: ContextMenuItem[] = [\n { id: 'profile', label: '프로필', icon: 'user' },\n { id: 'settings', label: '설정', icon: 'settings' },\n { id: 'separator', label: '', separator: true },\n { id: 'logout', label: '로그아웃', icon: 'logOut' },\n]\n\n/**\n * 사용자 메뉴 아이템을 ContextMenuGroup 형식으로 변환\n */\nconst userMenuGroups = computed<ContextMenuGroup[]>(() => {\n return [{\n items: fixedUserMenuItems\n }]\n})\n\n/**\n * 로고 클릭 핸들러\n */\nconst handleLogoClick = () => {\n emit('logoClick')\n}\n\n/**\n * 네비게이션 아이템 클릭 핸들러\n */\nconst handleNavClick = (item: HeaderNavItem, index: number) => {\n item.onClick?.()\n emit('navClick', item, index)\n}\n\n/**\n * 알림 클릭 핸들러\n */\nconst handleNotificationClick = (item: NotificationItem) => {\n item.onClick?.()\n emit('notificationClick', item)\n}\n\n/**\n * 사용자 메뉴 선택 핸들러\n */\nconst handleUserMenuSelect = (itemId: string) => {\n emit('userMenuSelect', itemId)\n}\n\n/**\n * 로그인 버튼 클릭 핸들러\n */\nconst handleLogin = () => {\n emit('login')\n}\n\n/**\n * 사이드바 토글 핸들러\n */\nconst handleSidebarToggle = () => {\n emit('sidebarToggle')\n}\n\n/**\n * tweakcn 테마 타입 정의 (동적 감지 지원)\n */\ntype ThemeName = string\n\n/**\n * 다크모드 상태를 추적하는 반응형 변수\n * - false: 라이트 모드\n * - true: 다크 모드\n * 초기값은 false(라이트 모드)이며, 컴포넌트 마운트 시 실제 테마로 업데이트됩니다.\n */\nconst isDarkMode = ref(false)\n\n/**\n * 현재 선택된 tweakcn 테마 (디폴트 테마로 초기화)\n */\nconst currentTheme = ref<ThemeName>(getDefaultTheme())\n\n/**\n * 테마 옵션 목록 (동적으로 감지)\n */\nconst themeOptions = ref<ThemeName[]>([])\n\n/**\n * 내부 업데이트 플래그\n * - true: 컴포넌트 내부에서 테마를 변경 중 (MutationObserver가 무시해야 함)\n * - false: 외부에서 테마가 변경됨 (MutationObserver가 동기화해야 함)\n */\nlet isInternalUpdate = false\n\n/**\n * MutationObserver 인스턴스\n */\nlet themeObserver: MutationObserver | null = null\nlet styleObserver: MutationObserver | null = null\n\n/**\n * Storybook 환경인지 확인하는 함수\n * \n * @returns Storybook 환경이면 true, 아니면 false\n */\nconst isStorybook = (): boolean => {\n if (typeof window === 'undefined') return false\n return !!(\n window.location?.href?.includes('storybook') ||\n (window as any).__STORYBOOK_GLOBALS__\n )\n}\n\n/**\n * 테마 목록을 동적으로 감지하고 업데이트합니다.\n */\nconst updateThemeOptions = () => {\n const detectedThemes = detectThemeClasses()\n const ensuredThemes = ensureDefaultTheme(detectedThemes)\n \n // availableThemes prop이 지정되면 필터링\n if (props.availableThemes && props.availableThemes.length > 0) {\n const filtered = ensuredThemes.filter(theme => props.availableThemes!.includes(theme))\n // default는 항상 포함\n themeOptions.value = ensureDefaultTheme(filtered)\n } else {\n themeOptions.value = ensuredThemes\n }\n}\n\n/**\n * localStorage에서 저장된 다크모드 테마를 읽어오는 함수\n * \n * 우선순위:\n * 1. localStorage에 저장된 'theme' 값 (사용자가 이전에 선택한 테마)\n * 2. 시스템 설정 (OS의 다크모드 설정)\n * 3. 기본값 'light' (라이트 모드)\n * \n * @returns 'light' | 'dark' - 적용할 테마\n */\nconst getStoredDarkModeTheme = (): 'light' | 'dark' => {\n if (typeof window === 'undefined') return 'light'\n \n const stored = localStorage.getItem('theme')\n if (stored === 'dark' || stored === 'light') {\n return stored\n }\n \n if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {\n return 'dark'\n }\n \n return 'light'\n}\n\n/**\n * 다크모드 테마를 HTML 문서에 적용하는 함수\n * \n * @param theme - 적용할 테마 ('light' 또는 'dark')\n */\nconst applyDarkModeTheme = (theme: 'light' | 'dark') => {\n isInternalUpdate = true\n \n const root = document.documentElement\n \n if (theme === 'dark') {\n root.classList.add('dark')\n isDarkMode.value = true\n } else {\n root.classList.remove('dark')\n isDarkMode.value = false\n }\n \n localStorage.setItem('theme', theme)\n \n isInternalUpdate = false\n}\n\n/**\n * 테마 토글 버튼 클릭 시 호출되는 핸들러 함수\n */\nconst handleThemeToggle = () => {\n const newTheme = isDarkMode.value ? 'light' : 'dark'\n applyDarkModeTheme(newTheme)\n}\n\n/**\n * 저장된 tweakcn 테마를 가져와서 적용합니다.\n */\nconst getStoredTweakcnTheme = (): ThemeName => {\n // defaultTheme prop이 지정되면 우선 사용\n if (props.defaultTheme) {\n const validated = validateThemeExists(props.defaultTheme)\n return validated\n }\n \n const stored = getStoredTheme('tweakcn-theme')\n if (!stored) {\n return getDefaultTheme()\n }\n \n // 저장된 테마가 유효한지 검증\n const validated = validateThemeExists(stored)\n return validated\n}\n\n/**\n * tweakcn 테마를 HTML 문서에 적용하는 함수\n */\nconst applyTweakcnTheme = (theme: ThemeName): boolean => {\n isInternalUpdate = true\n \n const validatedTheme = validateThemeExists(theme)\n const success = applyTheme(validatedTheme)\n \n if (success) {\n currentTheme.value = validatedTheme\n setStoredTheme(validatedTheme, 'tweakcn-theme')\n \n // 스토리북 환경에서만 전역 상태 동기화\n if (isStorybook()) {\n try {\n const storybookGlobals = (window as any).__STORYBOOK_GLOBALS__\n if (storybookGlobals) {\n storybookGlobals.theme = validatedTheme\n }\n } catch (e) {\n // 무시\n }\n }\n } else {\n // 실패 시 디폴트 테마로 폴백\n const defaultTheme = getDefaultTheme()\n applyTheme(defaultTheme)\n currentTheme.value = defaultTheme\n setStoredTheme(defaultTheme, 'tweakcn-theme')\n }\n \n isInternalUpdate = false\n return success\n}\n\n/**\n * 테마 변경 핸들러\n */\nconst handleThemeChange = (theme: ThemeName | string | number) => {\n applyTweakcnTheme(String(theme))\n}\n\n/**\n * 현재 테마에 따라 표시할 아이콘을 계산하는 computed 속성\n */\nconst themeIcon = computed(() => {\n return isDarkMode.value ? 'sun' : 'moon'\n})\n\n/**\n * 초기화: 저장된 테마 적용 및 테마 목록 감지\n */\nonMounted(() => {\n // 테마 목록 초기 감지\n updateThemeOptions()\n \n // 저장된 다크모드 테마 적용\n const darkModeTheme = getStoredDarkModeTheme()\n applyDarkModeTheme(darkModeTheme)\n \n // 저장된 tweakcn 테마 적용\n const storedTheme = getStoredTweakcnTheme()\n applyTweakcnTheme(storedTheme)\n \n // HTML의 dark 클래스 변경을 감지하여 헤더 컴포넌트 상태를 동기화\n const root = document.documentElement\n \n // 현재 dark 클래스 상태로 초기화\n isDarkMode.value = root.classList.contains('dark')\n \n // MutationObserver로 동적 테마 클래스 감지\n themeObserver = new MutationObserver(() => {\n if (isInternalUpdate) return\n \n // 다크모드 클래스 변경 감지\n const hasDarkClass = root.classList.contains('dark')\n if (hasDarkClass !== isDarkMode.value) {\n isDarkMode.value = hasDarkClass\n localStorage.setItem('theme', hasDarkClass ? 'dark' : 'light')\n }\n \n // 테마 목록 업데이트\n updateThemeOptions()\n \n // 현재 적용된 tweakcn 테마 확인\n const currentThemeClass = Array.from(root.classList).find(cls => cls.startsWith('theme-'))\n if (currentThemeClass) {\n const themeName = currentThemeClass.replace('theme-', '')\n if (themeOptions.value.includes(themeName)) {\n currentTheme.value = themeName\n }\n }\n })\n \n // document.documentElement의 클래스 변경 감지\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n })\n \n // 스타일시트 변경 감지 (동적으로 추가된 테마 감지)\n styleObserver = new MutationObserver(() => {\n updateThemeOptions()\n })\n \n styleObserver.observe(document.head, {\n childList: true,\n subtree: true,\n })\n \n // 스토리북 환경에서만 다크모드 및 테마 변경 감지 (추가 보완)\n let intervalId: ReturnType<typeof setInterval> | null = null\n \n if (isStorybook()) {\n const checkStorybookGlobals = () => {\n try {\n const storybookGlobals = (window as any).__STORYBOOK_GLOBALS__\n if (storybookGlobals) {\n // 다크모드 동기화\n if (typeof storybookGlobals.darkMode !== 'undefined') {\n const storybookDarkMode = storybookGlobals.darkMode\n if (storybookDarkMode !== isDarkMode.value) {\n applyDarkModeTheme(storybookDarkMode ? 'dark' : 'light')\n }\n }\n \n // tweakcn 테마 동기화\n if (storybookGlobals.theme && storybookGlobals.theme !== currentTheme.value) {\n const storybookTheme = String(storybookGlobals.theme)\n if (themeOptions.value.includes(storybookTheme)) {\n applyTweakcnTheme(storybookTheme)\n }\n }\n }\n } catch (e) {\n // 무시\n }\n }\n \n // 초기 확인\n checkStorybookGlobals()\n \n // 주기적으로 확인 (스토리북이 직접 이벤트를 제공하지 않는 경우)\n intervalId = setInterval(checkStorybookGlobals, 500)\n }\n \n // 컴포넌트 언마운트 시 정리\n onUnmounted(() => {\n if (themeObserver) {\n themeObserver.disconnect()\n themeObserver = null\n }\n if (styleObserver) {\n styleObserver.disconnect()\n styleObserver = null\n }\n if (intervalId) {\n clearInterval(intervalId)\n }\n })\n})\n</script>\n\n<template>\n <header :class=\"cn('flex items-center justify-between w-full', preset.containerClass)\">\n <!-- 왼쪽: 햄버거 메뉴 + 로고 + 네비게이션 -->\n <div class=\"flex items-center gap-6 flex-1\">\n <!-- 햄버거 메뉴 버튼 (사이드바 토글) -->\n <JButton\n v-if=\"showSidebarToggle\"\n variant=\"ghost\"\n size=\"icon\"\n class=\"flex-shrink-0\"\n aria-label=\"사이드바 토글\"\n @click=\"handleSidebarToggle\"\n >\n <JIcon name=\"menu\" size=\"md\" />\n </JButton>\n\n <!-- 로고 -->\n <div\n v-if=\"displayLogoImage || logoText\"\n class=\"flex items-center cursor-pointer\"\n @click=\"handleLogoClick\"\n >\n <!-- 로고 이미지 (logo prop이 있고 에러가 2 미만일 때) -->\n <img\n v-if=\"displayLogoImage && logoImageError < 2\"\n :src=\"displayLogoImage\"\n alt=\"로고\"\n class=\"h-8 w-auto\"\n @error=\"handleLogoError\"\n />\n <!-- 로고 텍스트 (기본 또는 모든 이미지 실패 시) -->\n <span\n v-else-if=\"logoText\"\n class=\"text-lg font-bold text-foreground\"\n >\n {{ logoText }}\n </span>\n </div>\n\n <!-- 네비게이션 -->\n <nav\n v-if=\"navItems && navItems.length > 0\"\n class=\"flex items-center gap-1\"\n >\n <JButton\n v-for=\"(item, index) in navItems\"\n :key=\"index\"\n variant=\"ghost\"\n :class=\"cn(\n preset.navItemClass,\n item.active && preset.navItemActiveClass\n )\"\n @click=\"handleNavClick(item, index)\"\n >\n <JIcon\n v-if=\"item.icon\"\n :name=\"item.icon\"\n size=\"sm\"\n class=\"mr-1.5\"\n />\n {{ item.label }}\n </JButton>\n </nav>\n </div>\n\n <!-- 오른쪽: 테마 선택기 + 알림 + 다크모드 토글 + 사용자 메뉴 -->\n <div class=\"flex items-center gap-3\">\n <!-- tweakcn 테마 선택기 -->\n <JPopover\n v-if=\"showThemeSelector\"\n position=\"bottom\"\n align=\"end\"\n styletype=\"default-sm\"\n >\n <template #trigger>\n <JButton\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"테마 선택\"\n >\n <JIcon name=\"palette\" size=\"md\" />\n </JButton>\n </template>\n <div class=\"p-2 min-w-[200px]\">\n <div class=\"text-xs font-medium text-muted-foreground px-2 py-1.5 mb-1\">\n 테마 선택\n </div>\n <div class=\"space-y-1\">\n <button\n v-for=\"theme in themeOptions\"\n :key=\"theme\"\n :class=\"cn(\n 'w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded-md transition-colors',\n 'hover:bg-accent hover:text-accent-foreground',\n 'border-0 outline-none text-left',\n currentTheme === theme && 'bg-accent text-accent-foreground font-medium'\n )\"\n @click=\"handleThemeChange(theme)\"\n >\n <JIcon\n v-if=\"currentTheme === theme\"\n name=\"check\"\n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n <span v-else class=\"w-4\" />\n <span class=\"flex-1 capitalize\">{{ theme }}</span>\n </button>\n </div>\n </div>\n </JPopover>\n\n <!-- 알림 -->\n <JPopover\n v-if=\"showNotifications\"\n position=\"bottom\"\n align=\"end\"\n styletype=\"default-sm\"\n >\n <template #trigger>\n <JButton\n variant=\"ghost\"\n size=\"icon\"\n class=\"relative\"\n aria-label=\"알림\"\n >\n <JIcon name=\"circleAlert\" size=\"md\" />\n <span\n v-if=\"unreadCount > 0\"\n class=\"absolute top-0 right-0 h-4 w-4 rounded-full bg-destructive text-destructive-foreground text-xs flex items-center justify-center\"\n >\n {{ unreadCount > 9 ? '9+' : unreadCount }}\n </span>\n </JButton>\n </template>\n <div class=\"p-2\">\n <div\n v-if=\"notifications && notifications.length > 0\"\n class=\"max-h-96 overflow-y-auto space-y-1\"\n >\n <div\n v-for=\"notification in notifications\"\n :key=\"notification.id\"\n :class=\"cn(\n 'p-3 rounded-md cursor-pointer transition-colors',\n !notification.read ? 'bg-accent' : 'hover:bg-accent/50'\n )\"\n @click=\"handleNotificationClick(notification)\"\n >\n <div class=\"flex items-start gap-2\">\n <JIcon\n v-if=\"notification.icon\"\n :name=\"notification.icon\"\n size=\"sm\"\n class=\"mt-0.5 flex-shrink-0\"\n />\n <div class=\"flex-1 min-w-0\">\n <p class=\"text-sm font-medium text-foreground\">\n {{ notification.title }}\n </p>\n <p\n v-if=\"notification.message\"\n class=\"text-xs text-muted-foreground mt-1\"\n >\n {{ notification.message }}\n </p>\n <p\n v-if=\"notification.time\"\n class=\"text-xs text-muted-foreground/60 mt-1\"\n >\n {{ notification.time }}\n </p>\n </div>\n </div>\n </div>\n </div>\n <div\n v-else\n class=\"p-4 text-center text-sm text-muted-foreground\"\n >\n 알림이 없습니다.\n </div>\n </div>\n </JPopover>\n\n <!-- 다크모드 토글 버튼 -->\n <JButton\n variant=\"ghost\"\n size=\"icon\"\n :aria-label=\"isDarkMode ? '라이트 모드로 전환' : '다크 모드로 전환'\"\n @click=\"handleThemeToggle\"\n >\n <JIcon :name=\"themeIcon\" size=\"md\" />\n </JButton>\n\n <!-- 사용자 메뉴 (userName이 있으면 표시) -->\n <JPopover\n v-if=\"userName\"\n position=\"bottom\"\n align=\"end\"\n styletype=\"default-sm\"\n >\n <template #trigger>\n <div class=\"flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity\">\n <JAvatar\n :src=\"userAvatar\"\n :fallback=\"userName ? userName[0] : 'U'\"\n size=\"sm\"\n />\n <span\n class=\"text-sm text-foreground hidden sm:inline\"\n >\n {{ userName }}\n </span>\n <JIcon name=\"chevronDown\" size=\"sm\" class=\"text-muted-foreground hidden sm:inline\" />\n </div>\n </template>\n <div class=\"w-full rounded-md overflow-hidden\">\n <!-- 프로필 정보 영역 -->\n <div class=\"px-3 py-2 border-b border-border\">\n <p class=\"text-sm font-medium text-foreground\">{{ userName }}</p>\n <p\n v-if=\"userEmail\"\n class=\"text-xs text-muted-foreground mt-0.5\"\n >\n {{ userEmail }}\n </p>\n </div>\n \n <!-- 메뉴 아이템 -->\n <template v-for=\"(group, groupIndex) in userMenuGroups\" :key=\"groupIndex\">\n <template v-for=\"(item, itemIndex) in group.items\" :key=\"item.id\">\n <button\n v-if=\"!item.separator\"\n :disabled=\"item.disabled\"\n :class=\"cn(\n 'w-full flex items-center gap-2 px-3 py-1.5 text-sm transition-colors',\n item.id === 'logout' \n ? 'text-destructive hover:bg-destructive/10 hover:text-destructive' \n : 'hover:bg-accent hover:text-accent-foreground',\n 'disabled:opacity-50 disabled:pointer-events-none',\n 'border-0 outline-none',\n group.items[itemIndex + 1]?.separator && 'border-b-0'\n )\"\n @click=\"handleUserMenuSelect(item.id)\"\n >\n <JIcon\n v-if=\"item.icon\"\n :name=\"item.icon\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0',\n item.id === 'logout' && 'text-destructive'\n )\"\n />\n <span class=\"flex-1 text-left truncate\">{{ item.label }}</span>\n </button>\n <div\n v-else-if=\"item.separator && itemIndex > 0\"\n class=\"h-px bg-muted my-1\"\n />\n </template>\n <div\n v-if=\"groupIndex < userMenuGroups.length - 1\"\n class=\"h-px bg-muted my-1\"\n />\n </template>\n </div>\n </JPopover>\n\n <!-- 로그인 버튼 (userName이 없으면 표시) -->\n <JButton\n v-else\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"로그인\"\n @click=\"handleLogin\"\n >\n <JIcon name=\"logIn\" size=\"sm\" class=\"sm:mr-1.5\" />\n <span class=\"hidden sm:inline\">로그인</span>\n </JButton>\n\n <!-- 커스텀 슬롯 -->\n <slot name=\"actions\" />\n </div>\n </header>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","logoImageError","ref","logoImageSrc","displayLogoImage","logoFallback","handleLogoError","unreadCount","n","fixedUserMenuItems","userMenuGroups","handleLogoClick","handleNavClick","item","index","handleNotificationClick","handleUserMenuSelect","itemId","handleLogin","handleSidebarToggle","isDarkMode","currentTheme","getDefaultTheme","themeOptions","isInternalUpdate","themeObserver","styleObserver","isStorybook","updateThemeOptions","detectedThemes","detectThemeClasses","ensuredThemes","ensureDefaultTheme","filtered","theme","getStoredDarkModeTheme","stored","applyDarkModeTheme","root","handleThemeToggle","newTheme","getStoredTweakcnTheme","validateThemeExists","getStoredTheme","applyTweakcnTheme","validatedTheme","success","applyTheme","setStoredTheme","storybookGlobals","defaultTheme","handleThemeChange","themeIcon","onMounted","darkModeTheme","storedTheme","hasDarkClass","currentThemeClass","cls","themeName","intervalId","checkStorybookGlobals","storybookDarkMode","storybookTheme","onUnmounted","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_createBlock","JButton","_createVNode","JIcon","_hoisted_3","_toDisplayString","_openBlock","_hoisted_4","_Fragment","_renderList","$event","_hoisted_5","JPopover","_hoisted_6","_cache","_hoisted_7","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","notification","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","JAvatar","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","group","groupIndex","itemIndex","_hoisted_29","_hoisted_28","_hoisted_30","_renderSlot","_ctx"],"mappings":"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"}
|