@j-solution/components 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +6 -5
  2. package/assets/jwms-portal-frontend-B5GA5JuZ.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/components/atoms/JBadge.vue.cjs +1 -1
  5. package/components/atoms/JBadge.vue.cjs.map +1 -1
  6. package/components/atoms/JBadge.vue.js +4 -4
  7. package/components/atoms/JBadge.vue.js.map +1 -1
  8. package/components/atoms/JGrid.vue.cjs +1 -1
  9. package/components/atoms/JGrid.vue.js +2 -2
  10. package/components/atoms/JGrid.vue2.cjs +1 -1
  11. package/components/atoms/JGrid.vue2.cjs.map +1 -1
  12. package/components/atoms/JGrid.vue2.js +16 -15
  13. package/components/atoms/JGrid.vue2.js.map +1 -1
  14. package/components/molecules/JCard.vue.cjs +1 -1
  15. package/components/molecules/JCard.vue.cjs.map +1 -1
  16. package/components/molecules/JCard.vue.js +28 -25
  17. package/components/molecules/JCard.vue.js.map +1 -1
  18. package/components/molecules/JFormField.vue.cjs +1 -1
  19. package/components/molecules/JFormField.vue.js +1 -1
  20. package/components/molecules/JFormField.vue2.cjs +1 -1
  21. package/components/molecules/JFormField.vue2.cjs.map +1 -1
  22. package/components/molecules/JFormField.vue2.js +124 -109
  23. package/components/molecules/JFormField.vue2.js.map +1 -1
  24. package/components/molecules/JTabs.vue.cjs +1 -1
  25. package/components/molecules/JTabs.vue.js +2 -2
  26. package/components/molecules/JTabs.vue2.cjs +1 -1
  27. package/components/molecules/JTabs.vue2.cjs.map +1 -1
  28. package/components/molecules/JTabs.vue2.js +108 -57
  29. package/components/molecules/JTabs.vue2.js.map +1 -1
  30. package/components/molecules/JTitlebar.vue.cjs +1 -1
  31. package/components/molecules/JTitlebar.vue.cjs.map +1 -1
  32. package/components/molecules/JTitlebar.vue.js +5 -5
  33. package/components/molecules/JTitlebar.vue.js.map +1 -1
  34. package/components/organisms/JFilterBar.vue.cjs +1 -1
  35. package/components/organisms/JFilterBar.vue.js +1 -1
  36. package/components/organisms/JFilterBar.vue2.cjs +1 -1
  37. package/components/organisms/JFilterBar.vue2.cjs.map +1 -1
  38. package/components/organisms/JFilterBar.vue2.js +14 -14
  39. package/components/organisms/JFilterBar.vue2.js.map +1 -1
  40. package/components/organisms/JPageContainer.vue.cjs +1 -1
  41. package/components/organisms/JPageContainer.vue.cjs.map +1 -1
  42. package/components/organisms/JPageContainer.vue.js +13 -13
  43. package/components/organisms/JPageContainer.vue.js.map +1 -1
  44. package/components/shadcn/FieldDescription.vue.cjs +1 -1
  45. package/components/shadcn/FieldDescription.vue.cjs.map +1 -1
  46. package/components/shadcn/FieldDescription.vue.js +1 -1
  47. package/components/shadcn/FieldDescription.vue.js.map +1 -1
  48. package/components/shadcn/FieldError.vue.cjs +1 -1
  49. package/components/shadcn/FieldError.vue.cjs.map +1 -1
  50. package/components/shadcn/FieldError.vue.js +7 -7
  51. package/components/shadcn/FieldError.vue.js.map +1 -1
  52. package/package.json +1 -1
  53. package/types/index.d.ts +9 -1
  54. package/assets/jwms-portal-frontend-DbH22gPh.css +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue2.cjs","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-2 py-1\">\n <div class=\"flex items-center gap-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-2 pb-2\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 활성 필터 배지 — cursor-default(JBadge)의 기본 상태 구분감 보정 */\n:deep(.cursor-default) {\n background-color: hsl(var(--muted) / 0.82) !important;\n border: 1px solid hsl(var(--border) / 0.9) !important;\n}\n:deep(.cursor-default) > button {\n background-color: hsl(var(--background) / 0.98);\n color: hsl(var(--foreground) / 0.9);\n}\n:deep(.cursor-default) > button:hover {\n background-color: hsl(var(--muted) / 1);\n color: hsl(var(--foreground));\n}\n\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.5rem 0.75rem;\n --label-w: 5rem; /* 필터 컨텍스트: 라벨 컴팩트 (80px) → 필드에 공간 확보 */\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":"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"}
1
+ {"version":3,"file":"JFilterBar.vue2.cjs","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground shadow-sm', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-2 py-1\">\n <div class=\"flex items-center gap-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-2 pb-2\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 활성 필터 배지 — cursor-default(JBadge)의 기본 상태 구분감 보정 */\n:deep(.cursor-default) {\n background-color: hsl(var(--muted) / 0.82) !important;\n border: 1px solid hsl(var(--border) / 0.9) !important;\n}\n:deep(.cursor-default) > button {\n background-color: hsl(var(--background) / 0.98);\n color: hsl(var(--foreground) / 0.9);\n}\n:deep(.cursor-default) > button:hover {\n background-color: hsl(var(--muted) / 1);\n color: hsl(var(--foreground));\n}\n\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.375rem 0.75rem;\n --label-w: 5rem; /* 필터 컨텍스트: 라벨 컴팩트 (80px) → 필드에 공간 확보 */\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { defineComponent as D, computed as v, createElementBlock as u, openBlock as o, normalizeClass as x, unref as p, createElementVNode as n, withDirectives as T, createCommentVNode as i, createBlock as f, createVNode as b, Fragment as $, renderList as z, withCtx as m, toDisplayString as d, withModifiers as E, renderSlot as V, createTextVNode as B, vShow as F } from "vue";
1
+ import { defineComponent as D, computed as v, createElementBlock as u, openBlock as o, normalizeClass as x, unref as m, createElementVNode as n, withDirectives as T, createCommentVNode as i, createBlock as f, createVNode as b, Fragment as $, renderList as z, withCtx as p, toDisplayString as d, withModifiers as E, renderSlot as V, createTextVNode as w, vShow as F } from "vue";
2
2
  import { ChevronDown as N, X as R } from "lucide-vue-next";
3
3
  import J from "../atoms/JBadge.vue.js";
4
- import k from "../atoms/JButton.vue.js";
4
+ import B from "../atoms/JButton.vue.js";
5
5
  import O from "../atoms/JLabel.vue.js";
6
6
  import { cn as L } from "../../lib/utils.js";
7
7
  const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class: "flex items-center gap-1" }, q = {
@@ -22,8 +22,8 @@ const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class:
22
22
  searchButtonText: { default: "조회" }
23
23
  },
24
24
  emits: ["update:collapsed", "update:filterValues", "search", "reset"],
