@j-solution/components 1.9.4 → 1.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/assets/jwms-portal-frontend-CJtAlYis.css +1 -0
- package/assets/styles/j-components.css +1 -1
- package/assets/styles/main.css +12 -3
- package/components/atoms/JCombo.vue.cjs +1 -1
- package/components/atoms/JCombo.vue.cjs.map +1 -1
- package/components/atoms/JCombo.vue.js +1 -1
- package/components/atoms/JCombo.vue.js.map +1 -1
- package/components/atoms/JDatepicker.vue.cjs +1 -1
- package/components/atoms/JDatepicker.vue.cjs.map +1 -1
- package/components/atoms/JDatepicker.vue.js +1 -1
- package/components/atoms/JDatepicker.vue.js.map +1 -1
- package/components/atoms/JGrid.vue.cjs +1 -1
- package/components/atoms/JGrid.vue.js +2 -2
- package/components/atoms/JGrid.vue2.cjs.map +1 -1
- package/components/atoms/JGrid.vue2.js.map +1 -1
- package/components/atoms/JInput.vue.cjs +1 -1
- package/components/atoms/JInput.vue.cjs.map +1 -1
- package/components/atoms/JInput.vue.js +1 -1
- package/components/atoms/JInput.vue.js.map +1 -1
- package/components/atoms/JSearchCombo.vue.cjs +1 -1
- package/components/atoms/JSearchCombo.vue.cjs.map +1 -1
- package/components/atoms/JSearchCombo.vue.js +5 -5
- package/components/atoms/JSearchCombo.vue.js.map +1 -1
- package/components/atoms/JSplitter.vue.cjs +1 -1
- package/components/atoms/JSplitter.vue.js +2 -2
- package/components/atoms/JSplitter.vue2.cjs +1 -1
- package/components/atoms/JSplitter.vue2.cjs.map +1 -1
- package/components/atoms/JSplitter.vue2.js +14 -10
- package/components/atoms/JSplitter.vue2.js.map +1 -1
- package/components/molecules/JTabs.vue.cjs +1 -1
- package/components/molecules/JTabs.vue.js +2 -2
- package/components/molecules/JTabs.vue2.cjs +1 -1
- package/components/molecules/JTabs.vue2.cjs.map +1 -1
- package/components/molecules/JTabs.vue2.js +4 -4
- package/components/molecules/JTabs.vue2.js.map +1 -1
- package/components/organisms/JDynamicForm.vue.cjs +1 -1
- package/components/organisms/JDynamicForm.vue.js +1 -1
- package/components/organisms/JDynamicForm.vue2.cjs +1 -1
- package/components/organisms/JDynamicForm.vue2.cjs.map +1 -1
- package/components/organisms/JDynamicForm.vue2.js +35 -35
- package/components/organisms/JDynamicForm.vue2.js.map +1 -1
- package/components/organisms/JFilterBar.vue.cjs +1 -1
- package/components/organisms/JFilterBar.vue.js +2 -2
- package/components/organisms/JFilterBar.vue2.cjs +1 -1
- package/components/organisms/JFilterBar.vue2.cjs.map +1 -1
- package/components/organisms/JFilterBar.vue2.js +2 -2
- package/components/organisms/JFilterBar.vue2.js.map +1 -1
- package/components/organisms/JPageContainer.vue.cjs +1 -1
- package/components/organisms/JPageContainer.vue.cjs.map +1 -1
- package/components/organisms/JPageContainer.vue.js +4 -4
- package/components/organisms/JPageContainer.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 +1 -1
- package/components/organisms/JSearchPanel.vue2.cjs.map +1 -1
- package/components/organisms/JSearchPanel.vue2.js +3 -3
- package/components/organisms/JSearchPanel.vue2.js.map +1 -1
- package/components/shadcn/Input.vue.cjs +1 -1
- package/components/shadcn/Input.vue.cjs.map +1 -1
- package/components/shadcn/Input.vue.js +1 -1
- package/components/shadcn/Input.vue.js.map +1 -1
- package/components/shadcn/SelectTrigger.vue.cjs +1 -1
- package/components/shadcn/SelectTrigger.vue.cjs.map +1 -1
- package/components/shadcn/SelectTrigger.vue.js +1 -1
- package/components/shadcn/SelectTrigger.vue.js.map +1 -1
- package/components/shadcn/resizable/ResizableHandle.vue.cjs +1 -1
- package/components/shadcn/resizable/ResizableHandle.vue.cjs.map +1 -1
- package/components/shadcn/resizable/ResizableHandle.vue.js +3 -3
- package/components/shadcn/resizable/ResizableHandle.vue.js.map +1 -1
- package/package.json +1 -1
- package/assets/jwms-portal-frontend-Bd12BmyZ.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JInput.vue.js","sources":["../../../../src/components/atoms/JInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { Input } from '@/components/shadcn'\r\n\r\ntype StyleType =\r\n | 'default' // 기본 스타일\r\n | 'error' // 에러 상태 (빨강 테두리)\r\n | 'success' // 성공 상태 (초록 테두리)\r\n | 'warning' // 경고 상태 (주황 테두리)\r\n | 'sm' // 작은 크기\r\n | 'lg' // 큰 크기\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n modelValue?: string | number\r\n type?: string\r\n placeholder?: string\r\n disabled?: boolean\r\n readonly?: boolean\r\n required?: boolean\r\n name?: string\r\n id?: string\r\n class?: string\r\n /** 스타일 프리셋 */\r\n styletype?: StyleType\r\n }>(),\r\n {\r\n type: 'text',\r\n placeholder: '',\r\n disabled: false,\r\n readonly: false,\r\n required: false,\r\n styletype: 'default',\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:modelValue': [value: string | number]\r\n 'change': [value: string | number]\r\n 'focus': [event: FocusEvent]\r\n 'blur': [event: FocusEvent]\r\n}>()\r\n\r\n/**\r\n * styletype -> class 매핑\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, { class: string }> = {\r\n default: { class: '' },\r\n error: { \r\n class: 'border-destructive focus-visible:ring-destructive',\r\n },\r\n success: { \r\n class: 'border-green-500 focus-visible:ring-green-500',\r\n },\r\n warning: { \r\n class: 'border-amber-500 focus-visible:ring-amber-500',\r\n },\r\n sm: { \
|
|
1
|
+
{"version":3,"file":"JInput.vue.js","sources":["../../../../src/components/atoms/JInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { Input } from '@/components/shadcn'\r\n\r\ntype StyleType =\r\n | 'default' // 기본 스타일\r\n | 'error' // 에러 상태 (빨강 테두리)\r\n | 'success' // 성공 상태 (초록 테두리)\r\n | 'warning' // 경고 상태 (주황 테두리)\r\n | 'sm' // 작은 크기\r\n | 'lg' // 큰 크기\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n modelValue?: string | number\r\n type?: string\r\n placeholder?: string\r\n disabled?: boolean\r\n readonly?: boolean\r\n required?: boolean\r\n name?: string\r\n id?: string\r\n class?: string\r\n /** 스타일 프리셋 */\r\n styletype?: StyleType\r\n }>(),\r\n {\r\n type: 'text',\r\n placeholder: '',\r\n disabled: false,\r\n readonly: false,\r\n required: false,\r\n styletype: 'default',\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:modelValue': [value: string | number]\r\n 'change': [value: string | number]\r\n 'focus': [event: FocusEvent]\r\n 'blur': [event: FocusEvent]\r\n}>()\r\n\r\n/**\r\n * styletype -> class 매핑\r\n */\r\nconst STYLE_PRESETS: Record<StyleType, { class: string }> = {\r\n default: { class: '' },\r\n error: { \r\n class: 'border-destructive focus-visible:ring-destructive',\r\n },\r\n success: { \r\n class: 'border-green-500 focus-visible:ring-green-500',\r\n },\r\n warning: { \r\n class: 'border-amber-500 focus-visible:ring-amber-500',\r\n },\r\n sm: { \n class: 'h-7 text-xs px-2',\n },\n lg: { \r\n class: 'h-12 text-base px-4',\r\n },\r\n}\r\n\r\n/** 최종 바인딩: 직접 넘긴 class가 있으면 styletype 기본값과 병합 */\r\nconst mapped = computed(() => {\r\n const styleKey = props.styletype || 'default'\r\n const preset = STYLE_PRESETS[styleKey] ?? STYLE_PRESETS.default\r\n const finalClass = [preset?.class, props.class].filter(Boolean).join(' ')\r\n \r\n return {\r\n type: props.type,\r\n placeholder: props.placeholder,\r\n disabled: props.disabled,\r\n readonly: props.readonly,\r\n required: props.required,\r\n name: props.name,\r\n id: props.id,\r\n class: finalClass,\r\n }\r\n})\r\n\r\nconst handleInput = (value: string | number) => {\r\n emit('update:modelValue', value)\r\n emit('change', value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <Input \r\n v-bind=\"mapped\" \r\n :model-value=\"modelValue\" \r\n @update:model-value=\"handleInput\"\r\n @focus=\"emit('focus', $event)\"\r\n @blur=\"emit('blur', $event)\"\r\n >\r\n <slot />\r\n </Input>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","mapped","computed","styleKey","finalClass","handleInput","value","_createBlock","_unref","Input","_mergeProps","_cache","$event","_renderSlot","_ctx"],"mappings":";;;;;;;;;;;;;;;;;;;AAYA,UAAMA,IAAQC,GAwBRC,IAAOC,GAUPC,IAAsD;AAAA,MAC1D,SAAS,EAAE,OAAO,GAAA;AAAA,MAClB,OAAO;AAAA,QACL,OAAO;AAAA,MAAA;AAAA,MAET,SAAS;AAAA,QACP,OAAO;AAAA,MAAA;AAAA,MAET,SAAS;AAAA,QACP,OAAO;AAAA,MAAA;AAAA,MAET,IAAI;AAAA,QACF,OAAO;AAAA,MAAA;AAAA,MAET,IAAI;AAAA,QACF,OAAO;AAAA,MAAA;AAAA,IACT,GAIIC,IAASC,EAAS,MAAM;AAC5B,YAAMC,IAAWP,EAAM,aAAa,WAE9BQ,IAAa,EADJJ,EAAcG,CAAQ,KAAKH,EAAc,UAC5B,OAAOJ,EAAM,KAAK,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAExE,aAAO;AAAA,QACL,MAAMA,EAAM;AAAA,QACZ,aAAaA,EAAM;AAAA,QACnB,UAAUA,EAAM;AAAA,QAChB,UAAUA,EAAM;AAAA,QAChB,UAAUA,EAAM;AAAA,QAChB,MAAMA,EAAM;AAAA,QACZ,IAAIA,EAAM;AAAA,QACV,OAAOQ;AAAA,MAAA;AAAA,IAEX,CAAC,GAEKC,IAAc,CAACC,MAA2B;AAC9C,MAAAR,EAAK,qBAAqBQ,CAAK,GAC/BR,EAAK,UAAUQ,CAAK;AAAA,IACtB;2BAIEC,EAQQC,EAAAC,CAAA,GARRC,EAQQT,EAAA,OAPQ;AAAA,MACb,eAAaJ,EAAA;AAAA,MACb,uBAAoBQ;AAAA,MACpB,SAAKM,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAC,MAAEd,EAAI,SAAUc,CAAM;AAAA,MAC3B,QAAID,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAC,MAAEd,EAAI,QAASc,CAAM;AAAA,IAAA;iBAE1B,MAAQ;AAAA,QAARC,EAAQC,EAAA,QAAA,SAAA;AAAA,MAAA;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),n=require("lucide-vue-next"),o=require("../../lib/utils.cjs");require("../shadcn/index.cjs");const b=require("../shadcn/Combobox.vue.cjs"),x=require("../shadcn/ComboboxAnchor.vue.cjs"),_=require("../shadcn/ComboboxTrigger.vue.cjs"),v=require("../shadcn/Button.vue.cjs"),C=require("../shadcn/ComboboxList.vue.cjs"),h=require("../shadcn/ComboboxInput.vue.cjs"),y=require("../shadcn/ComboboxEmpty.vue.cjs"),g=require("../shadcn/ComboboxGroup.vue.cjs"),V=require("../shadcn/ComboboxItem.vue.cjs"),q={class:"relative w-full max-w-sm items-center"},N={class:"absolute start-0 inset-y-0 flex items-center justify-center px-2.5"},w=e.defineComponent({__name:"JSearchCombo",props:{modelValue:{},options:{default:()=>[]},placeholder:{default:"선택해주세요."},searchPlaceholder:{default:""},emptyText:{default:"검색 결과가 없습니다."},disabled:{type:Boolean,default:!1},required:{type:Boolean,default:!1},name:{},id:{},multiple:{type:Boolean,default:!1},class:{},styletype:{default:"default"}},emits:["update:modelValue","change","focus","blur"],setup(t,{emit:c}){const d=t,r=c,i={default:{variant:"outline",buttonClass:"h-
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),n=require("lucide-vue-next"),o=require("../../lib/utils.cjs");require("../shadcn/index.cjs");const b=require("../shadcn/Combobox.vue.cjs"),x=require("../shadcn/ComboboxAnchor.vue.cjs"),_=require("../shadcn/ComboboxTrigger.vue.cjs"),v=require("../shadcn/Button.vue.cjs"),C=require("../shadcn/ComboboxList.vue.cjs"),h=require("../shadcn/ComboboxInput.vue.cjs"),y=require("../shadcn/ComboboxEmpty.vue.cjs"),g=require("../shadcn/ComboboxGroup.vue.cjs"),V=require("../shadcn/ComboboxItem.vue.cjs"),q={class:"relative w-full max-w-sm items-center"},N={class:"absolute start-0 inset-y-0 flex items-center justify-center px-2.5"},w=e.defineComponent({__name:"JSearchCombo",props:{modelValue:{},options:{default:()=>[]},placeholder:{default:"선택해주세요."},searchPlaceholder:{default:""},emptyText:{default:"검색 결과가 없습니다."},disabled:{type:Boolean,default:!1},required:{type:Boolean,default:!1},name:{},id:{},multiple:{type:Boolean,default:!1},class:{},styletype:{default:"default"}},emits:["update:modelValue","change","focus","blur"],setup(t,{emit:c}){const d=t,r=c,i={default:{variant:"outline",buttonClass:"h-7 text-xs px-2.5"},error:{variant:"outline",buttonClass:"h-7 text-xs px-2.5 border-destructive text-destructive",inputClass:"border-destructive focus:ring-destructive"},success:{variant:"outline",buttonClass:"h-7 text-xs px-2.5 border-green-500 text-green-700",inputClass:"border-green-500 focus:ring-green-500"},warning:{variant:"outline",buttonClass:"h-7 text-xs px-2.5 border-amber-500 text-amber-700",inputClass:"border-amber-500 focus:ring-amber-500"},sm:{variant:"outline",buttonClass:"h-7 text-[11px] px-2",inputClass:"h-7 text-[11px] px-2 py-1"},lg:{variant:"outline",buttonClass:"h-10 text-sm px-3",inputClass:"h-10 text-sm px-3 py-2"}},s=e.computed(()=>{const a=d.styletype||"default";return i[a]??i.default}),p=e.computed(()=>o.cn("justify-between",s.value?.buttonClass,d.class)),f=e.computed(()=>o.cn("pl-8 pr-2 py-1 focus-visible:ring-0 border-0 border-b rounded-none h-7 text-xs",s.value?.inputClass)),m=a=>{r("update:modelValue",a),r("change",a)};return(a,l)=>(e.openBlock(),e.createBlock(e.unref(b.default),{"model-value":t.modelValue,by:"label",disabled:t.disabled,required:t.required,name:t.name,"onUpdate:modelValue":l[2]||(l[2]=u=>m(u))},{default:e.withCtx(()=>[e.createVNode(e.unref(x.default),{"as-child":""},{default:e.withCtx(()=>[e.createVNode(e.unref(_.default),{"as-child":""},{default:e.withCtx(()=>[e.createVNode(e.unref(v.default),{id:t.id,variant:s.value.variant,class:e.normalizeClass(p.value),disabled:t.disabled,onFocus:l[0]||(l[0]=u=>r("focus",u)),onBlur:l[1]||(l[1]=u=>r("blur",u))},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(t.modelValue?.label??t.placeholder)+" ",1),e.createVNode(e.unref(n.ChevronsUpDown),{class:"ml-2 h-3.5 w-3.5 shrink-0 opacity-50"})]),_:1},8,["id","variant","class","disabled"])]),_:1})]),_:1}),e.createVNode(e.unref(C.default),null,{default:e.withCtx(()=>[e.createElementVNode("div",q,[e.createVNode(e.unref(h.default),{class:e.normalizeClass(f.value),placeholder:t.searchPlaceholder},null,8,["class","placeholder"]),e.createElementVNode("span",N,[e.createVNode(e.unref(n.Search),{class:"size-3.5 text-muted-foreground"})])]),e.createVNode(e.unref(y.default),null,{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(t.emptyText),1)]),_:1}),e.createVNode(e.unref(g.default),null,{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.options,u=>(e.openBlock(),e.createBlock(e.unref(V.default),{key:u.value,value:u},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(u.label)+" ",1),t.modelValue?.value===u.value?(e.openBlock(),e.createBlock(e.unref(n.Check),{key:0,class:e.normalizeClass(e.unref(o.cn)("ml-auto h-3.5 w-3.5"))},null,8,["class"])):e.createCommentVNode("",!0)]),_:2},1032,["value"]))),128))]),_:1})]),_:1})]),_:1},8,["model-value","disabled","required","name"]))}});exports.default=w;
|
|
2
2
|
//# sourceMappingURL=JSearchCombo.vue.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSearchCombo.vue.cjs","sources":["../../../../src/components/atoms/JSearchCombo.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Check, ChevronsUpDown, Search } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport {\n Button,\n Combobox,\n ComboboxAnchor,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxTrigger,\n} from '@/components/shadcn'\n\nexport interface ComboboxOption {\n value: string | number\n label: string\n}\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'error' // 에러 상태\n | 'success' // 성공 상태\n | 'warning' // 경고 상태\n | 'sm' // 작은 크기\n | 'lg' // 큰 크기\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: ComboboxOption\n options?: ComboboxOption[]\n placeholder?: string\n searchPlaceholder?: string\n emptyText?: string\n disabled?: boolean\n required?: boolean\n name?: string\n id?: string\n multiple?: boolean\n class?: string\n /** 스타일 프리셋 */\n styletype?: StyleType\n }>(),\n {\n options: () => [],\n placeholder: '선택해주세요.',\n searchPlaceholder: '',\n emptyText: '검색 결과가 없습니다.',\n disabled: false,\n required: false,\n multiple: false,\n styletype: 'default',\n },\n)\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ComboboxOption | undefined]\n 'change': [value: ComboboxOption | undefined]\n 'focus': [event: FocusEvent]\n 'blur': [event: FocusEvent]\n}>()\n\n/**\n * styletype -> variant/class 매핑\n */\nconst STYLE_PRESETS: Record<StyleType, { variant: 'default' | 'outline' | 'destructive' | 'secondary' | 'ghost' | 'link', buttonClass?: string, inputClass?: string }> = {\n default: { \n variant: 'outline',\n buttonClass: 'h-
|
|
1
|
+
{"version":3,"file":"JSearchCombo.vue.cjs","sources":["../../../../src/components/atoms/JSearchCombo.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Check, ChevronsUpDown, Search } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport {\n Button,\n Combobox,\n ComboboxAnchor,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxTrigger,\n} from '@/components/shadcn'\n\nexport interface ComboboxOption {\n value: string | number\n label: string\n}\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'error' // 에러 상태\n | 'success' // 성공 상태\n | 'warning' // 경고 상태\n | 'sm' // 작은 크기\n | 'lg' // 큰 크기\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: ComboboxOption\n options?: ComboboxOption[]\n placeholder?: string\n searchPlaceholder?: string\n emptyText?: string\n disabled?: boolean\n required?: boolean\n name?: string\n id?: string\n multiple?: boolean\n class?: string\n /** 스타일 프리셋 */\n styletype?: StyleType\n }>(),\n {\n options: () => [],\n placeholder: '선택해주세요.',\n searchPlaceholder: '',\n emptyText: '검색 결과가 없습니다.',\n disabled: false,\n required: false,\n multiple: false,\n styletype: 'default',\n },\n)\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ComboboxOption | undefined]\n 'change': [value: ComboboxOption | undefined]\n 'focus': [event: FocusEvent]\n 'blur': [event: FocusEvent]\n}>()\n\n/**\n * styletype -> variant/class 매핑\n */\nconst STYLE_PRESETS: Record<StyleType, { variant: 'default' | 'outline' | 'destructive' | 'secondary' | 'ghost' | 'link', buttonClass?: string, inputClass?: string }> = {\n default: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5',\n },\n error: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5 border-destructive text-destructive',\n inputClass: 'border-destructive focus:ring-destructive',\n },\n success: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5 border-green-500 text-green-700',\n inputClass: 'border-green-500 focus:ring-green-500',\n },\n warning: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5 border-amber-500 text-amber-700',\n inputClass: 'border-amber-500 focus:ring-amber-500',\n },\n sm: { \n variant: 'outline',\n buttonClass: 'h-7 text-[11px] px-2',\n inputClass: 'h-7 text-[11px] px-2 py-1',\n },\n lg: { \n variant: 'outline',\n buttonClass: 'h-10 text-sm px-3',\n inputClass: 'h-10 text-sm px-3 py-2',\n },\n}\n\nconst preset = computed(() => {\n const styleKey = props.styletype || 'default'\n return STYLE_PRESETS[styleKey] ?? STYLE_PRESETS.default\n})\n\nconst buttonClass = computed(() => {\n return cn('justify-between', preset.value?.buttonClass, props.class)\n})\n\nconst inputClass = computed(() => {\n return cn('pl-8 pr-2 py-1 focus-visible:ring-0 border-0 border-b rounded-none h-7 text-xs', preset.value?.inputClass)\n})\n\nconst handleChange = (value: ComboboxOption | undefined) => {\n emit('update:modelValue', value)\n emit('change', value)\n}\n</script>\n\n<template>\n <Combobox \n :model-value=\"modelValue\" \n by=\"label\" \n :disabled=\"disabled\"\n :required=\"required\"\n :name=\"name\"\n @update:model-value=\"(value) => handleChange(value as ComboboxOption | undefined)\"\n >\n <ComboboxAnchor as-child>\n <ComboboxTrigger as-child>\n <Button \n :id=\"id\"\n :variant=\"preset.variant\" \n :class=\"buttonClass\"\n :disabled=\"disabled\"\n @focus=\"emit('focus', $event as FocusEvent)\"\n @blur=\"emit('blur', $event as FocusEvent)\"\n >\n {{ modelValue?.label ?? placeholder }}\n <ChevronsUpDown class=\"ml-2 h-3.5 w-3.5 shrink-0 opacity-50\" />\n </Button>\n </ComboboxTrigger>\n </ComboboxAnchor>\n\n <ComboboxList>\n <div class=\"relative w-full max-w-sm items-center\">\n <ComboboxInput :class=\"inputClass\" :placeholder=\"searchPlaceholder\" />\n <span class=\"absolute start-0 inset-y-0 flex items-center justify-center px-2.5\">\n <Search class=\"size-3.5 text-muted-foreground\" />\n </span>\n </div>\n\n <ComboboxEmpty>\n {{ emptyText }}\n </ComboboxEmpty>\n\n <ComboboxGroup>\n <ComboboxItem\n v-for=\"option in options\"\n :key=\"option.value\"\n :value=\"option\"\n >\n {{ option.label }}\n\n <Check v-if=\"modelValue?.value === option.value\" :class=\"cn('ml-auto h-3.5 w-3.5')\" />\n </ComboboxItem>\n </ComboboxGroup>\n </ComboboxList>\n </Combobox>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","styleKey","buttonClass","cn","inputClass","handleChange","value","_createBlock","_unref","Combobox","_cache","_createVNode","ComboboxAnchor","ComboboxTrigger","Button","$event","_createTextVNode","_toDisplayString","ChevronsUpDown","ComboboxList","_createElementVNode","_hoisted_1","ComboboxInput","_hoisted_2","Search","ComboboxEmpty","ComboboxGroup","_createElementBlock","_Fragment","_renderList","option","ComboboxItem","Check"],"mappings":"ipCA6BA,MAAMA,EAAQC,EA4BRC,EAAOC,EAUPC,EAAmK,CACvK,QAAS,CACP,QAAS,UACT,YAAa,oBAAA,EAEf,MAAO,CACL,QAAS,UACT,YAAa,yDACb,WAAY,2CAAA,EAEd,QAAS,CACP,QAAS,UACT,YAAa,qDACb,WAAY,uCAAA,EAEd,QAAS,CACP,QAAS,UACT,YAAa,qDACb,WAAY,uCAAA,EAEd,GAAI,CACF,QAAS,UACT,YAAa,uBACb,WAAY,2BAAA,EAEd,GAAI,CACF,QAAS,UACT,YAAa,oBACb,WAAY,wBAAA,CACd,EAGIC,EAASC,EAAAA,SAAS,IAAM,CAC5B,MAAMC,EAAWP,EAAM,WAAa,UACpC,OAAOI,EAAcG,CAAQ,GAAKH,EAAc,OAClD,CAAC,EAEKI,EAAcF,EAAAA,SAAS,IACpBG,EAAAA,GAAG,kBAAmBJ,EAAO,OAAO,YAAaL,EAAM,KAAK,CACpE,EAEKU,EAAaJ,EAAAA,SAAS,IACnBG,EAAAA,GAAG,iFAAkFJ,EAAO,OAAO,UAAU,CACrH,EAEKM,EAAgBC,GAAsC,CAC1DV,EAAK,oBAAqBU,CAAK,EAC/BV,EAAK,SAAUU,CAAK,CACtB,8BAIEC,EAAAA,YAgDWC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CA/CR,cAAad,EAAA,WACd,GAAG,QACF,SAAUA,EAAA,SACV,SAAUA,EAAA,SACV,KAAMA,EAAA,KACN,sBAAkBe,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAGJ,GAAUD,EAAaC,CAAK,EAAA,qBAElD,IAciB,CAdjBK,EAAAA,YAciBH,EAAAA,MAAAI,EAAAA,OAAA,EAAA,CAdD,WAAA,IAAQ,mBACtB,IAYkB,CAZlBD,EAAAA,YAYkBH,EAAAA,MAAAK,EAAAA,OAAA,EAAA,CAZD,WAAA,IAAQ,mBACvB,IAUS,CAVTF,cAUSH,EAAAA,MAAAM,EAAAA,OAAA,EAAA,CATN,GAAInB,EAAA,GACJ,QAASI,EAAA,MAAO,QAChB,uBAAOG,EAAA,KAAW,EAClB,SAAUP,EAAA,SACV,QAAKe,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAK,GAAEnB,EAAI,QAAUmB,CAAM,GAC3B,OAAIL,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAK,GAAEnB,EAAI,OAASmB,CAAM,EAAA,qBAE1B,IAAsC,CAAnCC,kBAAAC,EAAAA,gBAAAtB,EAAA,YAAY,OAASA,EAAA,WAAW,EAAG,IACtC,CAAA,EAAAgB,EAAAA,YAA+DH,EAAAA,MAAAU,EAAAA,cAAA,EAAA,CAA/C,MAAM,uCAAsC,CAAA,+DAKlEP,EAAAA,YAuBeH,EAAAA,MAAAW,SAAA,EAAA,KAAA,mBAtBb,IAKM,CALNC,EAAAA,mBAKM,MALNC,EAKM,CAJJV,cAAsEH,EAAAA,MAAAc,EAAAA,OAAA,EAAA,CAAtD,uBAAOlB,EAAA,KAAU,EAAG,YAAaT,EAAA,iBAAA,kCACjDyB,EAAAA,mBAEO,OAFPG,EAEO,CADLZ,EAAAA,YAAiDH,EAAAA,MAAAgB,EAAAA,MAAA,EAAA,CAAzC,MAAM,iCAAgC,CAAA,KAIlDb,EAAAA,YAEgBH,EAAAA,MAAAiB,SAAA,EAAA,KAAA,mBADd,IAAe,qCAAZ9B,EAAA,SAAS,EAAA,CAAA,CAAA,SAGdgB,EAAAA,YAUgBH,EAAAA,MAAAkB,SAAA,EAAA,KAAA,mBARZ,IAAyB,kBAD3BC,EAAAA,mBAQeC,EAAAA,SAAA,KAAAC,EAAAA,WAPIlC,EAAA,QAAVmC,kBADTvB,EAAAA,YAQeC,EAAAA,MAAAuB,EAAAA,OAAA,EAAA,CANZ,IAAKD,EAAO,MACZ,MAAOA,CAAA,qBAER,IAAkB,qCAAfA,EAAO,KAAK,EAAG,IAElB,CAAA,EAAanC,EAAA,YAAY,QAAUmC,EAAO,qBAA1CvB,EAAAA,YAAsFC,QAAAwB,EAAAA,KAAA,EAAA,OAApC,uBAAOxB,EAAAA,MAAAL,EAAAA,EAAA,EAAE,qBAAA,CAAA,CAAA"}
|
|
@@ -32,21 +32,21 @@ const J = { class: "relative w-full max-w-sm items-center" }, K = { class: "abso
|
|
|
32
32
|
const x = e, u = C, h = {
|
|
33
33
|
default: {
|
|
34
34
|
variant: "outline",
|
|
35
|
-
buttonClass: "h-
|
|
35
|
+
buttonClass: "h-7 text-xs px-2.5"
|
|
36
36
|
},
|
|
37
37
|
error: {
|
|
38
38
|
variant: "outline",
|
|
39
|
-
buttonClass: "h-
|
|
39
|
+
buttonClass: "h-7 text-xs px-2.5 border-destructive text-destructive",
|
|
40
40
|
inputClass: "border-destructive focus:ring-destructive"
|
|
41
41
|
},
|
|
42
42
|
success: {
|
|
43
43
|
variant: "outline",
|
|
44
|
-
buttonClass: "h-
|
|
44
|
+
buttonClass: "h-7 text-xs px-2.5 border-green-500 text-green-700",
|
|
45
45
|
inputClass: "border-green-500 focus:ring-green-500"
|
|
46
46
|
},
|
|
47
47
|
warning: {
|
|
48
48
|
variant: "outline",
|
|
49
|
-
buttonClass: "h-
|
|
49
|
+
buttonClass: "h-7 text-xs px-2.5 border-amber-500 text-amber-700",
|
|
50
50
|
inputClass: "border-amber-500 focus:ring-amber-500"
|
|
51
51
|
},
|
|
52
52
|
sm: {
|
|
@@ -62,7 +62,7 @@ const J = { class: "relative w-full max-w-sm items-center" }, K = { class: "abso
|
|
|
62
62
|
}, i = d(() => {
|
|
63
63
|
const n = x.styletype || "default";
|
|
64
64
|
return h[n] ?? h.default;
|
|
65
|
-
}), y = d(() => b("justify-between", i.value?.buttonClass, x.class)), g = d(() => b("pl-8 pr-2 py-1
|
|
65
|
+
}), y = d(() => b("justify-between", i.value?.buttonClass, x.class)), g = d(() => b("pl-8 pr-2 py-1 focus-visible:ring-0 border-0 border-b rounded-none h-7 text-xs", i.value?.inputClass)), V = (n) => {
|
|
66
66
|
u("update:modelValue", n), u("change", n);
|
|
67
67
|
};
|
|
68
68
|
return (n, r) => (o(), m(t(q), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSearchCombo.vue.js","sources":["../../../../src/components/atoms/JSearchCombo.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Check, ChevronsUpDown, Search } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport {\n Button,\n Combobox,\n ComboboxAnchor,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxTrigger,\n} from '@/components/shadcn'\n\nexport interface ComboboxOption {\n value: string | number\n label: string\n}\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'error' // 에러 상태\n | 'success' // 성공 상태\n | 'warning' // 경고 상태\n | 'sm' // 작은 크기\n | 'lg' // 큰 크기\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: ComboboxOption\n options?: ComboboxOption[]\n placeholder?: string\n searchPlaceholder?: string\n emptyText?: string\n disabled?: boolean\n required?: boolean\n name?: string\n id?: string\n multiple?: boolean\n class?: string\n /** 스타일 프리셋 */\n styletype?: StyleType\n }>(),\n {\n options: () => [],\n placeholder: '선택해주세요.',\n searchPlaceholder: '',\n emptyText: '검색 결과가 없습니다.',\n disabled: false,\n required: false,\n multiple: false,\n styletype: 'default',\n },\n)\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ComboboxOption | undefined]\n 'change': [value: ComboboxOption | undefined]\n 'focus': [event: FocusEvent]\n 'blur': [event: FocusEvent]\n}>()\n\n/**\n * styletype -> variant/class 매핑\n */\nconst STYLE_PRESETS: Record<StyleType, { variant: 'default' | 'outline' | 'destructive' | 'secondary' | 'ghost' | 'link', buttonClass?: string, inputClass?: string }> = {\n default: { \n variant: 'outline',\n buttonClass: 'h-
|
|
1
|
+
{"version":3,"file":"JSearchCombo.vue.js","sources":["../../../../src/components/atoms/JSearchCombo.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Check, ChevronsUpDown, Search } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport {\n Button,\n Combobox,\n ComboboxAnchor,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxTrigger,\n} from '@/components/shadcn'\n\nexport interface ComboboxOption {\n value: string | number\n label: string\n}\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'error' // 에러 상태\n | 'success' // 성공 상태\n | 'warning' // 경고 상태\n | 'sm' // 작은 크기\n | 'lg' // 큰 크기\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: ComboboxOption\n options?: ComboboxOption[]\n placeholder?: string\n searchPlaceholder?: string\n emptyText?: string\n disabled?: boolean\n required?: boolean\n name?: string\n id?: string\n multiple?: boolean\n class?: string\n /** 스타일 프리셋 */\n styletype?: StyleType\n }>(),\n {\n options: () => [],\n placeholder: '선택해주세요.',\n searchPlaceholder: '',\n emptyText: '검색 결과가 없습니다.',\n disabled: false,\n required: false,\n multiple: false,\n styletype: 'default',\n },\n)\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ComboboxOption | undefined]\n 'change': [value: ComboboxOption | undefined]\n 'focus': [event: FocusEvent]\n 'blur': [event: FocusEvent]\n}>()\n\n/**\n * styletype -> variant/class 매핑\n */\nconst STYLE_PRESETS: Record<StyleType, { variant: 'default' | 'outline' | 'destructive' | 'secondary' | 'ghost' | 'link', buttonClass?: string, inputClass?: string }> = {\n default: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5',\n },\n error: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5 border-destructive text-destructive',\n inputClass: 'border-destructive focus:ring-destructive',\n },\n success: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5 border-green-500 text-green-700',\n inputClass: 'border-green-500 focus:ring-green-500',\n },\n warning: { \n variant: 'outline',\n buttonClass: 'h-7 text-xs px-2.5 border-amber-500 text-amber-700',\n inputClass: 'border-amber-500 focus:ring-amber-500',\n },\n sm: { \n variant: 'outline',\n buttonClass: 'h-7 text-[11px] px-2',\n inputClass: 'h-7 text-[11px] px-2 py-1',\n },\n lg: { \n variant: 'outline',\n buttonClass: 'h-10 text-sm px-3',\n inputClass: 'h-10 text-sm px-3 py-2',\n },\n}\n\nconst preset = computed(() => {\n const styleKey = props.styletype || 'default'\n return STYLE_PRESETS[styleKey] ?? STYLE_PRESETS.default\n})\n\nconst buttonClass = computed(() => {\n return cn('justify-between', preset.value?.buttonClass, props.class)\n})\n\nconst inputClass = computed(() => {\n return cn('pl-8 pr-2 py-1 focus-visible:ring-0 border-0 border-b rounded-none h-7 text-xs', preset.value?.inputClass)\n})\n\nconst handleChange = (value: ComboboxOption | undefined) => {\n emit('update:modelValue', value)\n emit('change', value)\n}\n</script>\n\n<template>\n <Combobox \n :model-value=\"modelValue\" \n by=\"label\" \n :disabled=\"disabled\"\n :required=\"required\"\n :name=\"name\"\n @update:model-value=\"(value) => handleChange(value as ComboboxOption | undefined)\"\n >\n <ComboboxAnchor as-child>\n <ComboboxTrigger as-child>\n <Button \n :id=\"id\"\n :variant=\"preset.variant\" \n :class=\"buttonClass\"\n :disabled=\"disabled\"\n @focus=\"emit('focus', $event as FocusEvent)\"\n @blur=\"emit('blur', $event as FocusEvent)\"\n >\n {{ modelValue?.label ?? placeholder }}\n <ChevronsUpDown class=\"ml-2 h-3.5 w-3.5 shrink-0 opacity-50\" />\n </Button>\n </ComboboxTrigger>\n </ComboboxAnchor>\n\n <ComboboxList>\n <div class=\"relative w-full max-w-sm items-center\">\n <ComboboxInput :class=\"inputClass\" :placeholder=\"searchPlaceholder\" />\n <span class=\"absolute start-0 inset-y-0 flex items-center justify-center px-2.5\">\n <Search class=\"size-3.5 text-muted-foreground\" />\n </span>\n </div>\n\n <ComboboxEmpty>\n {{ emptyText }}\n </ComboboxEmpty>\n\n <ComboboxGroup>\n <ComboboxItem\n v-for=\"option in options\"\n :key=\"option.value\"\n :value=\"option\"\n >\n {{ option.label }}\n\n <Check v-if=\"modelValue?.value === option.value\" :class=\"cn('ml-auto h-3.5 w-3.5')\" />\n </ComboboxItem>\n </ComboboxGroup>\n </ComboboxList>\n </Combobox>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","styleKey","buttonClass","cn","inputClass","handleChange","value","_createBlock","_unref","Combobox","_cache","_createVNode","ComboboxAnchor","ComboboxTrigger","Button","$event","_createTextVNode","_toDisplayString","ChevronsUpDown","ComboboxList","_createElementVNode","_hoisted_1","ComboboxInput","_hoisted_2","Search","ComboboxEmpty","ComboboxGroup","_createElementBlock","_Fragment","_renderList","option","ComboboxItem","Check"],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
for (const [t_key, t_val] of t_opts)
|
|
4
4
|
t_merged[t_key] = t_val;
|
|
5
5
|
return t_merged;
|
|
6
|
-
};,u=t(e.default,[["__scopeId","data-v-
|
|
6
|
+
};,u=t(e.default,[["__scopeId","data-v-9c1f0de8"]]);exports.default=u;
|
|
7
7
|
//# sourceMappingURL=JSplitter.vue.cjs.map
|
|
@@ -6,8 +6,8 @@ const o = (o_comp, o_opts) => {
|
|
|
6
6
|
o_merged[o_key] = o_val;
|
|
7
7
|
return o_merged;
|
|
8
8
|
};
|
|
9
|
-
const
|
|
9
|
+
const f = /* @__PURE__ */ o(t, [["__scopeId", "data-v-9c1f0de8"]]);
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
f as default
|
|
12
12
|
};
|
|
13
13
|
//# sourceMappingURL=JSplitter.vue.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),s=require("../shadcn/resizable/ResizableHandle.vue.cjs"),
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),s=require("../../lib/utils.cjs"),r=require("../shadcn/resizable/ResizableHandle.vue.cjs"),u=require("../shadcn/resizable/ResizablePanelGroup.vue.cjs"),l=require("reka-ui"),o=e.defineComponent({__name:"JSplitter",props:{direction:{default:"horizontal"},defaultSize:{default:40},minSize:{default:20},maxSize:{},secondMinSize:{},secondMaxSize:{},withHandle:{type:Boolean,default:!1},gap:{default:2},class:{}},setup(t){const i=t,n=e.computed(()=>100-i.defaultSize);return(a,d)=>(e.openBlock(),e.createBlock(e.unref(u.default),{direction:t.direction,class:e.normalizeClass(e.unref(s.cn)("jsplitter-group min-h-0 min-w-0",a.$props.class))},{default:e.withCtx(()=>[e.createVNode(e.unref(l.SplitterPanel),{"default-size":t.defaultSize,"min-size":t.minSize,"max-size":t.maxSize,class:e.normalizeClass(t.gap>0?t.direction==="horizontal"?"pr-[calc(var(--gap)/2)]":"pb-[calc(var(--gap)/2)]":""),style:e.normalizeStyle(t.gap>0?{"--gap":`${t.gap}px`}:{})},{default:e.withCtx(()=>[e.renderSlot(a.$slots,"first",{},void 0,!0),e.renderSlot(a.$slots,"left",{},void 0,!0),e.renderSlot(a.$slots,"top",{},void 0,!0)]),_:3},8,["default-size","min-size","max-size","class","style"]),e.createVNode(e.unref(r.default),{"with-handle":t.withHandle,class:"jsplitter-handle"},null,8,["with-handle"]),e.createVNode(e.unref(l.SplitterPanel),{"default-size":n.value,"min-size":t.secondMinSize,"max-size":t.secondMaxSize,class:e.normalizeClass(t.gap>0?t.direction==="horizontal"?"pl-[calc(var(--gap)/2)]":"pt-[calc(var(--gap)/2)]":""),style:e.normalizeStyle(t.gap>0?{"--gap":`${t.gap}px`}:{})},{default:e.withCtx(()=>[e.renderSlot(a.$slots,"second",{},void 0,!0),e.renderSlot(a.$slots,"right",{},void 0,!0),e.renderSlot(a.$slots,"bottom",{},void 0,!0)]),_:3},8,["default-size","min-size","max-size","class","style"])]),_:3},8,["direction","class"]))}});exports.default=o;
|
|
2
2
|
//# sourceMappingURL=JSplitter.vue2.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSplitter.vue2.cjs","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap:
|
|
1
|
+
{"version":3,"file":"JSplitter.vue2.cjs","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* 패널 콘텐츠가 최소 크기 이하로 줄 때 overflow 깨짐 방지 */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* splitter: 시각선 1px + 드래그 히트영역 8px */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n z-index: 3;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n z-index: 3;\n}\n\n:deep(.jsplitter-handle::after) {\n background: hsl(var(--border) / 0.75) !important;\n}\n\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.05) !important;\n}\n\n:deep(.jsplitter-handle:hover::after),\n:deep(.jsplitter-handle:active::after),\n:deep(.jsplitter-handle[data-state=\"drag\"]::after) {\n background: hsl(var(--primary) / 0.85) !important;\n}\n</style>\n"],"names":["props","__props","secondDefaultSize","computed","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":"kiBAWA,MAAMA,EAAQC,EA+BRC,EAAoBC,EAAAA,SAAS,IAAM,IAAMH,EAAM,WAAW,8BAI9DI,EAAAA,YA+BsBC,EAAAA,MAAAC,EAAAA,OAAA,EAAA,CA/BA,UAAWL,EAAA,UAAY,MAAKM,EAAAA,eAAEF,EAAAA,MAAAG,EAAAA,EAAA,EAAE,kCAAoCC,EAAAA,OAAO,KAAK,CAAA,CAAA,qBAEpG,IAWiB,CAXjBC,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcV,EAAA,YACd,WAAUA,EAAA,QACV,WAAUA,EAAA,QACV,MAAKM,EAAAA,eAAEN,EAAA,IAAG,EAAQA,EAAA,YAAS,aAAA,0BAAA,0BAAA,EAAA,EAC3B,MAAKW,EAAAA,eAAEX,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAqB,CAArBY,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EAErBD,EAAAA,WAAoBC,EAAA,OAAA,OAAA,CAAA,EAAA,OAAA,EAAA,EACpBD,EAAAA,WAAmBC,EAAA,OAAA,MAAA,CAAA,EAAA,OAAA,EAAA,CAAA,kEAIrBJ,cAAsEL,EAAAA,MAAAU,EAAAA,OAAA,EAAA,CAApD,cAAad,EAAA,WAAY,MAAM,kBAAA,0BAGjDS,cAWiBL,EAAAA,MAAAM,EAAAA,aAAA,EAAA,CAVd,eAAcT,EAAA,MACd,WAAUD,EAAA,cACV,WAAUA,EAAA,cACV,MAAKM,EAAAA,eAAEN,EAAA,IAAG,EAAQA,EAAA,YAAS,aAAA,0BAAA,0BAAA,EAAA,EAC3B,MAAKW,EAAAA,eAAEX,EAAA,IAAG,EAAA,CAAA,QAAA,GAAqBA,EAAA,GAAG,IAAA,EAAA,CAAA,CAAA,CAAA,qBAEnC,IAAsB,CAAtBY,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,EAEtBD,EAAAA,WAAqBC,EAAA,OAAA,QAAA,CAAA,EAAA,OAAA,EAAA,EACrBD,EAAAA,WAAsBC,EAAA,OAAA,SAAA,CAAA,EAAA,OAAA,EAAA,CAAA"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
import S from "
|
|
3
|
-
import g from "../shadcn/resizable/
|
|
1
|
+
import { defineComponent as m, computed as u, createBlock as f, openBlock as z, unref as t, normalizeClass as l, withCtx as s, createVNode as n, normalizeStyle as o, renderSlot as i } from "vue";
|
|
2
|
+
import { cn as S } from "../../lib/utils.js";
|
|
3
|
+
import g from "../shadcn/resizable/ResizableHandle.vue.js";
|
|
4
|
+
import h from "../shadcn/resizable/ResizablePanelGroup.vue.js";
|
|
4
5
|
import { SplitterPanel as d } from "reka-ui";
|
|
5
|
-
const
|
|
6
|
+
const M = /* @__PURE__ */ m({
|
|
6
7
|
__name: "JSplitter",
|
|
7
8
|
props: {
|
|
8
9
|
direction: { default: "horizontal" },
|
|
@@ -12,14 +13,14 @@ const w = /* @__PURE__ */ u({
|
|
|
12
13
|
secondMinSize: {},
|
|
13
14
|
secondMaxSize: {},
|
|
14
15
|
withHandle: { type: Boolean, default: !1 },
|
|
15
|
-
gap: { default:
|
|
16
|
+
gap: { default: 2 },
|
|
16
17
|
class: {}
|
|
17
18
|
},
|
|
18
19
|
setup(e) {
|
|
19
|
-
const c = e, r =
|
|
20
|
-
return (a,
|
|
20
|
+
const c = e, r = u(() => 100 - c.defaultSize);
|
|
21
|
+
return (a, p) => (z(), f(t(h), {
|
|
21
22
|
direction: e.direction,
|
|
22
|
-
class: l(
|
|
23
|
+
class: l(t(S)("jsplitter-group min-h-0 min-w-0", a.$props.class))
|
|
23
24
|
}, {
|
|
24
25
|
default: s(() => [
|
|
25
26
|
n(t(d), {
|
|
@@ -36,7 +37,10 @@ const w = /* @__PURE__ */ u({
|
|
|
36
37
|
]),
|
|
37
38
|
_: 3
|
|
38
39
|
}, 8, ["default-size", "min-size", "max-size", "class", "style"]),
|
|
39
|
-
n(t(
|
|
40
|
+
n(t(g), {
|
|
41
|
+
"with-handle": e.withHandle,
|
|
42
|
+
class: "jsplitter-handle"
|
|
43
|
+
}, null, 8, ["with-handle"]),
|
|
40
44
|
n(t(d), {
|
|
41
45
|
"default-size": r.value,
|
|
42
46
|
"min-size": e.secondMinSize,
|
|
@@ -57,6 +61,6 @@ const w = /* @__PURE__ */ u({
|
|
|
57
61
|
}
|
|
58
62
|
});
|
|
59
63
|
export {
|
|
60
|
-
|
|
64
|
+
M as default
|
|
61
65
|
};
|
|
62
66
|
//# sourceMappingURL=JSplitter.vue2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSplitter.vue2.js","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap:
|
|
1
|
+
{"version":3,"file":"JSplitter.vue2.js","sources":["../../../../src/components/atoms/JSplitter.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport {\n ResizablePanelGroup,\n ResizablePanel,\n ResizableHandle,\n} from '@/components/shadcn/resizable'\n\ntype Orientation = 'horizontal' | 'vertical'\n\nconst props = withDefaults(\n defineProps<{\n /** 분할 방향 (horizontal: 좌우, vertical: 상하) */\n direction?: Orientation\n /** 첫 번째 패널의 기본 크기 (%) */\n defaultSize?: number\n /** 첫 번째 패널의 최소 크기 (%) */\n minSize?: number\n /** 첫 번째 패널의 최대 크기 (%) */\n maxSize?: number\n /** 두 번째 패널의 최소 크기 (%) */\n secondMinSize?: number\n /** 두 번째 패널의 최대 크기 (%) */\n secondMaxSize?: number\n /** ResizableHandle에 grip 아이콘 표시 여부 */\n withHandle?: boolean\n /** 패널 간 여백 (px) */\n gap?: number\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n direction: 'horizontal',\n defaultSize: 40,\n minSize: 20,\n withHandle: false,\n gap: 2,\n },\n)\n\n// 두 번째 패널의 기본 크기 계산\nconst secondDefaultSize = computed(() => 100 - props.defaultSize)\n</script>\n\n<template>\n <ResizablePanelGroup :direction=\"direction\" :class=\"cn('jsplitter-group min-h-0 min-w-0', $props.class)\">\n <!-- 첫 번째 패널 (좌측/상단) -->\n <ResizablePanel \n :default-size=\"defaultSize\" \n :min-size=\"minSize\" \n :max-size=\"maxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pr-[calc(var(--gap)/2)]' : 'pb-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"first\" />\n <!-- direction=\"horizontal\"일 때 left, vertical일 때 top으로도 사용 가능 -->\n <slot name=\"left\" />\n <slot name=\"top\" />\n </ResizablePanel>\n\n <!-- 크기 조정 핸들 -->\n <ResizableHandle :with-handle=\"withHandle\" class=\"jsplitter-handle\" />\n\n <!-- 두 번째 패널 (우측/하단) -->\n <ResizablePanel\n :default-size=\"secondDefaultSize\"\n :min-size=\"secondMinSize\"\n :max-size=\"secondMaxSize\"\n :class=\"gap > 0 ? (direction === 'horizontal' ? 'pl-[calc(var(--gap)/2)]' : 'pt-[calc(var(--gap)/2)]') : ''\"\n :style=\"gap > 0 ? { '--gap': `${gap}px` } : {}\"\n >\n <slot name=\"second\" />\n <!-- direction=\"horizontal\"일 때 right, vertical일 때 bottom으로도 사용 가능 -->\n <slot name=\"right\" />\n <slot name=\"bottom\" />\n </ResizablePanel>\n </ResizablePanelGroup>\n</template>\n\n<style scoped>\n/* 패널 콘텐츠가 최소 크기 이하로 줄 때 overflow 깨짐 방지 */\n:deep(.jsplitter-group [data-panel]) {\n min-width: 0;\n min-height: 0;\n}\n\n/* splitter: 시각선 1px + 드래그 히트영역 8px */\n:deep(.jsplitter-handle[data-orientation=\"horizontal\"]) {\n width: 8px !important;\n background: transparent !important;\n z-index: 3;\n}\n\n:deep(.jsplitter-handle[data-orientation=\"vertical\"]) {\n height: 8px !important;\n background: transparent !important;\n z-index: 3;\n}\n\n:deep(.jsplitter-handle::after) {\n background: hsl(var(--border) / 0.75) !important;\n}\n\n:deep(.jsplitter-handle:hover) {\n background: hsl(var(--primary) / 0.05) !important;\n}\n\n:deep(.jsplitter-handle:hover::after),\n:deep(.jsplitter-handle:active::after),\n:deep(.jsplitter-handle[data-state=\"drag\"]::after) {\n background: hsl(var(--primary) / 0.85) !important;\n}\n</style>\n"],"names":["props","__props","secondDefaultSize","computed","_createBlock","_unref","ResizablePanelGroup","_normalizeClass","cn","$props","_createVNode","ResizablePanel","_normalizeStyle","_renderSlot","_ctx","ResizableHandle"],"mappings":";;;;;;;;;;;;;;;;;;;AAWA,UAAMA,IAAQC,GA+BRC,IAAoBC,EAAS,MAAM,MAAMH,EAAM,WAAW;2BAI9DI,EA+BsBC,EAAAC,CAAA,GAAA;AAAA,MA/BA,WAAWL,EAAA;AAAA,MAAY,OAAKM,EAAEF,EAAAG,CAAA,EAAE,mCAAoCC,EAAAA,OAAO,KAAK,CAAA;AAAA,IAAA;iBAEpG,MAWiB;AAAA,QAXjBC,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcV,EAAA;AAAA,UACd,YAAUA,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKM,EAAEN,EAAA,MAAG,IAAQA,EAAA,cAAS,eAAA,4BAAA,4BAAA,EAAA;AAAA,UAC3B,OAAKW,EAAEX,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAqB;AAAA,YAArBY,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAErBD,EAAoBC,EAAA,QAAA,QAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACpBD,EAAmBC,EAAA,QAAA,OAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;QAIrBJ,EAAsEL,EAAAU,CAAA,GAAA;AAAA,UAApD,eAAad,EAAA;AAAA,UAAY,OAAM;AAAA,QAAA;QAGjDS,EAWiBL,EAAAM,CAAA,GAAA;AAAA,UAVd,gBAAcT,EAAA;AAAA,UACd,YAAUD,EAAA;AAAA,UACV,YAAUA,EAAA;AAAA,UACV,OAAKM,EAAEN,EAAA,MAAG,IAAQA,EAAA,cAAS,eAAA,4BAAA,4BAAA,EAAA;AAAA,UAC3B,OAAKW,EAAEX,EAAA,MAAG,IAAA,EAAA,SAAA,GAAqBA,EAAA,GAAG,KAAA,IAAA,CAAA,CAAA;AAAA,QAAA;qBAEnC,MAAsB;AAAA,YAAtBY,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YAEtBD,EAAqBC,EAAA,QAAA,SAAA,CAAA,GAAA,QAAA,EAAA;AAAA,YACrBD,EAAsBC,EAAA,QAAA,UAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAAA;;;;;;;;"}
|
|
@@ -6,8 +6,8 @@ const t = (t_comp, t_opts) => {
|
|
|
6
6
|
t_merged[t_key] = t_val;
|
|
7
7
|
return t_merged;
|
|
8
8
|
};
|
|
9
|
-
const
|
|
9
|
+
const p = /* @__PURE__ */ t(o, [["__scopeId", "data-v-0e5b57c9"]]);
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
p as default
|
|
12
12
|
};
|
|
13
13
|
//# sourceMappingURL=JTabs.vue.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");const h=require("lucide-vue-next"),i=require("../../lib/utils.cjs"),x=require("../atoms/JIcon.vue.cjs"),y=require("../shadcn/Tabs.vue.cjs"),T=require("../shadcn/TabsList.vue.cjs"),b=require("../shadcn/TabsTrigger.vue.cjs"),B=require("../shadcn/TabsContent.vue.cjs"),S={class:"flex-1 truncate"},E=["aria-label","onClick"],P={class:"flex-1 w-full overflow-auto"},q={key:1,class:"p-4"},V={class:"text-muted-foreground"},z=e.defineComponent({__name:"JTabs",props:{tabs:{},activeTabId:{},class:{},listClass:{},styletype:{default:"default"}},emits:["tabChange","tabClose","update:activeTabId"],setup(f,{emit:v}){const n=f,r=v,u=e.computed(()=>Array.isArray(n.tabs)?n.tabs:[]),a=e.ref(n.activeTabId||(u.value.length>0?u.value[0]?.id:"")||"");let o=!1;e.watch(()=>n.activeTabId,t=>{t!==void 0&&t!==a.value&&(a.value=t)},{immediate:!0}),e.watch(u,t=>{!n.activeTabId&&t.length>0&&!t.find(s=>s.id===a.value)&&t[0]&&(a.value=t[0].id)});const _=t=>{if(o)return;const s=String(t);s!==a.value&&(o=!0,a.value=s,r("update:activeTabId",s),r("tabChange",s),e.nextTick(()=>{o=!1}))},m=t=>{o||t===a.value||(o=!0,a.value=t,r("update:activeTabId",t),r("tabChange",t),e.nextTick(()=>{o=!1}))},C=(t,s)=>{t.stopPropagation(),r("tabClose",s)},g=e.computed(()=>i.cn("flex flex-col w-full h-full",n.class)),d={default:{tabPaddingClass:"px-2 py-
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");const h=require("lucide-vue-next"),i=require("../../lib/utils.cjs"),x=require("../atoms/JIcon.vue.cjs"),y=require("../shadcn/Tabs.vue.cjs"),T=require("../shadcn/TabsList.vue.cjs"),b=require("../shadcn/TabsTrigger.vue.cjs"),B=require("../shadcn/TabsContent.vue.cjs"),S={class:"flex-1 truncate"},E=["aria-label","onClick"],P={class:"flex-1 w-full overflow-auto"},q={key:1,class:"p-4"},V={class:"text-muted-foreground"},z=e.defineComponent({__name:"JTabs",props:{tabs:{},activeTabId:{},class:{},listClass:{},styletype:{default:"default"}},emits:["tabChange","tabClose","update:activeTabId"],setup(f,{emit:v}){const n=f,r=v,u=e.computed(()=>Array.isArray(n.tabs)?n.tabs:[]),a=e.ref(n.activeTabId||(u.value.length>0?u.value[0]?.id:"")||"");let o=!1;e.watch(()=>n.activeTabId,t=>{t!==void 0&&t!==a.value&&(a.value=t)},{immediate:!0}),e.watch(u,t=>{!n.activeTabId&&t.length>0&&!t.find(s=>s.id===a.value)&&t[0]&&(a.value=t[0].id)});const _=t=>{if(o)return;const s=String(t);s!==a.value&&(o=!0,a.value=s,r("update:activeTabId",s),r("tabChange",s),e.nextTick(()=>{o=!1}))},m=t=>{o||t===a.value||(o=!0,a.value=t,r("update:activeTabId",t),r("tabChange",t),e.nextTick(()=>{o=!1}))},C=(t,s)=>{t.stopPropagation(),r("tabClose",s)},g=e.computed(()=>i.cn("flex flex-col w-full h-full",n.class)),d={default:{tabPaddingClass:"px-2 py-1",tabTextSizeClass:"text-xs",listPaddingClass:"px-1 py-1"},minimal:{tabPaddingClass:"px-1.5 py-1",tabTextSizeClass:"text-xs",listPaddingClass:"px-1 py-1"}},c=e.computed(()=>d[n.styletype]??d.default),k=e.computed(()=>i.cn("w-full justify-start",c.value.listPaddingClass,n.listClass));return(t,s)=>(e.openBlock(),e.createBlock(e.unref(y.default),{"model-value":a.value,"onUpdate:modelValue":_,orientation:"horizontal",class:e.normalizeClass(g.value)},{default:e.withCtx(()=>[e.createVNode(e.unref(T.default),{class:e.normalizeClass(k.value)},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(u.value,l=>(e.openBlock(),e.createBlock(e.unref(b.default),{key:l.id,value:l.id,onClick:p=>m(l.id),class:e.normalizeClass(e.unref(i.cn)("!flex !items-center !gap-2",c.value.tabPaddingClass,c.value.tabTextSizeClass))},{default:e.withCtx(()=>[l.icon?(e.openBlock(),e.createBlock(x.default,{key:0,name:l.icon,size:"sm",class:"flex-shrink-0"},null,8,["name"])):e.createCommentVNode("",!0),e.createElementVNode("span",S,e.toDisplayString(l.label),1),l.closable?(e.openBlock(),e.createElementBlock("button",{key:1,type:"button",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","aria-label":`${l.label} 탭 닫기`,onClick:p=>C(p,l.id)},[e.createVNode(e.unref(h.X),{class:"h-2.5 w-2.5"})],8,E)):e.createCommentVNode("",!0)]),_:2},1032,["value","onClick","class"]))),128))]),_:1},8,["class"]),e.createElementVNode("div",P,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(u.value,l=>(e.openBlock(),e.createBlock(e.unref(B.default),{key:`content-${l.id}`,value:l.id,class:"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col"},{default:e.withCtx(()=>[e.renderSlot(t.$slots,`content-${l.id}`,{tab:l},()=>[l.component?(e.openBlock(),e.createBlock(e.resolveDynamicComponent(l.component),e.mergeProps({key:0,ref_for:!0},l.props||{}),null,16)):(e.openBlock(),e.createElementBlock("div",q,[e.createElementVNode("p",V,e.toDisplayString(l.label)+" 콘텐츠",1)]))],!0)]),_:2},1032,["value"]))),128))])]),_:3},8,["model-value","class"]))}});exports.default=z;
|
|
2
2
|
//# sourceMappingURL=JTabs.vue2.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JTabs.vue2.cjs","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n * \n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n * \n * @example\n * ```vue\n * <JTabs \n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n \n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n // reka-ui 이벤트가 작동하지 않을 경우 직접 처리\n // handleTabValueChange가 이미 처리했으면 중복 방지\n if (isHandlingEvent || tabId === internalActiveId.value) return\n \n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation() // 탭 클릭 이벤트 전파 방지\n emit('tabClose', tabId)\n}\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n tabPaddingClass: string\n tabTextSizeClass: string\n listPaddingClass: string\n}> = {\n default: {\n tabPaddingClass: 'px-2 py-0.5',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1.5 py-0.5',\n },\n minimal: {\n tabPaddingClass: 'px-1.5 py-0.5',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1.5 py-0.5',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 탭 리스트 클래스\n * Tabs list classes\n */\nconst listClasses = computed(() => {\n return cn('w-full justify-start', preset.value.listPaddingClass, props.listClass)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"cn('!flex !items-center !gap-2', preset.tabPaddingClass, preset.tabTextSizeClass)\"\n >\n <!-- 탭 아이콘 (있을 경우) / Tab Icon -->\n <JIcon \n v-if=\"tab.icon\" \n :name=\"tab.icon\" \n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n \n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n \n <!-- 닫기 버튼 / Close Button (항상 표시) -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div class=\"flex-1 w-full overflow-auto\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col\"\n >\n <!-- 슬롯 우선 / Slot First -->\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <!-- 동적 컴포넌트 렌더링 / Dynamic Component Rendering -->\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n \n <!-- 기본 콘텐츠 / Default Content -->\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/**\n * 탭 리스트 스타일 - 하단 보더 제거\n * Tab list styles without bottom border\n */\n:deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n background: hsl(var(--background));\n padding: 0.125rem 0.375rem 0;\n gap: 0.125rem;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar) {\n height: 6px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(0, 0, 0, 0.3);\n}\n\n/**\n * 다크모드에서 스크롤바 스타일\n * Scrollbar styles in dark mode\n */\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n/**\n * 탭 버튼 스타일 - 명확한 구분\n * Tab button styles - clear distinction\n */\n:deep([role=\"tab\"]) {\n position: relative;\n padding: 0.25rem 0.625rem;\n min-height: 1.75rem;\n border-radius: 0.375rem 0.375rem 0 0;\n transition: all 0.2s ease;\n border: 1px solid transparent;\n border-bottom: none;\n}\n\n/**\n * Minimal 스타일 탭 버튼 - JSidebarAdvanced와 높이 맞춤\n */\n:deep([role=\"tablist\"][class*=\"p-0.5\"] [role=\"tab\"]) {\n padding: 0.25rem 0.5rem;\n}\n\n/**\n * 비활성 탭 - 명확하게 구분\n * Inactive tabs - clear distinction\n */\n:deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--muted) / 0.2);\n color: hsl(var(--muted-foreground));\n border-top: 1px solid hsl(var(--border) / 0.4);\n border-left: 1px solid hsl(var(--border) / 0.4);\n border-right: 1px solid hsl(var(--border) / 0.4);\n}\n\n:deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted) / 0.4);\n color: hsl(var(--foreground));\n}\n\n/**\n * 다크모드에서 비활성 탭 - 다크 배경색 사용\n * Inactive tabs in dark mode - use dark background\n */\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--secondary));\n color: hsl(var(--secondary-foreground));\n border-top: 1px solid hsl(var(--border) / 0.5);\n border-left: 1px solid hsl(var(--border) / 0.5);\n border-right: 1px solid hsl(var(--border) / 0.5);\n}\n\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted));\n color: hsl(var(--muted-foreground));\n}\n\n/**\n * 활성 탭 - 강조된 스타일\n * Active tab - emphasized style\n */\n:deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--background));\n color: hsl(var(--foreground));\n font-weight: 500;\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n z-index: 1;\n}\n\n/**\n * 다크모드에서 활성 탭 - 배경색 명확히 구분\n * Active tab in dark mode - clear background distinction\n */\n.dark :deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--card));\n color: hsl(var(--card-foreground));\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","e","rootClasses","cn","STYLE_PRESETS","preset","listClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","_normalizeClass","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","X","_hoisted_3","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JTabs.vue2.cjs","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n * \n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n * \n * @example\n * ```vue\n * <JTabs \n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n \n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n // reka-ui 이벤트가 작동하지 않을 경우 직접 처리\n // handleTabValueChange가 이미 처리했으면 중복 방지\n if (isHandlingEvent || tabId === internalActiveId.value) return\n \n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation() // 탭 클릭 이벤트 전파 방지\n emit('tabClose', tabId)\n}\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n tabPaddingClass: string\n tabTextSizeClass: string\n listPaddingClass: string\n}> = {\n default: {\n tabPaddingClass: 'px-2 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n minimal: {\n tabPaddingClass: 'px-1.5 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 탭 리스트 클래스\n * Tabs list classes\n */\nconst listClasses = computed(() => {\n return cn('w-full justify-start', preset.value.listPaddingClass, props.listClass)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"cn('!flex !items-center !gap-2', preset.tabPaddingClass, preset.tabTextSizeClass)\"\n >\n <!-- 탭 아이콘 (있을 경우) / Tab Icon -->\n <JIcon \n v-if=\"tab.icon\" \n :name=\"tab.icon\" \n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n \n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n \n <!-- 닫기 버튼 / Close Button (항상 표시) -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div class=\"flex-1 w-full overflow-auto\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col\"\n >\n <!-- 슬롯 우선 / Slot First -->\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <!-- 동적 컴포넌트 렌더링 / Dynamic Component Rendering -->\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n \n <!-- 기본 콘텐츠 / Default Content -->\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/**\n * 탭 리스트 스타일 - 하단 보더 제거\n * Tab list styles without bottom border\n */\n:deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n background: hsl(var(--background));\n padding: 0 0.25rem;\n gap: 0.25rem;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar) {\n height: 6px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(0, 0, 0, 0.3);\n}\n\n/**\n * 다크모드에서 스크롤바 스타일\n * Scrollbar styles in dark mode\n */\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n/**\n * 탭 버튼 스타일 - 명확한 구분\n * Tab button styles - clear distinction\n */\n:deep([role=\"tab\"]) {\n position: relative;\n padding: 0.25rem 0.5rem;\n min-height: 1.75rem;\n border-radius: 0.375rem 0.375rem 0 0;\n transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n border: 1px solid transparent;\n border-bottom: none;\n}\n\n/**\n * Minimal 스타일 탭 버튼 - JSidebarAdvanced와 높이 맞춤\n */\n:deep([role=\"tablist\"][class*=\"p-0.5\"] [role=\"tab\"]) {\n padding: 0.25rem 0.5rem;\n}\n\n/**\n * 비활성 탭 - 명확하게 구분\n * Inactive tabs - clear distinction\n */\n:deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--muted) / 0.2);\n color: hsl(var(--muted-foreground));\n border-top: 1px solid hsl(var(--border) / 0.4);\n border-left: 1px solid hsl(var(--border) / 0.4);\n border-right: 1px solid hsl(var(--border) / 0.4);\n}\n\n:deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted) / 0.4);\n color: hsl(var(--foreground));\n}\n\n/**\n * 다크모드에서 비활성 탭 - 다크 배경색 사용\n * Inactive tabs in dark mode - use dark background\n */\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--secondary));\n color: hsl(var(--secondary-foreground));\n border-top: 1px solid hsl(var(--border) / 0.5);\n border-left: 1px solid hsl(var(--border) / 0.5);\n border-right: 1px solid hsl(var(--border) / 0.5);\n}\n\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted));\n color: hsl(var(--muted-foreground));\n}\n\n/**\n * 활성 탭 - 강조된 스타일\n * Active tab - emphasized style\n */\n:deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--background));\n color: hsl(var(--foreground));\n font-weight: 500;\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n z-index: 1;\n}\n\n/**\n * 다크모드에서 활성 탭 - 배경색 명확히 구분\n * Active tab in dark mode - clear background distinction\n */\n.dark :deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--card));\n color: hsl(var(--card-foreground));\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","e","rootClasses","cn","STYLE_PRESETS","preset","listClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","_normalizeClass","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","X","_hoisted_3","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5"],"mappings":"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"}
|
|
@@ -44,14 +44,14 @@ const Y = { class: "flex-1 truncate" }, q = ["aria-label", "onClick"], G = { cla
|
|
|
44
44
|
e.stopPropagation(), r("tabClose", l);
|
|
45
45
|
}, w = d(() => C("flex flex-col w-full h-full", n.class)), h = {
|
|
46
46
|
default: {
|
|
47
|
-
tabPaddingClass: "px-2 py-
|
|
47
|
+
tabPaddingClass: "px-2 py-1",
|
|
48
48
|
tabTextSizeClass: "text-xs",
|
|
49
|
-
listPaddingClass: "px-1
|
|
49
|
+
listPaddingClass: "px-1 py-1"
|
|
50
50
|
},
|
|
51
51
|
minimal: {
|
|
52
|
-
tabPaddingClass: "px-1.5 py-
|
|
52
|
+
tabPaddingClass: "px-1.5 py-1",
|
|
53
53
|
tabTextSizeClass: "text-xs",
|
|
54
|
-
listPaddingClass: "px-1
|
|
54
|
+
listPaddingClass: "px-1 py-1"
|
|
55
55
|
}
|
|
56
56
|
}, p = d(() => h[n.styletype] ?? h.default), A = d(() => C("w-full justify-start", p.value.listPaddingClass, n.listClass));
|
|
57
57
|
return (e, l) => (s(), u(i(J), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JTabs.vue2.js","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n * \n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n * \n * @example\n * ```vue\n * <JTabs \n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n \n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n // reka-ui 이벤트가 작동하지 않을 경우 직접 처리\n // handleTabValueChange가 이미 처리했으면 중복 방지\n if (isHandlingEvent || tabId === internalActiveId.value) return\n \n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation() // 탭 클릭 이벤트 전파 방지\n emit('tabClose', tabId)\n}\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n tabPaddingClass: string\n tabTextSizeClass: string\n listPaddingClass: string\n}> = {\n default: {\n tabPaddingClass: 'px-2 py-0.5',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1.5 py-0.5',\n },\n minimal: {\n tabPaddingClass: 'px-1.5 py-0.5',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1.5 py-0.5',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 탭 리스트 클래스\n * Tabs list classes\n */\nconst listClasses = computed(() => {\n return cn('w-full justify-start', preset.value.listPaddingClass, props.listClass)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"cn('!flex !items-center !gap-2', preset.tabPaddingClass, preset.tabTextSizeClass)\"\n >\n <!-- 탭 아이콘 (있을 경우) / Tab Icon -->\n <JIcon \n v-if=\"tab.icon\" \n :name=\"tab.icon\" \n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n \n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n \n <!-- 닫기 버튼 / Close Button (항상 표시) -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div class=\"flex-1 w-full overflow-auto\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col\"\n >\n <!-- 슬롯 우선 / Slot First -->\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <!-- 동적 컴포넌트 렌더링 / Dynamic Component Rendering -->\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n \n <!-- 기본 콘텐츠 / Default Content -->\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/**\n * 탭 리스트 스타일 - 하단 보더 제거\n * Tab list styles without bottom border\n */\n:deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n background: hsl(var(--background));\n padding: 0.125rem 0.375rem 0;\n gap: 0.125rem;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar) {\n height: 6px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(0, 0, 0, 0.3);\n}\n\n/**\n * 다크모드에서 스크롤바 스타일\n * Scrollbar styles in dark mode\n */\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n/**\n * 탭 버튼 스타일 - 명확한 구분\n * Tab button styles - clear distinction\n */\n:deep([role=\"tab\"]) {\n position: relative;\n padding: 0.25rem 0.625rem;\n min-height: 1.75rem;\n border-radius: 0.375rem 0.375rem 0 0;\n transition: all 0.2s ease;\n border: 1px solid transparent;\n border-bottom: none;\n}\n\n/**\n * Minimal 스타일 탭 버튼 - JSidebarAdvanced와 높이 맞춤\n */\n:deep([role=\"tablist\"][class*=\"p-0.5\"] [role=\"tab\"]) {\n padding: 0.25rem 0.5rem;\n}\n\n/**\n * 비활성 탭 - 명확하게 구분\n * Inactive tabs - clear distinction\n */\n:deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--muted) / 0.2);\n color: hsl(var(--muted-foreground));\n border-top: 1px solid hsl(var(--border) / 0.4);\n border-left: 1px solid hsl(var(--border) / 0.4);\n border-right: 1px solid hsl(var(--border) / 0.4);\n}\n\n:deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted) / 0.4);\n color: hsl(var(--foreground));\n}\n\n/**\n * 다크모드에서 비활성 탭 - 다크 배경색 사용\n * Inactive tabs in dark mode - use dark background\n */\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--secondary));\n color: hsl(var(--secondary-foreground));\n border-top: 1px solid hsl(var(--border) / 0.5);\n border-left: 1px solid hsl(var(--border) / 0.5);\n border-right: 1px solid hsl(var(--border) / 0.5);\n}\n\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted));\n color: hsl(var(--muted-foreground));\n}\n\n/**\n * 활성 탭 - 강조된 스타일\n * Active tab - emphasized style\n */\n:deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--background));\n color: hsl(var(--foreground));\n font-weight: 500;\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n z-index: 1;\n}\n\n/**\n * 다크모드에서 활성 탭 - 배경색 명확히 구분\n * Active tab in dark mode - clear background distinction\n */\n.dark :deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--card));\n color: hsl(var(--card-foreground));\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","rootClasses","cn","STYLE_PRESETS","preset","listClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","_normalizeClass","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","e","X","_hoisted_3","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JTabs.vue2.js","sources":["../../../../src/components/molecules/JTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/shadcn'\nimport type { JTabsProps, JTabsEmits } from '@/types/dynamic-tabs.types'\nimport { X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport JIcon from '@/components/atoms/JIcon.vue'\n\n/**\n * JTabs - 기본 탭 UI 컴포넌트 (molecules)\n * Basic Tabs UI Component\n * \n * @description\n * 정적인 탭 목록을 렌더링하는 기본 탭 컴포넌트입니다.\n * 닫기 버튼, 아이콘 등을 지원합니다.\n * \n * @example\n * ```vue\n * <JTabs \n * :tabs=\"tabs\"\n * :active-tab-id=\"activeId\"\n * @tab-change=\"handleChange\"\n * @tab-close=\"handleClose\"\n * />\n * ```\n */\n\ntype StyleType = 'default' | 'minimal'\n\nconst props = withDefaults(defineProps<JTabsProps>(), {\n styletype: 'default',\n})\n\nconst emit = defineEmits<JTabsEmits>()\n\n/**\n * 안전한 tabs 배열 (undefined/null 체크)\n * Safe tabs array (undefined/null check)\n */\nconst safeTabs = computed(() => {\n return Array.isArray(props.tabs) ? props.tabs : []\n})\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst internalActiveId = ref<string>(\n props.activeTabId || (safeTabs.value.length > 0 ? safeTabs.value[0]?.id : '') || ''\n)\n\n/**\n * 이벤트 처리 중 플래그 (중복 이벤트 방지)\n * Flag to prevent duplicate events\n */\nlet isHandlingEvent = false\n\n/**\n * props.activeTabId가 변경되면 내부 상태 동기화\n * Sync internal state when props.activeTabId changes\n */\nwatch(() => props.activeTabId, (newValue) => {\n if (newValue !== undefined && newValue !== internalActiveId.value) {\n internalActiveId.value = newValue\n }\n}, { immediate: true })\n\n/**\n * props.tabs가 변경되고 activeTabId가 없으면 첫 번째 탭 활성화\n * Activate first tab when tabs change and no activeTabId\n */\nwatch(safeTabs, (newTabs) => {\n if (!props.activeTabId && newTabs.length > 0 && !newTabs.find(t => t.id === internalActiveId.value) && newTabs[0]) {\n internalActiveId.value = newTabs[0].id\n }\n})\n\n/**\n * 탭 값 변경 핸들러 (reka-ui TabsRoot에서 직접 호출됨)\n * Tab value change handler (called directly from reka-ui TabsRoot)\n */\nconst handleTabValueChange = (value: string | number) => {\n if (isHandlingEvent) return\n \n const stringValue = String(value)\n if (stringValue !== internalActiveId.value) {\n isHandlingEvent = true\n internalActiveId.value = stringValue\n emit('update:activeTabId', stringValue)\n emit('tabChange', stringValue)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n }\n}\n\n/**\n * 탭 클릭 핸들러 (백업 방안 - reka-ui 이벤트가 작동하지 않을 경우)\n * Tab click handler (backup - in case reka-ui events don't work)\n */\nconst handleTabClick = (tabId: string) => {\n // reka-ui 이벤트가 작동하지 않을 경우 직접 처리\n // handleTabValueChange가 이미 처리했으면 중복 방지\n if (isHandlingEvent || tabId === internalActiveId.value) return\n \n isHandlingEvent = true\n internalActiveId.value = tabId\n emit('update:activeTabId', tabId)\n emit('tabChange', tabId)\n // 다음 tick에서 플래그 리셋\n nextTick(() => {\n isHandlingEvent = false\n })\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleCloseTab = (e: Event, tabId: string) => {\n e.stopPropagation() // 탭 클릭 이벤트 전파 방지\n emit('tabClose', tabId)\n}\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('flex flex-col w-full h-full', props.class)\n})\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n tabPaddingClass: string\n tabTextSizeClass: string\n listPaddingClass: string\n}> = {\n default: {\n tabPaddingClass: 'px-2 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n minimal: {\n tabPaddingClass: 'px-1.5 py-1',\n tabTextSizeClass: 'text-xs',\n listPaddingClass: 'px-1 py-1',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 탭 리스트 클래스\n * Tabs list classes\n */\nconst listClasses = computed(() => {\n return cn('w-full justify-start', preset.value.listPaddingClass, props.listClass)\n})\n</script>\n\n<template>\n <Tabs\n :model-value=\"internalActiveId\"\n @update:model-value=\"handleTabValueChange\"\n orientation=\"horizontal\"\n :class=\"rootClasses\"\n >\n <!-- 탭 헤더 영역 / Tab Headers -->\n <TabsList :class=\"listClasses\">\n <TabsTrigger\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n @click=\"handleTabClick(tab.id)\"\n :class=\"cn('!flex !items-center !gap-2', preset.tabPaddingClass, preset.tabTextSizeClass)\"\n >\n <!-- 탭 아이콘 (있을 경우) / Tab Icon -->\n <JIcon \n v-if=\"tab.icon\" \n :name=\"tab.icon\" \n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n \n <!-- 탭 레이블 / Tab Label -->\n <span class=\"flex-1 truncate\">{{ tab.label }}</span>\n \n <!-- 닫기 버튼 / Close Button (항상 표시) -->\n <button\n v-if=\"tab.closable\"\n type=\"button\"\n class=\"flex-shrink-0 h-3.5 w-3.5 rounded-sm hover:bg-destructive/10 hover:text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring flex items-center justify-center\"\n :aria-label=\"`${tab.label} 탭 닫기`\"\n @click=\"(e) => handleCloseTab(e, tab.id)\"\n >\n <X class=\"h-2.5 w-2.5\" />\n </button>\n </TabsTrigger>\n </TabsList>\n\n <!-- 탭 콘텐츠 영역 / Tab Contents -->\n <div class=\"flex-1 w-full overflow-auto\">\n <TabsContent\n v-for=\"tab in safeTabs\"\n :key=\"`content-${tab.id}`\"\n :value=\"tab.id\"\n class=\"h-full mt-0 data-[state=active]:flex data-[state=active]:flex-col\"\n >\n <!-- 슬롯 우선 / Slot First -->\n <slot :name=\"`content-${tab.id}`\" :tab=\"tab\">\n <!-- 동적 컴포넌트 렌더링 / Dynamic Component Rendering -->\n <component\n v-if=\"tab.component\"\n :is=\"tab.component\"\n v-bind=\"tab.props || {}\"\n />\n \n <!-- 기본 콘텐츠 / Default Content -->\n <div v-else class=\"p-4\">\n <p class=\"text-muted-foreground\">{{ tab.label }} 콘텐츠</p>\n </div>\n </slot>\n </TabsContent>\n </div>\n </Tabs>\n</template>\n\n<style scoped>\n/**\n * 탭 리스트 스타일 - 하단 보더 제거\n * Tab list styles without bottom border\n */\n:deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n background: hsl(var(--background));\n padding: 0 0.25rem;\n gap: 0.25rem;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar) {\n height: 6px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-track) {\n background: transparent;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n}\n\n:deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(0, 0, 0, 0.3);\n}\n\n/**\n * 다크모드에서 스크롤바 스타일\n * Scrollbar styles in dark mode\n */\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb) {\n background-color: rgba(255, 255, 255, 0.2);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)::-webkit-scrollbar-thumb:hover) {\n background-color: rgba(255, 255, 255, 0.3);\n}\n\n.dark :deep([role=\"tablist\"]:not(.ag-side-buttons)) {\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n/**\n * 탭 버튼 스타일 - 명확한 구분\n * Tab button styles - clear distinction\n */\n:deep([role=\"tab\"]) {\n position: relative;\n padding: 0.25rem 0.5rem;\n min-height: 1.75rem;\n border-radius: 0.375rem 0.375rem 0 0;\n transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;\n border: 1px solid transparent;\n border-bottom: none;\n}\n\n/**\n * Minimal 스타일 탭 버튼 - JSidebarAdvanced와 높이 맞춤\n */\n:deep([role=\"tablist\"][class*=\"p-0.5\"] [role=\"tab\"]) {\n padding: 0.25rem 0.5rem;\n}\n\n/**\n * 비활성 탭 - 명확하게 구분\n * Inactive tabs - clear distinction\n */\n:deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--muted) / 0.2);\n color: hsl(var(--muted-foreground));\n border-top: 1px solid hsl(var(--border) / 0.4);\n border-left: 1px solid hsl(var(--border) / 0.4);\n border-right: 1px solid hsl(var(--border) / 0.4);\n}\n\n:deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted) / 0.4);\n color: hsl(var(--foreground));\n}\n\n/**\n * 다크모드에서 비활성 탭 - 다크 배경색 사용\n * Inactive tabs in dark mode - use dark background\n */\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]) {\n background: hsl(var(--secondary));\n color: hsl(var(--secondary-foreground));\n border-top: 1px solid hsl(var(--border) / 0.5);\n border-left: 1px solid hsl(var(--border) / 0.5);\n border-right: 1px solid hsl(var(--border) / 0.5);\n}\n\n.dark :deep([role=\"tab\"][data-state=\"inactive\"]:hover) {\n background: hsl(var(--muted));\n color: hsl(var(--muted-foreground));\n}\n\n/**\n * 활성 탭 - 강조된 스타일\n * Active tab - emphasized style\n */\n:deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--background));\n color: hsl(var(--foreground));\n font-weight: 500;\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n z-index: 1;\n}\n\n/**\n * 다크모드에서 활성 탭 - 배경색 명확히 구분\n * Active tab in dark mode - clear background distinction\n */\n.dark :deep([role=\"tab\"][data-state=\"active\"]) {\n background: hsl(var(--card));\n color: hsl(var(--card-foreground));\n border-top: 2px solid hsl(var(--primary));\n border-left: 1px solid hsl(var(--border));\n border-right: 1px solid hsl(var(--border));\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n</style>\n"],"names":["props","__props","emit","__emit","safeTabs","computed","internalActiveId","ref","isHandlingEvent","watch","newValue","newTabs","t","handleTabValueChange","value","stringValue","nextTick","handleTabClick","tabId","handleCloseTab","rootClasses","cn","STYLE_PRESETS","preset","listClasses","_createBlock","_unref","Tabs","_createVNode","TabsList","_createElementBlock","_Fragment","_renderList","tab","TabsTrigger","$event","_normalizeClass","JIcon","_createElementVNode","_hoisted_1","_toDisplayString","e","X","_hoisted_3","TabsContent","_renderSlot","_ctx","_openBlock","_resolveDynamicComponent","_mergeProps","_hoisted_4","_hoisted_5"],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
for (const [t_key, t_val] of t_opts)
|
|
4
4
|
t_merged[t_key] = t_val;
|
|
5
5
|
return t_merged;
|
|
6
|
-
};,u=t(e.default,[["__scopeId","data-v-
|
|
6
|
+
};,u=t(e.default,[["__scopeId","data-v-0b34d405"]]);exports.default=u;
|
|
7
7
|
//# sourceMappingURL=JDynamicForm.vue.cjs.map
|