25
- setup(r, { emit: w }) {
26
- const l = r, a = w, y = v(() => l.collapsible ? !l.collapsed : !0);
25
+ setup(r, { emit: k }) {
26
+ const l = r, a = k, y = v(() => l.collapsible ? !l.collapsed : !0);
27
27
  function _(e) {
28
28
  return !!(e == null || typeof e == "string" && e.trim() === "" || Array.isArray(e) && e.length === 0);
29
29
  }
@@ -60,7 +60,7 @@ const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class:
60
60
  typeof s == "string" ? t[e] = "" : Array.isArray(s) ? t[e] = [] : t[e] = null, a("update:filterValues", t);
61
61
  }
62
62
  return (e, t) => (o(), u("div", {
63
- class: x(p(L)("j-filter-bar w-full rounded-sm border bg-card text-card-foreground", l.class))
63
+ class: x(m(L)("j-filter-bar w-full rounded-sm border bg-card text-card-foreground shadow-sm", l.class))
64
64
  }, [
65
65
  n("div", M, [
66
66
  n("div", X, [
@@ -70,7 +70,7 @@ const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class:
70
70
  class: "flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors",
71
71
  onClick: C
72
72
  }, [
73
- b(p(N), {
73
+ b(m(N), {
74
74
  class: x([
75
75
  "h-3.5 w-3.5 transition-transform",
76
76
  y.value ? "rotate-0" : "-rotate-90"
@@ -89,7 +89,7 @@ const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class:
89
89
  size: "sm",
90
90
  class: "flex items-center gap-1 cursor-default"
91
91
  }, {
92
- default: m(() => [
92
+ default: p(() => [
93
93
  n("span", G, d(s.label) + ":", 1),
94
94
  n("span", null, d(s.value), 1),
95
95
  n("button", {
@@ -97,7 +97,7 @@ const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class:
97
97
  class: "ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors",
98
98
  onClick: E((c) => j(s.key), ["stop"])
99
99
  }, [
100
- b(p(R), { class: "h-3 w-3" })
100
+ b(m(R), { class: "h-3 w-3" })
101
101
  ], 8, H)
102
102
  ]),
103
103
  _: 2
@@ -106,25 +106,25 @@ const M = { class: "flex items-center justify-between px-2 py-1" }, X = { class:
106
106
  ]),
107
107
  n("div", I, [
108
108
  V(e.$slots, "actions", {}, void 0, !0),
109
- r.showResetButton ? (o(), f(k, {
109
+ r.showResetButton ? (o(), f(B, {
110
110
  key: 0,
111
111
  variant: "secondary",
112
112
  size: "sm",
113
113
  onClick: S
114
114
  }, {
115
- default: m(() => [
116
- B(d(r.resetButtonText), 1)
115
+ default: p(() => [
116
+ w(d(r.resetButtonText), 1)
117
117
  ]),
118
118
  _: 1
119
119
  })) : i("", !0),
120
- r.showSearchButton ? (o(), f(k, {
120
+ r.showSearchButton ? (o(), f(B, {
121
121
  key: 1,
122
122
  styletype: "primary",
123
123
  size: "sm",
124
124
  onClick: A
125
125
  }, {
126
- default: m(() => [
127
- B(d(r.searchButtonText), 1)
126
+ default: p(() => [
127
+ w(d(r.searchButtonText), 1)
128
128
  ]),
129
129
  _: 1
130
130
  })) : i("", !0)
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue2.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-2 py-1\">\n <div class=\"flex items-center gap-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-2 pb-2\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 활성 필터 배지 — cursor-default(JBadge)의 기본 상태 구분감 보정 */\n:deep(.cursor-default) {\n background-color: hsl(var(--muted) / 0.82) !important;\n border: 1px solid hsl(var(--border) / 0.9) !important;\n}\n:deep(.cursor-default) > button {\n background-color: hsl(var(--background) / 0.98);\n color: hsl(var(--foreground) / 0.9);\n}\n:deep(.cursor-default) > button:hover {\n background-color: hsl(var(--muted) / 1);\n color: hsl(var(--foreground));\n}\n\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.5rem 0.75rem;\n --label-w: 5rem; /* 필터 컨텍스트: 라벨 컴팩트 (80px) → 필드에 공간 확보 */\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":"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"}
1
+ {"version":3,"file":"JFilterBar.vue2.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground shadow-sm', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-2 py-1\">\n <div class=\"flex items-center gap-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-1\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-2 pb-2\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 활성 필터 배지 — cursor-default(JBadge)의 기본 상태 구분감 보정 */\n:deep(.cursor-default) {\n background-color: hsl(var(--muted) / 0.82) !important;\n border: 1px solid hsl(var(--border) / 0.9) !important;\n}\n:deep(.cursor-default) > button {\n background-color: hsl(var(--background) / 0.98);\n color: hsl(var(--foreground) / 0.9);\n}\n:deep(.cursor-default) > button:hover {\n background-color: hsl(var(--muted) / 1);\n color: hsl(var(--foreground));\n}\n\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.375rem 0.75rem;\n --label-w: 5rem; /* 필터 컨텍스트: 라벨 컴팩트 (80px) → 필드에 공간 확보 */\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),m=require("../molecules/JBreadcrumb.vue.cjs"),f=require("../molecules/JTitlebar.vue.cjs"),u=require("../../lib/utils.cjs"),C=e.defineComponent({__name:"JPageContainer",props:{breadcrumbItems:{default:()=>[]},showBreadcrumb:{type:Boolean,default:!0},title:{},icon:{},description:{},titlebarButtons:{},titlebarStyletype:{},showTitlebar:{type:Boolean,default:!0},styletype:{default:"default"},contentScroll:{type:Boolean,default:!0},class:{}},emits:["breadcrumbClick","titlebarButtonClick"],setup(t,{emit:s}){const n=t,o=s,r={default:{containerClass:"flex flex-col h-full w-full bg-background",contentClass:"flex-1 p-2 gap-2 bg-background text-foreground"},minimal:{containerClass:"flex flex-col h-full w-full bg-background",contentClass:"flex-1 p-1 gap-1 bg-background text-foreground"}},a=e.computed(()=>r[n.styletype]??r.default),i=e.computed(()=>u.cn(a.value.contentClass,n.contentScroll?"overflow-auto":"overflow-hidden")),d=(l,c)=>{o("breadcrumbClick",l,c)},b=l=>{o("titlebarButtonClick",l)};return(l,c)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(u.cn)(a.value.containerClass,n.class))},[t.showBreadcrumb&&t.breadcrumbItems&&t.breadcrumbItems.length>0?(e.openBlock(),e.createBlock(m.default,{key:0,items:t.breadcrumbItems,onItemClick:d},null,8,["items"])):e.createCommentVNode("",!0),t.showTitlebar?(e.openBlock(),e.createBlock(f.default,{key:1,title:t.title,icon:t.icon,description:t.description,buttons:t.titlebarButtons,styletype:t.titlebarStyletype,onButtonClick:b},{buttons:e.withCtx(()=>[e.renderSlot(l.$slots,"titlebar-buttons")]),_:3},8,["title","icon","description","buttons","styletype"])):e.createCommentVNode("",!0),e.createElementVNode("div",{class:e.normalizeClass(i.value)},[e.renderSlot(l.$slots,"default")],2)],2))}});exports.default=C;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),m=require("../molecules/JBreadcrumb.vue.cjs"),f=require("../molecules/JTitlebar.vue.cjs"),u=require("../../lib/utils.cjs"),C=e.defineComponent({__name:"JPageContainer",props:{breadcrumbItems:{default:()=>[]},showBreadcrumb:{type:Boolean,default:!0},title:{},icon:{},description:{},titlebarButtons:{},titlebarStyletype:{},showTitlebar:{type:Boolean,default:!0},styletype:{default:"default"},contentScroll:{type:Boolean,default:!0},class:{}},emits:["breadcrumbClick","titlebarButtonClick"],setup(t,{emit:s}){const n=t,o=s,r={default:{containerClass:"flex flex-col h-full w-full bg-background",contentClass:"flex-1 flex flex-col p-1.5 md:p-2 gap-2 bg-background text-foreground min-h-0"},minimal:{containerClass:"flex flex-col h-full w-full bg-background",contentClass:"flex-1 flex flex-col p-1 md:p-1.5 gap-1 bg-background text-foreground min-h-0"}},a=e.computed(()=>r[n.styletype]??r.default),i=e.computed(()=>u.cn(a.value.contentClass,n.contentScroll?"overflow-auto":"overflow-hidden")),d=(l,c)=>{o("breadcrumbClick",l,c)},b=l=>{o("titlebarButtonClick",l)};return(l,c)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(u.cn)(a.value.containerClass,n.class))},[t.showBreadcrumb&&t.breadcrumbItems&&t.breadcrumbItems.length>0?(e.openBlock(),e.createBlock(m.default,{key:0,items:t.breadcrumbItems,onItemClick:d},null,8,["items"])):e.createCommentVNode("",!0),t.showTitlebar?(e.openBlock(),e.createBlock(f.default,{key:1,title:t.title,icon:t.icon,description:t.description,buttons:t.titlebarButtons,styletype:t.titlebarStyletype,onButtonClick:b},{buttons:e.withCtx(()=>[e.renderSlot(l.$slots,"titlebar-buttons")]),_:3},8,["title","icon","description","buttons","styletype"])):e.createCommentVNode("",!0),e.createElementVNode("div",{class:e.normalizeClass(i.value)},[e.renderSlot(l.$slots,"default")],2)],2))}});exports.default=C;
2
2
  //# sourceMappingURL=JPageContainer.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JPageContainer.vue.cjs","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\nimport { cn } from '@/lib/utils'\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\n\n/**\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\n * Page Container Component\n * \n * @description\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\n * \n * @example\n * ```vue\n * <JPageContainer\n * :breadcrumb-items=\"breadcrumbItems\"\n * title=\"페이지 제목\"\n * :titlebar-buttons=\"buttons\"\n * >\n * <div>페이지 콘텐츠</div>\n * </JPageContainer>\n * ```\n */\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 브레드크럼 아이템 목록 */\n breadcrumbItems?: BreadcrumbItem[]\n /** 브레드크럼 표시 여부 */\n showBreadcrumb?: boolean\n /** JTitlebar의 모든 props 전달 */\n title?: string\n icon?: string\n description?: string\n titlebarButtons?: TitlebarButton[]\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\n /** JTitlebar 표시 여부 */\n showTitlebar?: boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n breadcrumbItems: () => [],\n showBreadcrumb: true,\n showTitlebar: true,\n styletype: 'default',\n contentScroll: true,\n }\n)\n\nconst emit = defineEmits<{\n /** 브레드크럼 아이템 클릭 이벤트 */\n breadcrumbClick: [item: BreadcrumbItem, index: number]\n /** 타이틀바 버튼 클릭 이벤트 */\n titlebarButtonClick: [button: TitlebarButton]\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-2 gap-2 bg-background text-foreground',\n },\n minimal: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-1 gap-1 bg-background text-foreground',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 콘텐츠 클래스 (스크롤 포함)\n */\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\n )\n})\n\n/**\n * 브레드크럼 아이템 클릭 핸들러\n */\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\n emit('breadcrumbClick', item, index)\n}\n\n/**\n * 타이틀바 버튼 클릭 핸들러\n */\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\n emit('titlebarButtonClick', button)\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 브레드크럼 -->\n <JBreadcrumb\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\n :items=\"breadcrumbItems\"\n @item-click=\"handleBreadcrumbClick\"\n />\n\n <!-- 제목 영역 (JTitlebar) -->\n <JTitlebar\n v-if=\"showTitlebar\"\n :title=\"title\"\n :icon=\"icon\"\n :description=\"description\"\n :buttons=\"titlebarButtons\"\n :styletype=\"titlebarStyletype\"\n @button-click=\"handleTitlebarButtonClick\"\n >\n <template #buttons>\n <slot name=\"titlebar-buttons\" />\n </template>\n </JTitlebar>\n\n <!-- 콘텐츠 영역 -->\n <div :class=\"contentClasses\">\n <slot />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":"6nBAgCA,MAAMA,EAAQC,EA8BRC,EAAOC,EAUPC,EAGD,CACH,QAAS,CACP,eAAgB,4CAChB,aAAc,gDAAA,EAEhB,QAAS,CACP,eAAgB,4CAChB,aAAc,gDAAA,CAChB,EAGIC,EAASC,EAAAA,SAAS,IACfF,EAAcJ,EAAM,SAAS,GAAKI,EAAc,OACxD,EAKKG,EAAiBD,EAAAA,SAAS,IACvBE,EAAAA,GACLH,EAAO,MAAM,aACbL,EAAM,cAAgB,gBAAkB,iBAAA,CAE3C,EAKKS,EAAwB,CAACC,EAAsBC,IAAkB,CACrET,EAAK,kBAAmBQ,EAAMC,CAAK,CACrC,EAKMC,EAA6BC,GAA2B,CAC5DX,EAAK,sBAAuBW,CAAM,CACpC,8BAIEC,EAAAA,mBA2BM,MAAA,CA3BA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGX,EAAA,MAAO,eAAgBL,EAAM,KAAK,CAAA,CAAA,GAGxCC,EAAA,gBAAkBA,EAAA,iBAAmBA,EAAA,gBAAgB,OAAM,iBADnEgB,EAAAA,YAIEC,EAAAA,QAAA,OAFC,MAAOjB,EAAA,gBACP,YAAYQ,CAAA,iDAKPR,EAAA,4BADRgB,EAAAA,YAYYE,EAAAA,QAAA,OAVT,MAAOlB,EAAA,MACP,KAAMA,EAAA,KACN,YAAaA,EAAA,YACb,QAASA,EAAA,gBACT,UAAWA,EAAA,kBACX,cAAcW,CAAA,GAEJ,kBACT,IAAgC,CAAhCQ,aAAgCC,EAAA,OAAA,kBAAA,CAAA,6FAKpCC,EAAAA,mBAEM,MAAA,CAFA,uBAAOf,EAAA,KAAc,CAAA,GACzBa,aAAQC,EAAA,OAAA,SAAA,CAAA"}
1
+ {"version":3,"file":"JPageContainer.vue.cjs","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\nimport { cn } from '@/lib/utils'\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\n\n/**\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\n * Page Container Component\n * \n * @description\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\n * \n * @example\n * ```vue\n * <JPageContainer\n * :breadcrumb-items=\"breadcrumbItems\"\n * title=\"페이지 제목\"\n * :titlebar-buttons=\"buttons\"\n * >\n * <div>페이지 콘텐츠</div>\n * </JPageContainer>\n * ```\n */\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 브레드크럼 아이템 목록 */\n breadcrumbItems?: BreadcrumbItem[]\n /** 브레드크럼 표시 여부 */\n showBreadcrumb?: boolean\n /** JTitlebar의 모든 props 전달 */\n title?: string\n icon?: string\n description?: string\n titlebarButtons?: TitlebarButton[]\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\n /** JTitlebar 표시 여부 */\n showTitlebar?: boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n breadcrumbItems: () => [],\n showBreadcrumb: true,\n showTitlebar: true,\n styletype: 'default',\n contentScroll: true,\n }\n)\n\nconst emit = defineEmits<{\n /** 브레드크럼 아이템 클릭 이벤트 */\n breadcrumbClick: [item: BreadcrumbItem, index: number]\n /** 타이틀바 버튼 클릭 이벤트 */\n titlebarButtonClick: [button: TitlebarButton]\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 flex flex-col p-1.5 md:p-2 gap-2 bg-background text-foreground min-h-0',\n },\n minimal: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 flex flex-col p-1 md:p-1.5 gap-1 bg-background text-foreground min-h-0',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 콘텐츠 클래스 (스크롤 포함)\n */\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\n )\n})\n\n/**\n * 브레드크럼 아이템 클릭 핸들러\n */\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\n emit('breadcrumbClick', item, index)\n}\n\n/**\n * 타이틀바 버튼 클릭 핸들러\n */\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\n emit('titlebarButtonClick', button)\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 브레드크럼 -->\n <JBreadcrumb\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\n :items=\"breadcrumbItems\"\n @item-click=\"handleBreadcrumbClick\"\n />\n\n <!-- 제목 영역 (JTitlebar) -->\n <JTitlebar\n v-if=\"showTitlebar\"\n :title=\"title\"\n :icon=\"icon\"\n :description=\"description\"\n :buttons=\"titlebarButtons\"\n :styletype=\"titlebarStyletype\"\n @button-click=\"handleTitlebarButtonClick\"\n >\n <template #buttons>\n <slot name=\"titlebar-buttons\" />\n </template>\n </JTitlebar>\n\n <!-- 콘텐츠 영역 -->\n <div :class=\"contentClasses\">\n <slot />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":"6nBAgCA,MAAMA,EAAQC,EA8BRC,EAAOC,EAUPC,EAGD,CACH,QAAS,CACP,eAAgB,4CAChB,aAAc,+EAAA,EAEhB,QAAS,CACP,eAAgB,4CAChB,aAAc,+EAAA,CAChB,EAGIC,EAASC,EAAAA,SAAS,IACfF,EAAcJ,EAAM,SAAS,GAAKI,EAAc,OACxD,EAKKG,EAAiBD,EAAAA,SAAS,IACvBE,EAAAA,GACLH,EAAO,MAAM,aACbL,EAAM,cAAgB,gBAAkB,iBAAA,CAE3C,EAKKS,EAAwB,CAACC,EAAsBC,IAAkB,CACrET,EAAK,kBAAmBQ,EAAMC,CAAK,CACrC,EAKMC,EAA6BC,GAA2B,CAC5DX,EAAK,sBAAuBW,CAAM,CACpC,8BAIEC,EAAAA,mBA2BM,MAAA,CA3BA,MAAKC,EAAAA,eAAEC,EAAAA,YAAGX,EAAA,MAAO,eAAgBL,EAAM,KAAK,CAAA,CAAA,GAGxCC,EAAA,gBAAkBA,EAAA,iBAAmBA,EAAA,gBAAgB,OAAM,iBADnEgB,EAAAA,YAIEC,EAAAA,QAAA,OAFC,MAAOjB,EAAA,gBACP,YAAYQ,CAAA,iDAKPR,EAAA,4BADRgB,EAAAA,YAYYE,EAAAA,QAAA,OAVT,MAAOlB,EAAA,MACP,KAAMA,EAAA,KACN,YAAaA,EAAA,YACb,QAASA,EAAA,gBACT,UAAWA,EAAA,kBACX,cAAcW,CAAA,GAEJ,kBACT,IAAgC,CAAhCQ,aAAgCC,EAAA,OAAA,kBAAA,CAAA,6FAKpCC,EAAAA,mBAEM,MAAA,CAFA,uBAAOf,EAAA,KAAc,CAAA,GACzBa,aAAQC,EAAA,OAAA,SAAA,CAAA"}
@@ -1,5 +1,5 @@
1
- import { defineComponent as B, computed as s, createElementBlock as g, openBlock as n, normalizeClass as i, unref as h, createBlock as u, createCommentVNode as d, createElementVNode as p, withCtx as w, renderSlot as m } from "vue";
2
- import x from "../molecules/JBreadcrumb.vue.js";
1
+ import { defineComponent as B, computed as i, createElementBlock as g, openBlock as n, normalizeClass as s, unref as h, createBlock as u, createCommentVNode as m, createElementVNode as p, withCtx as x, renderSlot as d } from "vue";
2
+ import w from "../molecules/JBreadcrumb.vue.js";
3
3
  import S from "../molecules/JTitlebar.vue.js";
4
4
  import { cn as b } from "../../lib/utils.js";
5
5
  const $ = /* @__PURE__ */ B({
@@ -22,13 +22,13 @@ const $ = /* @__PURE__ */ B({
22
22
  const l = t, o = f, a = {
23
23
  default: {
24
24
  containerClass: "flex flex-col h-full w-full bg-background",
25
- contentClass: "flex-1 p-2 gap-2 bg-background text-foreground"
25
+ contentClass: "flex-1 flex flex-col p-1.5 md:p-2 gap-2 bg-background text-foreground min-h-0"
26
26
  },
27
27
  minimal: {
28
28
  containerClass: "flex flex-col h-full w-full bg-background",
29
- contentClass: "flex-1 p-1 gap-1 bg-background text-foreground"
29
+ contentClass: "flex-1 flex flex-col p-1 md:p-1.5 gap-1 bg-background text-foreground min-h-0"
30
30
  }
31
- }, r = s(() => a[l.styletype] ?? a.default), C = s(() => b(
31
+ }, r = i(() => a[l.styletype] ?? a.default), C = i(() => b(
32
32
  r.value.contentClass,
33
33
  l.contentScroll ? "overflow-auto" : "overflow-hidden"
34
34
  )), k = (e, c) => {
@@ -37,13 +37,13 @@ const $ = /* @__PURE__ */ B({
37
37
  o("titlebarButtonClick", e);
38
38
  };
39
39
  return (e, c) => (n(), g("div", {
40
- class: i(h(b)(r.value.containerClass, l.class))
40
+ class: s(h(b)(r.value.containerClass, l.class))
41
41
  }, [
42
- t.showBreadcrumb && t.breadcrumbItems && t.breadcrumbItems.length > 0 ? (n(), u(x, {
42
+ t.showBreadcrumb && t.breadcrumbItems && t.breadcrumbItems.length > 0 ? (n(), u(w, {
43
43
  key: 0,
44
44
  items: t.breadcrumbItems,
45
45
  onItemClick: k
46
- }, null, 8, ["items"])) : d("", !0),
46
+ }, null, 8, ["items"])) : m("", !0),
47
47
  t.showTitlebar ? (n(), u(S, {
48
48
  key: 1,
49
49
  title: t.title,
@@ -53,15 +53,15 @@ const $ = /* @__PURE__ */ B({
53
53
  styletype: t.titlebarStyletype,
54
54
  onButtonClick: y
55
55
  }, {
56
- buttons: w(() => [
57
- m(e.$slots, "titlebar-buttons")
56
+ buttons: x(() => [
57
+ d(e.$slots, "titlebar-buttons")
58
58
  ]),
59
59
  _: 3
60
- }, 8, ["title", "icon", "description", "buttons", "styletype"])) : d("", !0),
60
+ }, 8, ["title", "icon", "description", "buttons", "styletype"])) : m("", !0),
61
61
  p("div", {
62
- class: i(C.value)
62
+ class: s(C.value)
63
63
  }, [
64
- m(e.$slots, "default")
64
+ d(e.$slots, "default")
65
65
  ], 2)
66
66
  ], 2));
67
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"JPageContainer.vue.js","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\nimport { cn } from '@/lib/utils'\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\n\n/**\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\n * Page Container Component\n * \n * @description\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\n * \n * @example\n * ```vue\n * <JPageContainer\n * :breadcrumb-items=\"breadcrumbItems\"\n * title=\"페이지 제목\"\n * :titlebar-buttons=\"buttons\"\n * >\n * <div>페이지 콘텐츠</div>\n * </JPageContainer>\n * ```\n */\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 브레드크럼 아이템 목록 */\n breadcrumbItems?: BreadcrumbItem[]\n /** 브레드크럼 표시 여부 */\n showBreadcrumb?: boolean\n /** JTitlebar의 모든 props 전달 */\n title?: string\n icon?: string\n description?: string\n titlebarButtons?: TitlebarButton[]\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\n /** JTitlebar 표시 여부 */\n showTitlebar?: boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n breadcrumbItems: () => [],\n showBreadcrumb: true,\n showTitlebar: true,\n styletype: 'default',\n contentScroll: true,\n }\n)\n\nconst emit = defineEmits<{\n /** 브레드크럼 아이템 클릭 이벤트 */\n breadcrumbClick: [item: BreadcrumbItem, index: number]\n /** 타이틀바 버튼 클릭 이벤트 */\n titlebarButtonClick: [button: TitlebarButton]\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-2 gap-2 bg-background text-foreground',\n },\n minimal: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 p-1 gap-1 bg-background text-foreground',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 콘텐츠 클래스 (스크롤 포함)\n */\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\n )\n})\n\n/**\n * 브레드크럼 아이템 클릭 핸들러\n */\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\n emit('breadcrumbClick', item, index)\n}\n\n/**\n * 타이틀바 버튼 클릭 핸들러\n */\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\n emit('titlebarButtonClick', button)\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 브레드크럼 -->\n <JBreadcrumb\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\n :items=\"breadcrumbItems\"\n @item-click=\"handleBreadcrumbClick\"\n />\n\n <!-- 제목 영역 (JTitlebar) -->\n <JTitlebar\n v-if=\"showTitlebar\"\n :title=\"title\"\n :icon=\"icon\"\n :description=\"description\"\n :buttons=\"titlebarButtons\"\n :styletype=\"titlebarStyletype\"\n @button-click=\"handleTitlebarButtonClick\"\n >\n <template #buttons>\n <slot name=\"titlebar-buttons\" />\n </template>\n </JTitlebar>\n\n <!-- 콘텐츠 영역 -->\n <div :class=\"contentClasses\">\n <slot />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,UAAMA,IAAQC,GA8BRC,IAAOC,GAUPC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,MAEhB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB,GAGIC,IAASC,EAAS,MACfF,EAAcJ,EAAM,SAAS,KAAKI,EAAc,OACxD,GAKKG,IAAiBD,EAAS,MACvBE;AAAA,MACLH,EAAO,MAAM;AAAA,MACbL,EAAM,gBAAgB,kBAAkB;AAAA,IAAA,CAE3C,GAKKS,IAAwB,CAACC,GAAsBC,MAAkB;AACrE,MAAAT,EAAK,mBAAmBQ,GAAMC,CAAK;AAAA,IACrC,GAKMC,IAA4B,CAACC,MAA2B;AAC5D,MAAAX,EAAK,uBAAuBW,CAAM;AAAA,IACpC;2BAIEC,EA2BM,OAAA;AAAA,MA3BA,OAAKC,EAAEC,KAAGX,EAAA,MAAO,gBAAgBL,EAAM,KAAK,CAAA;AAAA,IAAA;MAGxCC,EAAA,kBAAkBA,EAAA,mBAAmBA,EAAA,gBAAgB,SAAM,UADnEgB,EAIEC,GAAA;AAAA;QAFC,OAAOjB,EAAA;AAAA,QACP,aAAYQ;AAAA,MAAA;MAKPR,EAAA,qBADRgB,EAYYE,GAAA;AAAA;QAVT,OAAOlB,EAAA;AAAA,QACP,MAAMA,EAAA;AAAA,QACN,aAAaA,EAAA;AAAA,QACb,SAASA,EAAA;AAAA,QACT,WAAWA,EAAA;AAAA,QACX,eAAcW;AAAA,MAAA;QAEJ,WACT,MAAgC;AAAA,UAAhCQ,EAAgCC,EAAA,QAAA,kBAAA;AAAA,QAAA;;;MAKpCC,EAEM,OAAA;AAAA,QAFA,SAAOf,EAAA,KAAc;AAAA,MAAA;QACzBa,EAAQC,EAAA,QAAA,SAAA;AAAA,MAAA;;;;"}
1
+ {"version":3,"file":"JPageContainer.vue.js","sources":["../../../../src/components/organisms/JPageContainer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport JBreadcrumb from '@/components/molecules/JBreadcrumb.vue'\nimport JTitlebar from '@/components/molecules/JTitlebar.vue'\nimport { cn } from '@/lib/utils'\nimport type { BreadcrumbItem } from '@/components/molecules/JBreadcrumb.vue'\nimport type { TitlebarButton } from '@/components/molecules/JTitlebar.vue'\n\n/**\n * JPageContainer - 기본 페이지 컨테이너 컴포넌트 (organisms)\n * Page Container Component\n * \n * @description\n * 페이지의 기본 레이아웃을 담당하는 컨테이너 컴포넌트입니다.\n * 브레드크럼, 제목 영역(JTitlebar), 콘텐츠 영역을 포함합니다.\n * \n * @example\n * ```vue\n * <JPageContainer\n * :breadcrumb-items=\"breadcrumbItems\"\n * title=\"페이지 제목\"\n * :titlebar-buttons=\"buttons\"\n * >\n * <div>페이지 콘텐츠</div>\n * </JPageContainer>\n * ```\n */\n\ntype StyleType =\n | 'default' // 기본 스타일\n | 'minimal' // 최소 스타일\n\nconst props = withDefaults(\n defineProps<{\n /** 브레드크럼 아이템 목록 */\n breadcrumbItems?: BreadcrumbItem[]\n /** 브레드크럼 표시 여부 */\n showBreadcrumb?: boolean\n /** JTitlebar의 모든 props 전달 */\n title?: string\n icon?: string\n description?: string\n titlebarButtons?: TitlebarButton[]\n titlebarStyletype?: 'default' | 'primary' | 'accent' | 'neutral' | 'elevated'\n /** JTitlebar 표시 여부 */\n showTitlebar?: boolean\n /** 스타일 타입 */\n styletype?: StyleType\n /** 콘텐츠 영역 스크롤 가능 여부 */\n contentScroll?: boolean\n /** 추가 CSS 클래스 */\n class?: string\n }>(),\n {\n breadcrumbItems: () => [],\n showBreadcrumb: true,\n showTitlebar: true,\n styletype: 'default',\n contentScroll: true,\n }\n)\n\nconst emit = defineEmits<{\n /** 브레드크럼 아이템 클릭 이벤트 */\n breadcrumbClick: [item: BreadcrumbItem, index: number]\n /** 타이틀바 버튼 클릭 이벤트 */\n titlebarButtonClick: [button: TitlebarButton]\n}>()\n\n/**\n * 스타일 프리셋\n */\nconst STYLE_PRESETS: Record<StyleType, {\n containerClass: string\n contentClass: string\n}> = {\n default: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 flex flex-col p-1.5 md:p-2 gap-2 bg-background text-foreground min-h-0',\n },\n minimal: {\n containerClass: 'flex flex-col h-full w-full bg-background',\n contentClass: 'flex-1 flex flex-col p-1 md:p-1.5 gap-1 bg-background text-foreground min-h-0',\n },\n}\n\nconst preset = computed(() => {\n return STYLE_PRESETS[props.styletype] ?? STYLE_PRESETS.default\n})\n\n/**\n * 콘텐츠 클래스 (스크롤 포함)\n */\nconst contentClasses = computed(() => {\n return cn(\n preset.value.contentClass,\n props.contentScroll ? 'overflow-auto' : 'overflow-hidden'\n )\n})\n\n/**\n * 브레드크럼 아이템 클릭 핸들러\n */\nconst handleBreadcrumbClick = (item: BreadcrumbItem, index: number) => {\n emit('breadcrumbClick', item, index)\n}\n\n/**\n * 타이틀바 버튼 클릭 핸들러\n */\nconst handleTitlebarButtonClick = (button: TitlebarButton) => {\n emit('titlebarButtonClick', button)\n}\n</script>\n\n<template>\n <div :class=\"cn(preset.containerClass, props.class)\">\n <!-- 브레드크럼 -->\n <JBreadcrumb\n v-if=\"showBreadcrumb && breadcrumbItems && breadcrumbItems.length > 0\"\n :items=\"breadcrumbItems\"\n @item-click=\"handleBreadcrumbClick\"\n />\n\n <!-- 제목 영역 (JTitlebar) -->\n <JTitlebar\n v-if=\"showTitlebar\"\n :title=\"title\"\n :icon=\"icon\"\n :description=\"description\"\n :buttons=\"titlebarButtons\"\n :styletype=\"titlebarStyletype\"\n @button-click=\"handleTitlebarButtonClick\"\n >\n <template #buttons>\n <slot name=\"titlebar-buttons\" />\n </template>\n </JTitlebar>\n\n <!-- 콘텐츠 영역 -->\n <div :class=\"contentClasses\">\n <slot />\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","STYLE_PRESETS","preset","computed","contentClasses","cn","handleBreadcrumbClick","item","index","handleTitlebarButtonClick","button","_createElementBlock","_normalizeClass","_unref","_createBlock","JBreadcrumb","JTitlebar","_renderSlot","_ctx","_createElementVNode"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgCA,UAAMA,IAAQC,GA8BRC,IAAOC,GAUPC,IAGD;AAAA,MACH,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,MAEhB,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc;AAAA,MAAA;AAAA,IAChB,GAGIC,IAASC,EAAS,MACfF,EAAcJ,EAAM,SAAS,KAAKI,EAAc,OACxD,GAKKG,IAAiBD,EAAS,MACvBE;AAAA,MACLH,EAAO,MAAM;AAAA,MACbL,EAAM,gBAAgB,kBAAkB;AAAA,IAAA,CAE3C,GAKKS,IAAwB,CAACC,GAAsBC,MAAkB;AACrE,MAAAT,EAAK,mBAAmBQ,GAAMC,CAAK;AAAA,IACrC,GAKMC,IAA4B,CAACC,MAA2B;AAC5D,MAAAX,EAAK,uBAAuBW,CAAM;AAAA,IACpC;2BAIEC,EA2BM,OAAA;AAAA,MA3BA,OAAKC,EAAEC,KAAGX,EAAA,MAAO,gBAAgBL,EAAM,KAAK,CAAA;AAAA,IAAA;MAGxCC,EAAA,kBAAkBA,EAAA,mBAAmBA,EAAA,gBAAgB,SAAM,UADnEgB,EAIEC,GAAA;AAAA;QAFC,OAAOjB,EAAA;AAAA,QACP,aAAYQ;AAAA,MAAA;MAKPR,EAAA,qBADRgB,EAYYE,GAAA;AAAA;QAVT,OAAOlB,EAAA;AAAA,QACP,MAAMA,EAAA;AAAA,QACN,aAAaA,EAAA;AAAA,QACb,SAASA,EAAA;AAAA,QACT,WAAWA,EAAA;AAAA,QACX,eAAcW;AAAA,MAAA;QAEJ,WACT,MAAgC;AAAA,UAAhCQ,EAAgCC,EAAA,QAAA,kBAAA;AAAA,QAAA;;;MAKpCC,EAEM,OAAA;AAAA,QAFA,SAAOf,EAAA,KAAc;AAAA,MAAA;QACzBa,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"),r=require("../../lib/utils.cjs"),o=e.defineComponent({__name:"FieldDescription",props:{class:{}},setup(t){const n=t;return(a,l)=>(e.openBlock(),e.createElementBlock("p",{"data-slot":"field-description",class:e.normalizeClass(e.unref(r.cn)("text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance","last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5","[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",n.class))},[e.renderSlot(a.$slots,"default")],2))}});exports.default=o;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),r=require("../../lib/utils.cjs"),o=e.defineComponent({__name:"FieldDescription",props:{class:{}},setup(t){const n=t;return(a,l)=>(e.openBlock(),e.createElementBlock("p",{"data-slot":"field-description",class:e.normalizeClass(e.unref(r.cn)("text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance","last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5","[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",n.class))},[e.renderSlot(a.$slots,"default")],2))}});exports.default=o;
2
2
  //# sourceMappingURL=FieldDescription.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"FieldDescription.vue.cjs","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":"6OAIA,MAAMA,EAAQC,8BAMZC,EAAAA,mBAUI,IAAA,CATF,YAAU,oBACT,uBAAOC,EAAAA,MAAAC,IAAA,6PAA8RJ,EAAM,KAAA,KAO5SK,aAAQC,EAAA,OAAA,SAAA,CAAA"}
1
+ {"version":3,"file":"FieldDescription.vue.cjs","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":"6OAIA,MAAMA,EAAQC,8BAMZC,EAAAA,mBAUI,IAAA,CATF,YAAU,oBACT,uBAAOC,EAAAA,MAAAC,IAAA,6PAA8RJ,EAAM,KAAA,KAO5SK,aAAQC,EAAA,OAAA,SAAA,CAAA"}
@@ -10,7 +10,7 @@ const f = /* @__PURE__ */ n({
10
10
  return (a, m) => (r(), o("p", {
11
11
  "data-slot": "field-description",
12
12
  class: l(s(d)(
13
- "text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
13
+ "text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
14
14
  "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
15
15
  "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
16
16
  t.class
@@ -1 +1 @@
1
- {"version":3,"file":"FieldDescription.vue.js","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":";;;;;;;;AAIA,UAAMA,IAAQC;2BAMZC,EAUI,KAAA;AAAA,MATF,aAAU;AAAA,MACT,SAAOC,EAAAC,CAAA;AAAA;;;QAA8RJ,EAAM;AAAA,MAAA;;MAO5SK,EAAQC,EAAA,QAAA,SAAA;AAAA,IAAA;;;"}
1
+ {"version":3,"file":"FieldDescription.vue.js","sources":["../../../../src/components/shadcn/FieldDescription.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <p\r\n data-slot=\"field-description\"\r\n :class=\"cn(\r\n 'text-muted-foreground text-xs leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',\r\n 'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',\r\n '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\r\n props.class,\r\n )\"\r\n >\r\n <slot />\r\n </p>\r\n</template>\r\n"],"names":["props","__props","_createElementBlock","_unref","cn","_renderSlot","_ctx"],"mappings":";;;;;;;;AAIA,UAAMA,IAAQC;2BAMZC,EAUI,KAAA;AAAA,MATF,aAAU;AAAA,MACT,SAAOC,EAAAC,CAAA;AAAA;;;QAA8RJ,EAAM;AAAA,MAAA;;MAO5SK,EAAQC,EAAA,QAAA,SAAA;AAAA,IAAA;;;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),a=require("../../lib/utils.cjs"),c={key:2,class:"ml-4 flex list-disc flex-col gap-1"},u=e.defineComponent({__name:"FieldError",props:{class:{},errors:{}},setup(o){const r=o,t=e.computed(()=>!r.errors||r.errors.length===0?null:r.errors.length===1&&r.errors[0]?.message?r.errors[0].message:r.errors.some(l=>l?.message)?r.errors:null);return(l,i)=>l.$slots.default||t.value?(e.openBlock(),e.createElementBlock("div",{key:0,role:"alert","data-slot":"field-error",class:e.normalizeClass(e.unref(a.cn)("text-destructive text-sm font-normal",r.class))},[l.$slots.default?e.renderSlot(l.$slots,"default",{key:0}):typeof t.value=="string"?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createTextVNode(e.toDisplayString(t.value),1)],64)):Array.isArray(t.value)?(e.openBlock(),e.createElementBlock("ul",c,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value,(s,n)=>(e.openBlock(),e.createElementBlock("li",{key:n},e.toDisplayString(s?.message),1))),128))])):e.createCommentVNode("",!0)],2)):e.createCommentVNode("",!0)}});exports.default=u;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),a=require("../../lib/utils.cjs"),c={key:2,class:"ml-4 flex list-disc flex-col gap-1"},u=e.defineComponent({__name:"FieldError",props:{class:{},errors:{}},setup(o){const r=o,t=e.computed(()=>!r.errors||r.errors.length===0?null:r.errors.length===1&&r.errors[0]?.message?r.errors[0].message:r.errors.some(l=>l?.message)?r.errors:null);return(l,i)=>l.$slots.default||t.value?(e.openBlock(),e.createElementBlock("div",{key:0,role:"alert","data-slot":"field-error",class:e.normalizeClass(e.unref(a.cn)("text-destructive text-xs font-normal",r.class))},[l.$slots.default?e.renderSlot(l.$slots,"default",{key:0}):typeof t.value=="string"?(e.openBlock(),e.createElementBlock(e.Fragment,{key:1},[e.createTextVNode(e.toDisplayString(t.value),1)],64)):Array.isArray(t.value)?(e.openBlock(),e.createElementBlock("ul",c,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.value,(s,n)=>(e.openBlock(),e.createElementBlock("li",{key:n},e.toDisplayString(s?.message),1))),128))])):e.createCommentVNode("",!0)],2)):e.createCommentVNode("",!0)}});exports.default=u;
2
2
  //# sourceMappingURL=FieldError.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"FieldError.vue.cjs","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-sm font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":"sSAKA,MAAMA,EAAQC,EAKRC,EAAUC,EAAAA,SAAS,IACnB,CAACH,EAAM,QAAUA,EAAM,OAAO,SAAW,EACpC,KAELA,EAAM,OAAO,SAAW,GAAKA,EAAM,OAAO,CAAC,GAAG,QACzCA,EAAM,OAAO,CAAC,EAAE,QAGlBA,EAAM,OAAO,KAAKI,GAAKA,GAAG,OAAO,EACpCJ,EAAM,OACN,IACL,eAKSK,EAAAA,OAAO,SAAWH,EAAA,qBAD1BI,EAAAA,mBAiBM,MAAA,OAfJ,KAAK,QACL,YAAU,cACT,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,uCAAyCT,EAAM,KAAK,CAAA,CAAA,GAElDK,EAAAA,OAAO,QAAnBK,aAA8BC,EAAA,OAAA,UAAA,CAAA,IAAA,CAAA,CAAA,SAEFT,EAAA,OAAO,wBAAnCI,EAAAA,mBAEWM,WAAA,CAAA,IAAA,GAAA,qCADNV,EAAA,KAAO,EAAA,CAAA,CAAA,OAGG,MAAM,QAAQA,EAAA,KAAO,GAApCW,YAAA,EAAAP,qBAIK,KAJLQ,EAIK,EAHHD,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAEKM,WAAA,KAAAG,EAAAA,WAFwBb,EAAA,MAAO,CAAxBc,EAAOC,KAAnBJ,YAAA,EAAAP,qBAEK,MAFkC,IAAKW,CAAA,EAAKC,kBAC5CF,GAAO,OAAO,EAAA,CAAA"}
1
+ {"version":3,"file":"FieldError.vue.cjs","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-xs font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":"sSAKA,MAAMA,EAAQC,EAKRC,EAAUC,EAAAA,SAAS,IACnB,CAACH,EAAM,QAAUA,EAAM,OAAO,SAAW,EACpC,KAELA,EAAM,OAAO,SAAW,GAAKA,EAAM,OAAO,CAAC,GAAG,QACzCA,EAAM,OAAO,CAAC,EAAE,QAGlBA,EAAM,OAAO,KAAKI,GAAKA,GAAG,OAAO,EACpCJ,EAAM,OACN,IACL,eAKSK,EAAAA,OAAO,SAAWH,EAAA,qBAD1BI,EAAAA,mBAiBM,MAAA,OAfJ,KAAK,QACL,YAAU,cACT,MAAKC,EAAAA,eAAEC,QAAAC,EAAAA,EAAA,EAAE,uCAAyCT,EAAM,KAAK,CAAA,CAAA,GAElDK,EAAAA,OAAO,QAAnBK,aAA8BC,EAAA,OAAA,UAAA,CAAA,IAAA,CAAA,CAAA,SAEFT,EAAA,OAAO,wBAAnCI,EAAAA,mBAEWM,WAAA,CAAA,IAAA,GAAA,qCADNV,EAAA,KAAO,EAAA,CAAA,CAAA,OAGG,MAAM,QAAQA,EAAA,KAAO,GAApCW,YAAA,EAAAP,qBAIK,KAJLQ,EAIK,EAHHD,EAAAA,UAAA,EAAA,EAAAP,EAAAA,mBAEKM,WAAA,KAAAG,EAAAA,WAFwBb,EAAA,MAAO,CAAxBc,EAAOC,KAAnBJ,YAAA,EAAAP,qBAEK,MAFkC,IAAKW,CAAA,EAAKC,kBAC5CF,GAAO,OAAO,EAAA,CAAA"}
@@ -1,9 +1,9 @@
1
- import { defineComponent as i, computed as d, createElementBlock as s, createCommentVNode as l, openBlock as o, normalizeClass as f, unref as p, renderSlot as g, Fragment as a, createTextVNode as y, toDisplayString as n, renderList as k } from "vue";
1
+ import { defineComponent as m, computed as d, createElementBlock as s, createCommentVNode as l, openBlock as o, normalizeClass as f, unref as p, renderSlot as g, Fragment as a, createTextVNode as y, toDisplayString as n, renderList as k } from "vue";
2
2
  import { cn as v } from "../../lib/utils.js";
3
- const _ = {
3
+ const x = {
4
4
  key: 2,
5
5
  class: "ml-4 flex list-disc flex-col gap-1"
6
- }, $ = /* @__PURE__ */ i({
6
+ }, $ = /* @__PURE__ */ m({
7
7
  __name: "FieldError",
8
8
  props: {
9
9
  class: {},
@@ -11,16 +11,16 @@ const _ = {
11
11
  },
12
12
  setup(u) {
13
13
  const e = u, r = d(() => !e.errors || e.errors.length === 0 ? null : e.errors.length === 1 && e.errors[0]?.message ? e.errors[0].message : e.errors.some((t) => t?.message) ? e.errors : null);
14
- return (t, h) => t.$slots.default || r.value ? (o(), s("div", {
14
+ return (t, _) => t.$slots.default || r.value ? (o(), s("div", {
15
15
  key: 0,
16
16
  role: "alert",
17
17
  "data-slot": "field-error",
18
- class: f(p(v)("text-destructive text-sm font-normal", e.class))
18
+ class: f(p(v)("text-destructive text-xs font-normal", e.class))
19
19
  }, [
20
20
  t.$slots.default ? g(t.$slots, "default", { key: 0 }) : typeof r.value == "string" ? (o(), s(a, { key: 1 }, [
21
21
  y(n(r.value), 1)
22
- ], 64)) : Array.isArray(r.value) ? (o(), s("ul", _, [
23
- (o(!0), s(a, null, k(r.value, (m, c) => (o(), s("li", { key: c }, n(m?.message), 1))), 128))
22
+ ], 64)) : Array.isArray(r.value) ? (o(), s("ul", x, [
23
+ (o(!0), s(a, null, k(r.value, (c, i) => (o(), s("li", { key: i }, n(c?.message), 1))), 128))
24
24
  ])) : l("", !0)
25
25
  ], 2)) : l("", !0);
26
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FieldError.vue.js","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-sm font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":";;;;;;;;;;;;AAKA,UAAMA,IAAQC,GAKRC,IAAUC,EAAS,MACnB,CAACH,EAAM,UAAUA,EAAM,OAAO,WAAW,IACpC,OAELA,EAAM,OAAO,WAAW,KAAKA,EAAM,OAAO,CAAC,GAAG,UACzCA,EAAM,OAAO,CAAC,EAAE,UAGlBA,EAAM,OAAO,KAAK,CAAAI,MAAKA,GAAG,OAAO,IACpCJ,EAAM,SACN,IACL;qBAKSK,EAAAA,OAAO,WAAWH,EAAA,cAD1BI,EAiBM,OAAA;AAAA;MAfJ,MAAK;AAAA,MACL,aAAU;AAAA,MACT,OAAKC,EAAEC,EAAAC,CAAA,EAAE,wCAAyCT,EAAM,KAAK,CAAA;AAAA,IAAA;MAElDK,EAAAA,OAAO,UAAnBK,EAA8BC,EAAA,QAAA,WAAA,EAAA,KAAA,EAAA,CAAA,WAEFT,EAAA,SAAO,iBAAnCI,EAEWM,GAAA,EAAA,KAAA,KAAA;AAAA,YADNV,EAAA,KAAO,GAAA,CAAA;AAAA,MAAA,UAGG,MAAM,QAAQA,EAAA,KAAO,KAApCW,EAAA,GAAAP,EAIK,MAJLQ,GAIK;AAAA,SAHHD,EAAA,EAAA,GAAAP,EAEKM,GAAA,MAAAG,EAFwBb,EAAA,OAAO,CAAxBc,GAAOC,OAAnBJ,EAAA,GAAAP,EAEK,QAFkC,KAAKW,EAAA,GAAKC,EAC5CF,GAAO,OAAO,GAAA,CAAA;;;;;"}
1
+ {"version":3,"file":"FieldError.vue.js","sources":["../../../../src/components/shadcn/FieldError.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\"\r\nimport { computed } from \"vue\"\r\nimport { cn } from \"@/lib/utils\"\r\n\r\nconst props = defineProps<{\r\n class?: HTMLAttributes[\"class\"]\r\n errors?: Array<{ message?: string } | undefined>\r\n}>()\r\n\r\nconst content = computed(() => {\r\n if (!props.errors || props.errors.length === 0)\r\n return null\r\n\r\n if (props.errors.length === 1 && props.errors[0]?.message) {\r\n return props.errors[0].message\r\n }\r\n\r\n return props.errors.some(e => e?.message)\r\n ? props.errors\r\n : null\r\n})\r\n</script>\r\n\r\n<template>\r\n <div\r\n v-if=\"$slots.default || content\"\r\n role=\"alert\"\r\n data-slot=\"field-error\"\r\n :class=\"cn('text-destructive text-xs font-normal', props.class)\"\r\n >\r\n <slot v-if=\"$slots.default\" />\r\n\r\n <template v-else-if=\"typeof content === 'string'\">\r\n {{ content }}\r\n </template>\r\n\r\n <ul v-else-if=\"Array.isArray(content)\" class=\"ml-4 flex list-disc flex-col gap-1\">\r\n <li v-for=\"(error, index) in content\" :key=\"index\">\r\n {{ error?.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","content","computed","e","$slots","_createElementBlock","_normalizeClass","_unref","cn","_renderSlot","_ctx","_Fragment","_openBlock","_hoisted_1","_renderList","error","index","_toDisplayString"],"mappings":";;;;;;;;;;;;AAKA,UAAMA,IAAQC,GAKRC,IAAUC,EAAS,MACnB,CAACH,EAAM,UAAUA,EAAM,OAAO,WAAW,IACpC,OAELA,EAAM,OAAO,WAAW,KAAKA,EAAM,OAAO,CAAC,GAAG,UACzCA,EAAM,OAAO,CAAC,EAAE,UAGlBA,EAAM,OAAO,KAAK,CAAAI,MAAKA,GAAG,OAAO,IACpCJ,EAAM,SACN,IACL;qBAKSK,EAAAA,OAAO,WAAWH,EAAA,cAD1BI,EAiBM,OAAA;AAAA;MAfJ,MAAK;AAAA,MACL,aAAU;AAAA,MACT,OAAKC,EAAEC,EAAAC,CAAA,EAAE,wCAAyCT,EAAM,KAAK,CAAA;AAAA,IAAA;MAElDK,EAAAA,OAAO,UAAnBK,EAA8BC,EAAA,QAAA,WAAA,EAAA,KAAA,EAAA,CAAA,WAEFT,EAAA,SAAO,iBAAnCI,EAEWM,GAAA,EAAA,KAAA,KAAA;AAAA,YADNV,EAAA,KAAO,GAAA,CAAA;AAAA,MAAA,UAGG,MAAM,QAAQA,EAAA,KAAO,KAApCW,EAAA,GAAAP,EAIK,MAJLQ,GAIK;AAAA,SAHHD,EAAA,EAAA,GAAAP,EAEKM,GAAA,MAAAG,EAFwBb,EAAA,OAAO,CAAxBc,GAAOC,OAAnBJ,EAAA,GAAAP,EAEK,QAFkC,KAAKW,EAAA,GAAKC,EAC5CF,GAAO,OAAO,GAAA,CAAA;;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@j-solution/components",
3
3
  "description": "Vue 3 Atomic Design component kit for enterprise dashboards",
4
- "version": "2.0.2",
4
+ "version": "2.0.4",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
7
7
  "module": "./index.js",
package/types/index.d.ts CHANGED
@@ -731,6 +731,10 @@ declare type __VLS_Props_26 = {
731
731
  multiple?: boolean;
732
732
  /** Radio 옵션 나열 방향 */
733
733
  radioDirection?: 'horizontal' | 'vertical';
734
+ /** 에디터 높이 (기본값: '300px') */
735
+ editorHeight?: string | number;
736
+ /** 에디터가 남은 세로 공간을 모두 채우도록 확장 (type='editor'일 때만 적용) */
737
+ fillHeight?: boolean;
734
738
  };
735
739
 
736
740
  declare type __VLS_Props_27 = {
@@ -757,6 +761,8 @@ declare type __VLS_Props_28 = {
757
761
  footer?: string;
758
762
  /** 카드 variant (패턴 4) */
759
763
  variant?: CardVariant;
764
+ /** CardContent가 남은 세로 공간을 채우도록 flex column 적용 */
765
+ fillContent?: boolean;
760
766
  };
761
767
 
762
768
  declare type __VLS_Props_29 = {
@@ -1703,7 +1709,7 @@ declare interface ComboboxOption {
1703
1709
  label: string;
1704
1710
  }
1705
1711
 
1706
- declare type ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker';
1712
+ declare type ComponentType = 'input' | 'textarea' | 'checkbox' | 'switch' | 'combo' | 'radio' | 'searchCombo' | 'datepicker' | 'editor';
1707
1713
 
1708
1714
  /**
1709
1715
  * Context Menu Group 타입
@@ -2548,11 +2554,13 @@ clearError: () => void;
2548
2554
  change: (value: any) => any;
2549
2555
  focus: (event: FocusEvent) => any;
2550
2556
  blur: (event: FocusEvent) => any;
2557
+ save: (value: string) => any;
2551
2558
  }, string, PublicProps, Readonly<__VLS_Props_26> & Readonly<{
2552
2559
  "onUpdate:modelValue"?: ((value: any) => any) | undefined;
2553
2560
  onChange?: ((value: any) => any) | undefined;
2554
2561
  onFocus?: ((event: FocusEvent) => any) | undefined;
2555
2562
  onBlur?: ((event: FocusEvent) => any) | undefined;
2563
+ onSave?: ((value: string) => any) | undefined;
2556
2564
  }>, {
2557
2565
  orientation: "vertical" | "horizontal" | "responsive";
2558
2566
  type: ComponentType;