@j-solution/components 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +6 -14
  2. package/USAGE_GUIDE.md +772 -1480
  3. package/assets/jwms-portal-frontend-Cs1trVbC.css +1 -0
  4. package/assets/styles/j-components.css +1 -1
  5. package/components/atoms/JEditor.vue.cjs +1 -1
  6. package/components/atoms/JEditor.vue.js +2 -2
  7. package/components/atoms/JEditor.vue2.cjs +1 -1
  8. package/components/atoms/JEditor.vue2.cjs.map +1 -1
  9. package/components/atoms/JEditor.vue2.js +1 -0
  10. package/components/atoms/JEditor.vue2.js.map +1 -1
  11. package/components/atoms/JPreview.vue.cjs +1 -1
  12. package/components/atoms/JPreview.vue.js +1 -1
  13. package/components/atoms/JPreview.vue2.cjs +1 -1
  14. package/components/atoms/JPreview.vue2.cjs.map +1 -1
  15. package/components/atoms/JPreview.vue2.js +2 -1
  16. package/components/atoms/JPreview.vue2.js.map +1 -1
  17. package/components/molecules/JFormField.vue.cjs +1 -1
  18. package/components/molecules/JFormField.vue.cjs.map +1 -1
  19. package/components/molecules/JFormField.vue.js +1 -1
  20. package/components/molecules/JFormField.vue.js.map +1 -1
  21. package/components/organisms/JFilterBar.vue.cjs +2 -0
  22. package/components/organisms/JFilterBar.vue.cjs.map +1 -0
  23. package/components/organisms/JFilterBar.vue.js +51 -0
  24. package/components/organisms/JFilterBar.vue.js.map +1 -0
  25. package/components/organisms/JFilterBar.vue2.cjs +2 -0
  26. package/components/organisms/JFilterBar.vue2.cjs.map +1 -0
  27. package/components/organisms/JFilterBar.vue2.js +5 -0
  28. package/components/organisms/JFilterBar.vue2.js.map +1 -0
  29. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs +1 -1
  30. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs.map +1 -1
  31. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js +45 -40
  32. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
  33. package/components/organisms/JSidebarSimple.vue.cjs +1 -1
  34. package/components/organisms/JSidebarSimple.vue.js +2 -2
  35. package/components/organisms/JSidebarSimple.vue2.cjs +1 -1
  36. package/components/organisms/JSidebarSimple.vue2.cjs.map +1 -1
  37. package/components/organisms/JSidebarSimple.vue2.js +40 -70
  38. package/components/organisms/JSidebarSimple.vue2.js.map +1 -1
  39. package/components/organisms/JTree.vue.cjs +2 -0
  40. package/components/organisms/JTree.vue.cjs.map +1 -0
  41. package/components/organisms/JTree.vue.js +83 -0
  42. package/components/organisms/JTree.vue.js.map +1 -0
  43. package/components/organisms/JTree.vue2.cjs +2 -0
  44. package/components/organisms/JTree.vue2.cjs.map +1 -0
  45. package/components/organisms/JTree.vue2.js +5 -0
  46. package/components/organisms/JTree.vue2.js.map +1 -0
  47. package/index.cjs +1 -1
  48. package/index.js +37 -33
  49. package/lib/menu-utils.cjs +2 -0
  50. package/lib/menu-utils.cjs.map +1 -0
  51. package/lib/menu-utils.js +33 -0
  52. package/lib/menu-utils.js.map +1 -0
  53. package/package.json +1 -1
  54. package/types/index.d.ts +156 -34
  55. package/assets/jwms-portal-frontend-D8DdrheA.css +0 -1
package/USAGE_GUIDE.md CHANGED
@@ -1,1755 +1,1047 @@
1
- # J-Component 라이브러리 사용 가이드
1
+ # J-Component 사용 가이드
2
2
 
3
- > Vue.js 3 기반 Atomic Design 패턴 컴포넌트 라이브러리 사용 가이드
3
+ > Claude Code를 이용한 프론트엔드 개발 참조하는 Props 기반 컴포넌트 사용 가이드
4
4
 
5
- ## 📋 목차
6
-
7
- 1. [프로젝트 개요](#프로젝트-개요)
8
- 2. [컴포넌트 목록](#컴포넌트-목록)
9
- 3. [컴포넌트 배치 가이드](#컴포넌트-배치-가이드)
10
- 4. [컴포넌트 활용 가이드](#컴포넌트-활용-가이드)
11
- 5. [기본 사용 규칙](#기본-사용-규칙)
12
- 6. [고급 사용법](#고급-사용법)
13
- - [JDynamicTabs - 경로 기반 컴포넌트 로딩](#jdynamictabs---경로-기반-컴포넌트-로딩)
14
- 7. [레이아웃 템플릿 가이드](#레이아웃-템플릿-가이드)
15
-
16
- ---
17
-
18
- ## 프로젝트 개요
19
-
20
- J-Component는 Vue.js 3와 TypeScript를 기반으로 한 재사용 가능한 UI 컴포넌트 라이브러리입니다.
21
-
22
- ### 핵심 특징
23
-
24
- - **Atomic Design 패턴**: Atoms → Molecules → Organisms → Templates 계층 구조
25
- - **TypeScript 완전 지원**: 타입 안정성 보장
26
- - **shadcn/ui 기반**: 일관된 디자인 시스템
27
- - **TailwindCSS**: 유틸리티 기반 스타일링
28
- - **다크모드 지원**: 라이트/다크 모드 자동 전환
29
- - **CSS 자동 포함** (NPM 패키지): 컴포넌트만 import하면 스타일이 자동으로 적용됩니다
30
-
31
- ### 기술 스택
32
-
33
- - Vue.js 3.5
34
- - TypeScript 5.9
35
- - TailwindCSS 3.4
36
- - shadcn/ui (radix-vue 기반)
37
-
38
- ### 설치 및 사용
39
-
40
- #### NPM 패키지 사용 (권장)
5
+ ## 설치 및 Import
41
6
 
42
7
  ```ts
43
- import { JButton } from '@j-solution/components'
44
- // CSS는 자동으로 포함됨 - 별도 import 불필요
45
- ```
8
+ // NPM 패키지 (권장) - CSS 자동 포함
9
+ import { JButton, JInput, JGrid } from '@j-solution/components'
46
10
 
47
- > 💡 **참고**: NPM 패키지는 패키지 진입점에서 CSS가 자동으로 import되므로, 컴포넌트만 import하면 스타일이 자동으로 적용됩니다.
48
-
49
- #### Standard 방식 사용 (파일 복사)
50
-
51
- ```ts
52
- import { JButton } from '@/components'
53
- // themes.css를 별도로 import해야 함 (INSTALLATION_GUIDE.md 참고)
11
+ // Standard 방식 (파일 복사)
12
+ import { JButton, JInput, JGrid } from '@/components'
54
13
  ```
55
14
 
56
- > 📖 **설치 가이드**: 자세한 설치 방법은 [INSTALLATION_GUIDE.md](./INSTALLATION_GUIDE.md)를 참고하세요.
57
-
58
15
  ---
59
16
 
60
17
  ## 컴포넌트 목록
61
18
 
62
- ### Atoms (원자) - 25개
63
-
64
- 기본적인 UI 요소들로, 이상 분해할 수 없는 최소 단위의 컴포넌트입니다.
65
-
66
- | 컴포넌트 | 설명 | 주요 용도 |
67
- |---------|------|----------|
68
- | **JAvatar** | 사용자 아바타 | 프로필 이미지 표시 |
69
- | **JBadge** | 상태 표시 배지 | 상태, 카테고리 표시 |
70
- | **JButton** | 버튼 | 액션 실행, 폼 제출 |
71
- | **JCheckbox** | 체크박스 | 다중 선택 |
72
- | **JCombo** | 드롭다운 선택 | 단일/다중 선택 |
73
- | **JDatepicker** | 날짜 선택기 | 날짜 입력 |
74
- | **JDivider** | 구분선 | 콘텐츠 구분 |
75
- | **JEditor** | 마크다운 에디터 | 마크다운 문서 작성 |
76
- | **JGrid** | 데이터 그리드 | 테이블 데이터 표시 (AG Grid 기반) |
77
- | **JIcon** | 아이콘 | 시각적 표시 |
78
- | **JImage** | 이미지 | 이미지 표시 |
79
- | **JInput** | 텍스트 입력 필드 | 단일 라인 텍스트 입력 |
80
- | **JKbd** | 키보드 단축키 표시 | 단축키 안내 |
81
- | **JLabel** | 라벨 | 폼 필드 라벨 |
82
- | **JLink** | 링크 | 네비게이션 |
83
- | **JPopover** | 팝오버 | 추가 정보 표시 |
84
- | **JPreview** | 마크다운/HTML 뷰어 | 마크다운 또는 HTML 문서 표시 |
85
- | **JProgress** | 진행률 표시 | 작업 진행 상태 |
86
- | **JRadio** | 라디오 버튼 | 단일 선택 |
87
- | **JSearchCombo** | 검색 가능한 드롭다운 | 많은 옵션 중 검색 선택 |
88
- | **JSpinner** | 로딩 스피너 | 로딩 상태 표시 |
89
- | **JSwitch** | 토글 스위치 | ON/OFF 설정 |
90
- | **JTextarea** | 멀티라인 텍스트 입력 | 여러 라인 텍스트 입력 |
91
- | **JToaster** | Toast 알림 컨테이너 | 전역 알림 메시지 표시 |
92
- | **JTooltip** | 툴팁 | 추가 설명 표시 |
93
-
94
- ### Molecules (분자) - 11개
95
-
96
- 여러 Atoms를 조합하여 만든 더 복잡한 컴포넌트입니다.
97
-
98
- | 컴포넌트 | 설명 | 주요 용도 |
99
- |---------|------|----------|
100
- | **JAccordion** | 접을 있는 콘텐츠 섹션 | FAQ, 상세 정보 접기/펼치기 |
101
- | **JAlert** | 알림 메시지 표시 | 성공/경고/에러 메시지 |
102
- | **JBreadcrumb** | 페이지 네비게이션 경로 | 현재 위치 표시 |
103
- | **JButtonGroup** | 버튼 그룹 | 관련 버튼 묶기 |
104
- | **JCard** | 카드 컴포넌트 | 콘텐츠 그룹화 |
105
- | **JContextMenu** | 우클릭 컨텍스트 메뉴 | 우클릭 메뉴 |
106
- | **JFormField** | 필드 래퍼 | 라벨, 에러 메시지 포함 폼 필드 |
107
- | **JGroupCombo** | 그룹화된 드롭다운 | 카테고리별 선택 |
108
- | **JSearchAddr** | 주소 검색 | Daum Postcode API 활용 |
109
- | **JTabs** | 탭 UI | 콘텐츠 탭 전환 |
110
- | **JTitlebar** | 타이틀바 | 페이지/섹션 제목 및 액션 |
111
-
112
- ### Organisms (유기체) - 9개
113
-
114
- Molecules와 Atoms를 조합하여 만든 완전한 기능을 가진 컴포넌트입니다.
115
-
116
- | 컴포넌트 | 설명 | 주요 용도 |
117
- |---------|------|----------|
118
- | **JDynamicForm** | 스키마 기반 동적 폼 | JSON 스키마로 폼 자동 생성 |
119
- | **JDynamicTabs** | 동적 관리 | 런타임에 탭 추가/제거 |
120
- | **JFormModal** | 모달 | JDynamicForm 기반 모달 폼 |
121
- | **JHeader** | 애플리케이션 헤더 | 상단 헤더 영역 |
122
- | **JModal** | 모달 다이얼로그 | 다이얼로그 표시 |
123
- | **JPageContainer** | 페이지 컨테이너 | 페이지 기본 레이아웃 |
124
- | **JSearchPanel** | 검색 조건 패널 | JDynamicForm 기반 검색 UI |
125
- | **JSidebarAdvanced** | 고급 사이드바 | 검색, 즐겨찾기, 다단계 메뉴 |
126
- | **JSidebarSimple** | 간단한 사이드바 | 다단계 메뉴, 검색 |
127
-
128
- ### Templates (템플릿) - 3개
129
-
130
- Organisms를 조합하여 만든 페이지 레이아웃 구조입니다.
131
-
132
- | 컴포넌트 | 설명 | 주요 용도 |
133
- |---------|------|----------|
134
- | **JLayout** | 기본 레이아웃 | 커스텀 레이아웃 구성 |
135
- | **JLayoutAdvanced** | 고급 레이아웃 | JHeader + JSidebarAdvanced + JDynamicTabs |
136
- | **JLayoutSimple** | 간단한 레이아웃 | JHeader + JSidebarSimple + JPageContainer |
19
+ ### Atoms (26개)
20
+
21
+ | 컴포넌트 | 설명 |
22
+ |---------|------|
23
+ | JAvatar | 사용자 아바타 |
24
+ | JBadge | 상태 표시 배지 |
25
+ | JButton | 버튼 |
26
+ | JCheckbox | 체크박스 (Y/N 문자열) |
27
+ | JCombo | 드롭다운 선택 |
28
+ | JDatepicker | 날짜 선택기 |
29
+ | JDivider | 구분선 |
30
+ | JEditor | 마크다운 에디터 |
31
+ | JGrid | 데이터 그리드 (AG Grid) |
32
+ | JIcon | 아이콘 (Lucide) |
33
+ | JImage | 이미지 |
34
+ | JInput | 텍스트 입력 |
35
+ | JKbd | 키보드 단축키 표시 |
36
+ | JLabel | 라벨 |
37
+ | JLink | 링크 |
38
+ | JPopover | 팝오버 |
39
+ | JPreview | 마크다운/HTML 뷰어 |
40
+ | JProgress | 진행률 표시 |
41
+ | JRadio | 라디오 버튼 (options 배열) |
42
+ | JSearchCombo | 검색 가능한 드롭다운 |
43
+ | JSpinner | 로딩 스피너 |
44
+ | JSwitch | 토글 스위치 (Y/N 문자열) |
45
+ | JTextarea | 멀티라인 텍스트 입력 |
46
+ | JToaster | Toast 알림 컨테이너 |
47
+ | JTooltip | 툴팁 |
48
+ | JTree | 트리 |
49
+
50
+ ### Molecules (11개)
51
+
52
+ | 컴포넌트 | 설명 |
53
+ |---------|------|
54
+ | JAccordion | 접기/펼치기 섹션 |
55
+ | JAlert | 알림 메시지 |
56
+ | JBreadcrumb | 경로 탐색 |
57
+ | JButtonGroup | 버튼 그룹 |
58
+ | JCard | 카드 |
59
+ | JContextMenu | 우클릭 메뉴 |
60
+ | JFormField | 필드 래퍼 (label + error + 입력) |
61
+ | JGroupCombo | 그룹화된 드롭다운 |
62
+ | JSearchAddr | 주소 검색 (Daum API) |
63
+ | JTabs | UI |
64
+ | JTitlebar | 타이틀바 |
65
+
66
+ ### Organisms (9개)
67
+
68
+ | 컴포넌트 | 설명 |
69
+ |---------|------|
70
+ | JDynamicForm | 스키마 기반 동적 폼 |
71
+ | JDynamicTabs | 동적 관리 |
72
+ | JFormModal | 폼 모달 (JDynamicForm 기반) |
73
+ | JHeader | 애플리케이션 헤더 |
74
+ | JModal | 모달 다이얼로그 |
75
+ | JPageContainer | 페이지 컨테이너 |
76
+ | JSearchPanel | 검색 조건 패널 |
77
+ | JSidebarAdvanced | 고급 사이드바 |
78
+ | JSidebarSimple | 간단한 사이드바 |
79
+
80
+ ### Templates (3개)
81
+
82
+ | 컴포넌트 | 설명 |
83
+ |---------|------|
84
+ | JLayout | 기본 레이아웃 (커스텀) |
85
+ | JLayoutAdvanced | 고급 레이아웃 (탭 기반) |
86
+ | JLayoutSimple | 간단한 레이아웃 (단일 페이지) |
137
87
 
138
88
  ---
139
89
 
140
- ## 컴포넌트 배치 가이드
141
-
142
- ### Atomic Design 계층 구조
143
-
144
- 컴포넌트는 다음 계층 구조를 따릅니다:
145
-
146
- ```
147
- Templates (템플릿)
148
- └── Organisms (유기체)
149
- └── Molecules (분자)
150
- └── Atoms (원자)
151
- ```
152
-
153
- ### 배치 원칙
154
-
155
- 1. **하위 계층 우선 사용**
156
- - 상위 계층에 해당 컴포넌트가 없을 때에 한해서, 가능한 한 가장 낮은 계층의 컴포넌트를 사용하세요
157
- - 예: 단순 버튼은 `JButton` (Atoms) 사용, `JButtonGroup` (Molecules)은 여러 버튼을 묶을 때만 사용
158
-
159
- 2. **조합 우선**
160
- - 기존 컴포넌트로 원하는 기능을 구현할 수 있는 경우, 새로운 컴포넌트를 만들기보다 기존 컴포넌트를 조합하여 사용하세요
161
- - 예: `JCard` + `JButton` + `JInput` 조합으로 카드 형태의 폼 구성
90
+ ## 공통 Props
162
91
 
163
- 3. **템플릿 활용**
164
- - 전체 페이지 레이아웃은 Templates 사용
165
- - 예: `JLayoutAdvanced` 또는 `JLayoutSimple` 사용
166
-
167
- ### 계층별 사용 가이드
168
-
169
- #### Atoms 사용 시
170
-
171
- ```vue
172
- <template>
173
- <!-- 단일 기능만 필요한 경우 -->
174
- <JButton @click="handleClick">저장</JButton>
175
- <JInput v-model="name" placeholder="이름 입력" />
176
- </template>
177
- ```
178
-
179
- #### Molecules 사용 시
180
-
181
- ```vue
182
- <template>
183
- <!-- 여러 Atoms 조합이 필요한 경우 -->
184
- <JFormField
185
- type="input"
186
- label="이름"
187
- required
188
- v-model="name"
189
- placeholder="이름을 입력하세요"
190
- />
191
-
192
- <JCard title="제목">
193
- <p>콘텐츠</p>
194
- </JCard>
195
- </template>
196
- ```
197
-
198
- #### Organisms 사용 시
199
-
200
- ```vue
201
- <template>
202
- <!-- 완전한 기능 블록이 필요한 경우 -->
203
- <JModal :open="isOpen" title="확인" @confirm="handleConfirm" />
204
-
205
- <JDynamicForm :schema="formSchema" v-model="formData" />
206
- </template>
207
- ```
208
-
209
- #### Templates 사용 시
210
-
211
- ```vue
212
- <template>
213
- <!-- 전체 페이지 레이아웃 -->
214
- <JLayoutAdvanced
215
- :menu-items="menuItems"
216
- :favorites="favorites"
217
- >
218
- <template #content-tab-{id}>
219
- <RouterView />
220
- </template>
221
- </JLayoutAdvanced>
222
- </template>
223
- ```
224
-
225
- ---
226
-
227
- ## 컴포넌트 활용 가이드
228
-
229
- ### 상황별 추천 컴포넌트
230
-
231
- #### 폼 입력
232
-
233
- | 상황 | 추천 컴포넌트 | 이유 |
234
- |------|--------------|------|
235
- | 단일 라인 텍스트 입력 | `JInput` | 가장 기본적인 텍스트 입력 |
236
- | 여러 라인 텍스트 입력 | `JTextarea` | 긴 텍스트 입력 |
237
- | 날짜 선택 | `JDatepicker` | 날짜 선택 UI 제공 |
238
- | 단일 선택 (옵션 적음) | `JCombo` | 드롭다운 선택 |
239
- | 단일 선택 (옵션 많음) | `JSearchCombo` | 검색 기능으로 옵션 탐색 용이 |
240
- | 다중 선택 | `JCombo` (multiple) | 다중 선택 지원 |
241
- | ON/OFF 설정 | `JSwitch` | 토글 스위치 UI |
242
- | 체크박스 선택 | `JCheckbox` | 다중 선택 |
243
- | 라디오 선택 | `JRadio` | 단일 선택 (옵션 표시) |
244
- | 폼 필드 (라벨 + 에러 포함) | `JFormField` | 라벨, 에러 메시지 자동 처리 |
245
- | 동적 폼 생성 | `JDynamicForm` | JSON 스키마로 폼 자동 생성 |
246
-
247
- #### 데이터 표시
248
-
249
- | 상황 | 추천 컴포넌트 | 이유 |
250
- |------|--------------|------|
251
- | 테이블 데이터 | `JGrid` | 정렬, 필터링, 페이지네이션 지원 |
252
- | 상태 표시 | `JBadge` | 색상으로 상태 구분 |
253
- | 진행률 표시 | `JProgress` | 작업 진행 상태 시각화 |
254
- | 로딩 표시 | `JSpinner` | 비동기 작업 로딩 표시 |
255
-
256
- #### 사용자 인터랙션
257
-
258
- | 상황 | 추천 컴포넌트 | 이유 |
259
- |------|--------------|------|
260
- | 액션 버튼 | `JButton` | 다양한 스타일 지원 |
261
- | 버튼 그룹 | `JButtonGroup` | 관련 버튼 묶기 |
262
- | 모달 다이얼로그 | `JModal` | 다양한 크기 지원 (sm, md, lg, xl, 2xl, full) |
263
- | 폼 모달 | `JFormModal` | JDynamicForm 기반 모달 |
264
- | 탭 전환 | `JTabs` | 정적 탭 |
265
- | 동적 탭 | `JDynamicTabs` | 런타임에 탭 추가/제거 |
266
-
267
- #### 레이아웃
268
-
269
- | 상황 | 추천 컴포넌트 | 이유 |
270
- |------|--------------|------|
271
- | 콘텐츠 그룹화 | `JCard` | 카드 형태로 콘텐츠 구분 |
272
- | 접기/펼치기 | `JAccordion` | 섹션 접기/펼치기 |
273
- | 페이지 헤더 | `JHeader` | 애플리케이션 상단 헤더 |
274
- | 사이드바 (고급) | `JSidebarAdvanced` | 검색, 즐겨찾기 지원 |
275
- | 사이드바 (간단) | `JSidebarSimple` | 기본 메뉴만 필요할 때 |
276
- | 전체 레이아웃 (고급) | `JLayoutAdvanced` | 탭 기반 멀티 페이지 |
277
- | 전체 레이아웃 (간단) | `JLayoutSimple` | 단일 페이지 레이아웃 |
278
-
279
- #### 피드백
280
-
281
- | 상황 | 추천 컴포넌트 | 이유 |
282
- |------|--------------|------|
283
- | 성공/경고/에러 메시지 | `JAlert` | 타입별 스타일 자동 적용 |
284
- | 전역 알림 메시지 (Toast) | `JToast` (함수) + `JToaster` (컴포넌트) | 화면 상단/하단에 일시적으로 표시되는 알림 |
285
- | 추가 설명 | `JTooltip` | 호버 시 설명 표시 |
286
- | 추가 정보 | `JPopover` | 클릭 시 정보 표시 |
287
-
288
- #### 검색
289
-
290
- | 상황 | 추천 컴포넌트 | 이유 |
291
- |------|--------------|------|
292
- | 검색 조건 패널 | `JSearchPanel` | JDynamicForm 기반, 접기/펼치기 지원 |
293
- | 주소 검색 | `JSearchAddr` | Daum Postcode API 통합 |
294
-
295
- ---
296
-
297
- ## 기본 사용 규칙
298
-
299
- ### Props 공통 규칙
300
-
301
- 대부분의 입력 컴포넌트는 다음 공통 props를 지원합니다:
92
+ 대부분의 입력 컴포넌트가 지원하는 공통 props:
302
93
 
303
94
  | Prop | 타입 | 설명 | 기본값 |
304
95
  |------|------|------|--------|
305
- | `modelValue` | `string \| number \| boolean \| object` | `v-model` 양방향 데이터 바인딩 | - |
306
- | `placeholder` | `string` | 입력 전 표시되는 안내문 | `''` |
307
- | `disabled` | `boolean` | 비활성화 상태 | `false` |
308
- | `readonly` | `boolean` | 읽기 전용 상태 (Input/Textarea) | `false` |
309
- | `required` | `boolean` | 필수 입력/선택 여부 | `false` |
310
- | `id` | `string` | HTML id 속성 (label for 연결용) | - |
311
- | `name` | `string` | form 데이터 전송 시 키 이름 | - |
312
- | `styletype` | `string` | 스타일 테마 프리셋 | `'default'` |
96
+ | `modelValue` | 컴포넌트별 상이 | v-model 바인딩 | - |
97
+ | `placeholder` | `string` | 안내문 | `''` |
98
+ | `disabled` | `boolean` | 비활성화 | `false` |
99
+ | `readonly` | `boolean` | 읽기 전용 (Input/Textarea) | `false` |
100
+ | `required` | `boolean` | 필수 여부 | `false` |
101
+ | `id` | `string` | HTML id | - |
102
+ | `name` | `string` | form 필드명 | - |
313
103
  | `class` | `string` | 추가 CSS 클래스 | - |
314
104
 
315
- ### styletype 사용 가이드
316
-
317
- `styletype` prop은 컴포넌트의 스타일을 빠르게 변경할 수 있는 프리셋입니다.
318
-
319
- #### JButton styletype
105
+ ### 공통 이벤트
320
106
 
321
- ```vue
322
- <JButton styletype="primary">Primary</JButton>
323
- <JButton styletype="secondary">Secondary</JButton>
324
- <JButton styletype="danger">Danger</JButton>
325
- <JButton styletype="outline">Outline</JButton>
326
- <JButton styletype="ghost">Ghost</JButton>
327
- <JButton styletype="link">Link</JButton>
328
- <JButton styletype="sm">Small</JButton>
329
- <JButton styletype="lg">Large</JButton>
330
- ```
107
+ | 이벤트 | 설명 |
108
+ |--------|------|
109
+ | `update:modelValue` | v-model 업데이트 |
110
+ | `change` | 값 변경 |
111
+ | `focus` | 포커스 |
112
+ | `blur` | 포커스 해제 |
331
113
 
332
- **권장 사용:**
333
- - `primary`: 주요 액션 (저장, 확인 등)
334
- - `secondary`: 보조 액션
335
- - `danger`: 위험한 액션 (삭제 등)
336
- - `outline`: 덜 강조된 액션
337
- - `ghost`: 최소한의 시각적 강조
114
+ ---
338
115
 
339
- ### v-model 사용
116
+ ## Atoms Props 레퍼런스
340
117
 
341
- 모든 입력 컴포넌트는 `v-model`을 지원합니다:
118
+ ### JButton
342
119
 
343
120
  ```vue
344
- <template>
345
- <JInput v-model="name" placeholder="이름" />
346
- <JCombo v-model="selected" :options="options" />
347
- <JSwitch v-model="enabled" />
348
- </template>
349
-
350
- <script setup>
351
- import { ref } from 'vue'
352
-
353
- const name = ref('')
354
- const selected = ref(null)
355
- const enabled = ref(false)
356
- </script>
121
+ <JButton
122
+ type="button" <!-- 'button'|'submit'|'reset' -->
123
+ :disabled="false"
124
+ :loading="false" <!-- true이면 스피너 표시 -->
125
+ @click="handler"
126
+ >
127
+ 저장
128
+ </JButton>
357
129
  ```
358
130
 
359
- ### 이벤트 처리
131
+ ---
360
132
 
361
- 컴포넌트는 표준 Vue 이벤트를 발생시킵니다. `v-model`과 함께 이벤트 핸들러를 사용할 수 있습니다:
133
+ ### JInput
362
134
 
363
135
  ```vue
364
- <template>
365
- <JButton @click="handleClick">클릭</JButton>
366
- <JInput v-model="name" @change="handleChange" />
367
- <JModal
368
- :open="isOpen"
369
- @confirm="handleConfirm"
370
- @cancel="handleCancel"
371
- />
372
- </template>
373
-
374
- <script setup>
375
- import { ref } from 'vue'
376
-
377
- const name = ref('')
378
- const isOpen = ref(false)
379
-
380
- const handleClick = () => {
381
- console.log('버튼 클릭')
382
- }
383
-
384
- const handleChange = (value) => {
385
- console.log('값 변경:', value)
386
- }
387
-
388
- const handleConfirm = () => {
389
- console.log('확인')
390
- isOpen.value = false
391
- }
392
-
393
- const handleCancel = () => {
394
- console.log('취소')
395
- isOpen.value = false
396
- }
397
- </script>
136
+ <JInput
137
+ v-model="value"
138
+ type="text" <!-- 'text'|'email'|'password'|'number'|'tel'|'url' -->
139
+ placeholder="입력하세요"
140
+ :disabled="false"
141
+ :readonly="false"
142
+ :required="false"
143
+ @change="handler"
144
+ />
398
145
  ```
399
146
 
400
- ### 컴포넌트별 기본 사용법
401
-
402
- #### Atoms (원자 컴포넌트)
147
+ ---
403
148
 
404
- ##### JAvatar - 사용자 아바타
149
+ ### JTextarea
405
150
 
406
151
  ```vue
407
- <template>
408
- <!-- 기본 사용 -->
409
- <JAvatar fallback="JD" />
410
-
411
- <!-- 이미지와 함께 사용 -->
412
- <JAvatar
413
- src="https://example.com/avatar.jpg"
414
- alt="사용자 이름"
415
- fallback="JD"
416
- size="lg"
417
- shape="circle"
418
- status="online"
419
- />
420
- </template>
421
-
422
- <script setup>
423
- import { JAvatar } from '@/components/atoms'
424
- </script>
152
+ <JTextarea
153
+ v-model="value"
154
+ placeholder="입력하세요"
155
+ :rows="3"
156
+ :disabled="false"
157
+ :readonly="false"
158
+ />
425
159
  ```
426
160
 
427
- ##### JBadge - 상태 표시 배지
428
-
429
- ```vue
430
- <template>
431
- <JBadge styletype="success">활성</JBadge>
432
- <JBadge styletype="warning">대기</JBadge>
433
- <JBadge styletype="danger">비활성</JBadge>
434
- <JBadge styletype="primary" size="lg">프리미엄</JBadge>
435
- </template>
436
-
437
- <script setup>
438
- import { JBadge } from '@/components/atoms'
439
- </script>
440
- ```
161
+ ---
441
162
 
442
- ##### JCheckbox - 체크박스
163
+ ### JCombo
443
164
 
444
165
  ```vue
445
- <template>
446
- <JCheckbox v-model="checked" label="동의합니다" />
447
- <JCheckbox v-model="terms" label="약관에 동의합니다" required />
448
- </template>
449
-
450
- <script setup>
451
- import { ref } from 'vue'
452
- import { JCheckbox } from '@/components/atoms'
453
-
454
- const checked = ref(false)
455
- const terms = ref(false)
456
- </script>
166
+ <JCombo
167
+ v-model="selected"
168
+ :options="[
169
+ { value: 'opt1', label: '옵션 1' },
170
+ { value: 'opt2', label: '옵션 2' },
171
+ ]"
172
+ placeholder="선택하세요"
173
+ :multiple="false"
174
+ :disabled="false"
175
+ />
457
176
  ```
458
177
 
459
- ##### JCombo - 드롭다운 선택
460
-
461
- ```vue
462
- <template>
463
- <JCombo
464
- v-model="selected"
465
- :options="options"
466
- placeholder="선택하세요"
467
- />
468
- </template>
178
+ ---
469
179
 
470
- <script setup>
471
- import { ref } from 'vue'
472
- import { JCombo } from '@/components/atoms'
180
+ ### JSearchCombo
473
181
 
474
- const selected = ref('')
475
- const options = [
476
- { value: 'option1', label: '옵션 1' },
477
- { value: 'option2', label: '옵션 2' },
478
- { value: 'option3', label: '옵션 3' },
479
- ]
480
- </script>
481
- ```
482
-
483
- ##### JDatepicker - 날짜 선택기
182
+ > **주의**: `modelValue`는 `{ value, label }` 객체 타입
484
183
 
485
184
  ```vue
486
- <template>
487
- <JDatepicker
488
- v-model="date"
489
- placeholder="날짜를 선택하세요"
490
- />
491
- </template>
492
-
493
- <script setup>
494
- import { ref } from 'vue'
495
- import { JDatepicker } from '@/components/atoms'
496
-
497
- const date = ref(null)
498
- </script>
185
+ <JSearchCombo
186
+ v-model="selectedOption"
187
+ :options="[
188
+ { value: '1', label: '항목 1' },
189
+ { value: '2', label: '항목 2' },
190
+ ]"
191
+ placeholder="선택해주세요."
192
+ searchPlaceholder="검색어 입력"
193
+ emptyText="검색 결과가 없습니다."
194
+ :multiple="false"
195
+ :disabled="false"
196
+ />
499
197
  ```
500
198
 
501
- ##### JDivider - 구분선
502
-
503
- ```vue
504
- <template>
505
- <div>위쪽 콘텐츠</div>
506
- <JDivider />
507
- <div>아래쪽 콘텐츠</div>
508
-
509
- <!-- 텍스트와 함께 사용 -->
510
- <JDivider>또는</JDivider>
511
- </template>
512
-
513
- <script setup>
514
- import { JDivider } from '@/components/atoms'
515
- </script>
199
+ ```ts
200
+ const selectedOption = ref<{ value: string | number; label: string } | undefined>()
516
201
  ```
517
202
 
518
- ##### JGrid - 데이터 그리드
519
-
520
- ```vue
521
- <template>
522
- <JGrid
523
- :column-defs="columnDefs"
524
- :row-data="rowData"
525
- />
526
- </template>
527
-
528
- <script setup>
529
- import { JGrid } from '@/components/atoms'
530
-
531
- const columnDefs = [
532
- { field: 'name', headerName: '이름' },
533
- { field: 'age', headerName: '나이' },
534
- { field: 'email', headerName: '이메일' },
535
- ]
203
+ ---
536
204
 
537
- const rowData = [
538
- { name: '홍길동', age: 30, email: 'hong@example.com' },
539
- { name: '김철수', age: 25, email: 'kim@example.com' },
540
- ]
541
- </script>
542
- ```
205
+ ### JCheckbox
543
206
 
544
- ##### JIcon - 아이콘
207
+ > **주의**: `modelValue`는 `boolean`이 아닌 `'Y'` / `'N'` 문자열
545
208
 
546
209
  ```vue
547
- <template>
548
- <JIcon name="user" size="lg" />
549
- <JIcon name="settings" styletype="primary" />
550
- <JIcon name="check" styletype="success" />
551
- </template>
552
-
553
- <script setup>
554
- import { JIcon } from '@/components/atoms'
555
- </script>
210
+ <JCheckbox
211
+ v-model="checked"
212
+ label="동의합니다"
213
+ :disabled="false"
214
+ :required="false"
215
+ />
556
216
  ```
557
217
 
558
- ##### JImage - 이미지
559
-
560
- ```vue
561
- <template>
562
- <JImage
563
- src="https://example.com/image.jpg"
564
- alt="이미지 설명"
565
- width="300"
566
- height="200"
567
- />
568
- </template>
569
-
570
- <script setup>
571
- import { JImage } from '@/components/atoms'
572
- </script>
218
+ ```ts
219
+ const checked = ref('N') // 'Y' 또는 'N'
573
220
  ```
574
221
 
575
- ##### JKbd - 키보드 단축키 표시
576
-
577
- ```vue
578
- <template>
579
- <div>
580
- 저장: <JKbd>Ctrl</JKbd> + <JKbd>S</JKbd>
581
- 복사: <JKbd>Ctrl</JKbd> + <JKbd>C</JKbd>
582
- </div>
583
- </template>
222
+ ---
584
223
 
585
- <script setup>
586
- import { JKbd } from '@/components/atoms'
587
- </script>
588
- ```
224
+ ### JSwitch
589
225
 
590
- ##### JLabel - 라벨
226
+ > **주의**: `modelValue`는 `boolean`이 아닌 `'Y'` / `'N'` 문자열
591
227
 
592
228
  ```vue
593
- <template>
594
- <JLabel for="input-id">이름</JLabel>
595
- <JInput id="input-id" v-model="name" />
596
- </template>
597
-
598
- <script setup>
599
- import { ref } from 'vue'
600
- import { JLabel, JInput } from '@/components/atoms'
601
-
602
- const name = ref('')
603
- </script>
229
+ <JSwitch
230
+ v-model="enabled"
231
+ label="알림 받기"
232
+ :disabled="false"
233
+ />
604
234
  ```
605
235
 
606
- ##### JLink - 링크
607
-
608
- ```vue
609
- <template>
610
- <JLink href="/about">소개</JLink>
611
- <JLink href="/contact" styletype="primary">연락처</JLink>
612
- </template>
613
-
614
- <script setup>
615
- import { JLink } from '@/components/atoms'
616
- </script>
617
- ```
618
-
619
- ##### JPopover - 팝오버
620
-
621
- ```vue
622
- <template>
623
- <JPopover>
624
- <template #trigger>
625
- <JButton>팝오버 열기</JButton>
626
- </template>
627
- <template #content>
628
- <div class="p-4">
629
- <p>팝오버 내용입니다.</p>
630
- </div>
631
- </template>
632
- </JPopover>
633
- </template>
634
-
635
- <script setup>
636
- import { JPopover, JButton } from '@/components/atoms'
637
- </script>
236
+ ```ts
237
+ const enabled = ref('N') // 'Y' 또는 'N'
638
238
  ```
639
239
 
640
- ##### JProgress - 진행률 표시
641
-
642
- ```vue
643
- <template>
644
- <JProgress :value="progress" />
645
- <JProgress :value="75" styletype="success" />
646
- </template>
647
-
648
- <script setup>
649
- import { ref } from 'vue'
650
- import { JProgress } from '@/components/atoms'
240
+ ---
651
241
 
652
- const progress = ref(50)
653
- </script>
654
- ```
242
+ ### JRadio
655
243
 
656
- ##### JRadio - 라디오 버튼
244
+ > **주의**: `options` 배열로 라디오 항목 정의 (개별 나열 아님)
657
245
 
658
246
  ```vue
659
- <template>
660
- <JRadio v-model="selected" value="option1" label="옵션 1" />
661
- <JRadio v-model="selected" value="option2" label="옵션 2" />
662
- <JRadio v-model="selected" value="option3" label="옵션 3" />
663
- </template>
664
-
665
- <script setup>
666
- import { ref } from 'vue'
667
- import { JRadio } from '@/components/atoms'
668
-
669
- const selected = ref('option1')
670
- </script>
247
+ <JRadio
248
+ v-model="selected"
249
+ :options="[
250
+ { value: 'opt1', label: '옵션 1' },
251
+ { value: 'opt2', label: '옵션 2' },
252
+ { value: 'opt3', label: '옵션 3', disabled: true },
253
+ ]"
254
+ />
671
255
  ```
672
256
 
673
- ##### JSearchCombo - 검색 가능한 드롭다운
674
-
675
- ```vue
676
- <template>
677
- <JSearchCombo
678
- v-model="selected"
679
- :options="options"
680
- placeholder="검색하여 선택하세요"
681
- />
682
- </template>
683
-
684
- <script setup>
685
- import { ref } from 'vue'
686
- import { JSearchCombo } from '@/components/atoms'
687
-
688
- const selected = ref('')
689
- const options = [
690
- { value: '1', label: '항목 1' },
691
- { value: '2', label: '항목 2' },
692
- // ... 많은 옵션들
693
- ]
694
- </script>
695
- ```
257
+ ---
696
258
 
697
- ##### JSpinner - 로딩 스피너
259
+ ### JDatepicker
698
260
 
699
261
  ```vue
700
- <template>
701
- <JSpinner v-if="loading" />
702
- <div v-else>콘텐츠</div>
703
- </template>
704
-
705
- <script setup>
706
- import { ref } from 'vue'
707
- import { JSpinner } from '@/components/atoms'
708
-
709
- const loading = ref(true)
710
- </script>
262
+ <JDatepicker
263
+ v-model="date"
264
+ placeholder="날짜를 선택하세요"
265
+ :disabled="false"
266
+ />
711
267
  ```
712
268
 
713
- ##### JSwitch - 토글 스위치
714
-
715
- ```vue
716
- <template>
717
- <JSwitch v-model="enabled" label="알림 받기" />
718
- </template>
719
-
720
- <script setup>
721
- import { ref } from 'vue'
722
- import { JSwitch } from '@/components/atoms'
723
-
724
- const enabled = ref(false)
725
- </script>
269
+ ```ts
270
+ const date = ref<string | null>(null) // ISO 8601: 'YYYY-MM-DD'
726
271
  ```
727
272
 
728
- ##### JTextarea - 멀티라인 텍스트 입력
729
-
730
- ```vue
731
- <template>
732
- <JTextarea
733
- v-model="description"
734
- placeholder="설명을 입력하세요"
735
- rows="5"
736
- />
737
- </template>
738
-
739
- <script setup>
740
- import { ref } from 'vue'
741
- import { JTextarea } from '@/components/atoms'
742
-
743
- const description = ref('')
744
- </script>
745
- ```
273
+ ---
746
274
 
747
- ##### JTooltip - 툴팁
275
+ ### JGrid (AG Grid)
748
276
 
749
277
  ```vue
750
- <template>
751
- <JTooltip content="이것은 툴팁입니다">
752
- <JButton>호버하세요</JButton>
753
- </JTooltip>
754
- </template>
755
-
756
- <script setup>
757
- import { JTooltip, JButton } from '@/components/atoms'
758
- </script>
278
+ <JGrid
279
+ :column-defs="columnDefs"
280
+ :row-data="rowData"
281
+ theme="ag-theme-balham"
282
+ :pagination="false"
283
+ :checkbox="false"
284
+ :summary-column="false"
285
+ :hidden-column="false"
286
+ :enable-grouping="false" <!-- Enterprise -->
287
+ :enable-pivot="false" <!-- Enterprise -->
288
+ :enable-excel-export="false" <!-- Enterprise -->
289
+ />
759
290
  ```
760
291
 
761
- ##### JButton - 버튼
762
-
763
- **주요 props**: `styletype` (primary, secondary, danger, outline, ghost, link, sm, lg), `disabled`, `loading`
764
-
765
- **권장 사용 시나리오**:
766
- - `styletype="primary"`: 주요 액션 (저장, 확인 등)
767
- - `styletype="secondary"`: 보조 액션
768
- - `styletype="danger"`: 위험한 액션 (삭제 등)
769
- - `styletype="outline"`: 덜 강조된 액션
770
- - `styletype="ghost"`: 최소한의 시각적 강조
771
- - `disabled`: 비활성화 상태
772
- - `loading`: 비동기 작업 진행 중 표시
773
-
774
- ##### JInput - 텍스트 입력 필드
775
-
776
- **주요 props**: `modelValue`, `type` (text, email, password, number 등), `placeholder`, `disabled`, `readonly`, `required`, `styletype` (default, error, success, warning, sm, lg)
777
-
778
- **권장 사용 시나리오**:
779
- - `type="text"`: 일반 텍스트 입력
780
- - `type="email"`: 이메일 주소 입력
781
- - `type="password"`: 비밀번호 입력
782
- - `type="number"`: 숫자 입력
783
- - `styletype="error"`: 유효성 검사 실패 시
784
- - `styletype="success"`: 유효성 검사 통과 시
785
- - `readonly`: 읽기 전용 표시
786
-
787
- ##### JEditor - 마크다운 에디터
788
-
789
- **주요 props**: `modelValue` (마크다운 문자열), `placeholder`, `disabled`, `readonly`, `height`, `theme` (light, dark)
790
-
791
- **권장 사용 시나리오**:
792
- - `modelValue`: v-model로 마크다운 내용 바인딩
793
- - `height`: 에디터 높이 설정 (기본값: 500)
794
- - `theme`: 다크모드 지원
795
- - `readonly`: 읽기 전용 모드 (프리뷰 전용)
796
-
797
- ##### JPreview - 마크다운/HTML 뷰어
798
-
799
- **주요 props**: `modelValue` (마크다운 또는 HTML 문자열), `theme` (light, dark)
800
-
801
- **권장 사용 시나리오**:
802
- - `modelValue`: 마크다운 또는 HTML 문자열 자동 감지 및 렌더링
803
- - HTML 문서는 `<!DOCTYPE html>` 또는 `<html>` 태그로 시작하면 자동 감지
804
- - 마크다운은 그 외 모든 경우에 자동 처리
805
- - `theme`: 다크모드 지원
806
- - JEditor로 작성한 마크다운을 자동으로 변환하여 표시
807
-
808
- ##### JToaster - Toast 알림 컨테이너
809
-
810
- **권장 사용 시나리오**:
811
- - 앱의 루트 레벨에 `<JToaster />` 컴포넌트 추가
812
- - `JToast` 함수와 함께 사용: `JToast.success()`, `JToast.error()`, `JToast.info()`, `JToast.warning()`
813
- - 자동 사라짐 기능 내장
814
- - 여러 Toast 동시 표시 가능
815
-
816
- #### Molecules (분자 컴포넌트)
817
-
818
- ##### JAccordion - 접을 수 있는 콘텐츠 섹션
819
-
820
- ```vue
821
- <template>
822
- <JAccordion
823
- :items="items"
824
- type="single"
825
- collapsible
826
- />
827
- </template>
828
-
829
- <script setup>
830
- import { JAccordion } from '@/components/molecules'
831
-
832
- const items = [
833
- {
834
- value: 'item-1',
835
- title: '첫 번째 항목',
836
- content: '첫 번째 항목의 내용입니다.',
837
- },
838
- {
839
- value: 'item-2',
840
- title: '두 번째 항목',
841
- content: '두 번째 항목의 내용입니다.',
842
- },
292
+ ```ts
293
+ const columnDefs = [
294
+ { field: 'name', headerName: '이름' },
295
+ { field: 'age', headerName: '나이' },
296
+ { field: 'email', headerName: '이메일' },
843
297
  ]
844
- </script>
845
- ```
846
-
847
- ##### JAlert - 알림 메시지 표시
848
-
849
- ```vue
850
- <template>
851
- <JAlert
852
- variant="default"
853
- title="알림"
854
- description="이것은 알림 메시지입니다."
855
- button-text="확인"
856
- :show-footer="true"
857
- @confirm="handleConfirm"
858
- />
859
- </template>
860
298
 
861
- <script setup>
862
- import { JAlert } from '@/components/molecules'
299
+ const rowData = [
300
+ { name: '홍길동', age: 30, email: 'hong@example.com' },
301
+ ]
863
302
 
864
- const handleConfirm = () => {
865
- console.log('확인 클릭')
866
- }
867
- </script>
303
+ // ref로 접근하여 Excel 내보내기
304
+ gridRef.value?.exportToExcel()
868
305
  ```
869
306
 
870
- ##### JBreadcrumb - 페이지 네비게이션 경로
871
-
872
- ```vue
873
- <template>
874
- <JBreadcrumb :items="breadcrumbItems" />
875
- </template>
876
-
877
- <script setup>
878
- import { JBreadcrumb } from '@/components/molecules'
879
-
880
- const breadcrumbItems = [
881
- { label: '홈', href: '/' },
882
- { label: '카테고리', href: '/category' },
883
- { label: '현재 페이지' },
884
- ]
885
- </script>
886
- ```
307
+ ---
887
308
 
888
- ##### JButtonGroup - 버튼 그룹
309
+ ### JIcon
889
310
 
890
311
  ```vue
891
- <template>
892
- <JButtonGroup>
893
- <JButton>저장</JButton>
894
- <JButton>취소</JButton>
895
- <JButton>삭제</JButton>
896
- </JButtonGroup>
897
- </template>
898
-
899
- <script setup>
900
- import { JButtonGroup, JButton } from '@/components/molecules'
901
- </script>
312
+ <JIcon
313
+ name="user" <!-- Lucide 아이콘 이름 (필수) -->
314
+ size="md" <!-- 'sm'|'md'|'lg'|'xl' -->
315
+ color="#333" <!-- CSS 색상값 (선택) -->
316
+ />
902
317
  ```
903
318
 
904
- ##### JCard - 카드 컴포넌트
905
-
906
- ```vue
907
- <template>
908
- <JCard
909
- title="카드 제목"
910
- description="카드 설명"
911
- footer="푸터 텍스트"
912
- >
913
- <p>카드 내용입니다.</p>
914
- </JCard>
915
- </template>
916
-
917
- <script setup>
918
- import { JCard } from '@/components/molecules'
919
- </script>
920
- ```
319
+ ---
921
320
 
922
- ##### JContextMenu - 우클릭 컨텍스트 메뉴
321
+ ### JAvatar
923
322
 
924
323
  ```vue
925
- <template>
926
- <JContextMenu>
927
- <template #trigger>
928
- <div>우클릭하세요</div>
929
- </template>
930
- <template #content>
931
- <div class="p-2">
932
- <div class="p-2 hover:bg-gray-100 cursor-pointer">복사</div>
933
- <div class="p-2 hover:bg-gray-100 cursor-pointer">붙여넣기</div>
934
- <div class="p-2 hover:bg-gray-100 cursor-pointer">삭제</div>
935
- </div>
936
- </template>
937
- </JContextMenu>
938
- </template>
939
-
940
- <script setup>
941
- import { JContextMenu } from '@/components/molecules'
942
- </script>
324
+ <JAvatar
325
+ src="https://example.com/avatar.jpg"
326
+ alt="사용자 이름"
327
+ fallback="JD"
328
+ size="lg"
329
+ shape="circle"
330
+ status="online"
331
+ />
943
332
  ```
944
333
 
945
- ##### JGroupCombo - 그룹화된 드롭다운
946
-
947
- ```vue
948
- <template>
949
- <JGroupCombo
950
- v-model="selected"
951
- :groups="groupedOptions"
952
- placeholder="그룹에서 선택하세요"
953
- />
954
- </template>
955
-
956
- <script setup>
957
- import { ref } from 'vue'
958
- import { JGroupCombo } from '@/components/molecules'
959
-
960
- const selected = ref('')
961
- const groupedOptions = [
962
- {
963
- label: '그룹 1',
964
- options: [
965
- { value: '1-1', label: '옵션 1-1' },
966
- { value: '1-2', label: '옵션 1-2' },
967
- ],
968
- },
969
- {
970
- label: '그룹 2',
971
- options: [
972
- { value: '2-1', label: '옵션 2-1' },
973
- { value: '2-2', label: '옵션 2-2' },
974
- ],
975
- },
976
- ]
977
- </script>
978
- ```
334
+ ---
979
335
 
980
- ##### JSearchAddr - 주소 검색
336
+ ### JBadge
981
337
 
982
338
  ```vue
983
- <template>
984
- <JSearchAddr
985
- v-model="address"
986
- @select="handleAddressSelect"
987
- />
988
- </template>
989
-
990
- <script setup>
991
- import { ref } from 'vue'
992
- import { JSearchAddr } from '@/components/molecules'
993
-
994
- const address = ref('')
995
-
996
- const handleAddressSelect = (data) => {
997
- console.log('선택된 주소:', data)
998
- }
999
- </script>
339
+ <JBadge size="md">활성</JBadge>
1000
340
  ```
1001
341
 
1002
- ##### JTabs - 탭 UI
1003
-
1004
- ```vue
1005
- <template>
1006
- <JTabs
1007
- :tabs="tabs"
1008
- v-model:active-tab-id="activeTab"
1009
- @tab-change="handleTabChange"
1010
- />
1011
- </template>
1012
-
1013
- <script setup>
1014
- import { ref } from 'vue'
1015
- import { JTabs } from '@/components/molecules'
1016
-
1017
- const activeTab = ref('tab1')
1018
- const tabs = [
1019
- { id: 'tab1', label: '탭 1', closable: false },
1020
- { id: 'tab2', label: '탭 2', closable: true },
1021
- { id: 'tab3', label: '탭 3', closable: true },
1022
- ]
1023
-
1024
- const handleTabChange = (id) => {
1025
- console.log('탭 변경:', id)
1026
- }
1027
- </script>
1028
- ```
342
+ ---
1029
343
 
1030
- ##### JTitlebar - 타이틀바
344
+ ### JProgress
1031
345
 
1032
346
  ```vue
1033
- <template>
1034
- <JTitlebar
1035
- title="페이지 제목"
1036
- description="페이지 설명"
1037
- >
1038
- <template #actions>
1039
- <JButton>액션 1</JButton>
1040
- <JButton>액션 2</JButton>
1041
- </template>
1042
- </JTitlebar>
1043
- </template>
1044
-
1045
- <script setup>
1046
- import { JTitlebar, JButton } from '@/components/molecules'
1047
- </script>
347
+ <JProgress :value="75" />
1048
348
  ```
1049
349
 
1050
- ##### JFormField - 폼 필드 래퍼
350
+ ---
351
+
352
+ ### JEditor
1051
353
 
1052
- **주요 props**: `type` (input, textarea, checkbox, switch, combo, radio, searchCombo, datepicker), `label`, `description`, `errorMsg`, `modelValue`, `placeholder`, `disabled`, `required`, `orientation` (vertical, horizontal, responsive), `labelAlign`, `labelWidth`
354
+ ```vue
355
+ <JEditor
356
+ v-model="markdown"
357
+ placeholder="마크다운을 입력하세요..."
358
+ :height="500"
359
+ theme="light" <!-- 'light'|'dark' -->
360
+ :disabled="false"
361
+ :readonly="false"
362
+ @save="handleSave" <!-- Ctrl+S -->
363
+ />
364
+ ```
1053
365
 
1054
- **권장 사용 시나리오**:
1055
- - `type` prop으로 원하는 입력 컴포넌트 선택
1056
- - `label` prop으로 라벨 값을 입력하면 UI 배치시 라벨이 포함되며, 입력 필드와 자동 연결 (접근성)
1057
- - `errorMsg` prop으로 에러 메시지 값을 입력하면 UI 배치시 에러 메시지가 포함됨
1058
- - `orientation="horizontal"`: 레이블과 입력 필드를 가로로 배치
1059
- - `orientation="vertical"`: 레이블과 입력 필드를 세로로 배치 (기본값)
1060
- - `orientation="responsive"`: 화면 크기에 따라 자동 조정
1061
- - 레이아웃 일관성 유지
366
+ ---
1062
367
 
1063
- #### Organisms (유기체 컴포넌트)
368
+ ### JPreview
1064
369
 
1065
- ##### JDynamicForm - 스키마 기반 동적 폼
370
+ ```vue
371
+ <JPreview
372
+ :model-value="markdownOrHtml"
373
+ theme="light" <!-- 'light'|'dark' -->
374
+ />
375
+ ```
1066
376
 
1067
- **주요 props**: `schema` (JSON 스키마), `modelValue`, `type` (simple, sectioned, wizard)
377
+ > HTML은 `<!DOCTYPE html>` 또는 `<html>` 태그로 시작하면 자동 감지, 그 외는 마크다운 처리
1068
378
 
1069
- **권장 사용 시나리오**:
1070
- - `type="simple"`: 단순한 폼
1071
- - `type="sectioned"`: 섹션별로 구분된 폼
1072
- - `type="wizard"`: 단계별 입력 폼
1073
- - JSON 스키마로 폼 자동 생성
1074
- - `schema.fields` 배열에 필드 정의: `controlName`, `label`, `type`, `isRequired` 등
1075
- - Props 기반의 사용을 권장합니다
379
+ ---
1076
380
 
1077
- ##### JModal - 모달 다이얼로그
381
+ ### JTooltip
1078
382
 
1079
- **주요 props**: `open`, `size` (sm, md, lg, xl, 2xl, full), `title`, `description`, `show-footer`, `confirm-text`, `cancel-text`
383
+ ```vue
384
+ <JTooltip
385
+ content="설명 텍스트"
386
+ side="top" <!-- 'top'|'right'|'bottom'|'left' -->
387
+ align="center" <!-- 'start'|'center'|'end' -->
388
+ :delay="200"
389
+ maxWidth="200px"
390
+ trigger="hover" <!-- 'hover'|'focus'|'click'|'manual' -->
391
+ :disabled="false"
392
+ >
393
+ <JButton>호버하세요</JButton>
394
+ </JTooltip>
395
+ ```
1080
396
 
1081
- **권장 사용 시나리오**:
1082
- - `open`: 모달 열림/닫힘 상태 제어
1083
- - `size="sm"`: 작은 모달
1084
- - `size="md"`: 중간 모달 (기본값)
1085
- - `size="lg"`: 큰 모달
1086
- - `size="xl"`: 매우 큰 모달
1087
- - `size="2xl"`: 초대형 모달
1088
- - `size="full"`: 전체 화면 모달
1089
- - `title`: 모달 제목
1090
- - `show-footer`: 푸터 표시 여부
1091
- - `@confirm`: 확인 버튼 클릭 이벤트
1092
- - `@cancel`: 취소 버튼 클릭 이벤트
1093
- - Props 기반의 사용을 권장합니다
397
+ ---
1094
398
 
1095
- ##### JFormModal - 폼 모달
399
+ ### JDivider
1096
400
 
1097
401
  ```vue
1098
- <template>
1099
- <JFormModal
1100
- :open="isOpen"
1101
- :schema="formSchema"
1102
- v-model="formData"
1103
- title="폼 모달"
1104
- @confirm="handleConfirm"
1105
- @cancel="handleCancel"
1106
- />
1107
- </template>
402
+ <!-- 기본 구분선 -->
403
+ <JDivider />
1108
404
 
1109
- <script setup>
1110
- import { ref } from 'vue'
1111
- import { JFormModal } from '@/components/organisms'
405
+ <!-- 텍스트 포함 구분선 -->
406
+ <JDivider>또는</JDivider>
407
+ ```
1112
408
 
1113
- const isOpen = ref(false)
1114
- const formData = ref({})
1115
- const formSchema = {
1116
- type: 'simple',
1117
- fields: [
1118
- { controlName: 'name', label: '이름', type: 'input', isRequired: true },
1119
- { controlName: 'email', label: '이메일', type: 'input', inputType: 'email' },
1120
- ],
1121
- }
409
+ ---
1122
410
 
1123
- const handleConfirm = () => {
1124
- console.log('확인:', formData.value)
1125
- isOpen.value = false
1126
- }
411
+ ### JKbd
1127
412
 
1128
- const handleCancel = () => {
1129
- isOpen.value = false
1130
- }
1131
- </script>
413
+ ```vue
414
+ <div>
415
+ 저장: <JKbd>Ctrl</JKbd> + <JKbd>S</JKbd>
416
+ </div>
1132
417
  ```
1133
418
 
1134
- ##### JHeader - 애플리케이션 헤더
419
+ ---
1135
420
 
1136
- ```vue
1137
- <template>
1138
- <JHeader
1139
- :menu-items="menuItems"
1140
- :user="user"
1141
- @menu-click="handleMenuClick"
1142
- />
1143
- </template>
421
+ ### JLabel
1144
422
 
1145
- <script setup>
1146
- import { JHeader } from '@/components/organisms'
423
+ ```vue
424
+ <JLabel for="input-id">이름</JLabel>
425
+ <JInput id="input-id" v-model="name" />
426
+ ```
1147
427
 
1148
- const menuItems = [
1149
- { id: '1', label: '홈', path: '/' },
1150
- { id: '2', label: '설정', path: '/settings' },
1151
- ]
428
+ ---
1152
429
 
1153
- const user = {
1154
- name: '홍길동',
1155
- avatar: 'https://example.com/avatar.jpg',
1156
- }
430
+ ### JLink
1157
431
 
1158
- const handleMenuClick = (item) => {
1159
- console.log('메뉴 클릭:', item)
1160
- }
1161
- </script>
432
+ ```vue
433
+ <JLink href="/about">소개</JLink>
1162
434
  ```
1163
435
 
1164
- ##### JPageContainer - 페이지 컨테이너
436
+ ---
1165
437
 
1166
- ```vue
1167
- <template>
1168
- <JPageContainer>
1169
- <template #header>
1170
- <h1>페이지 제목</h1>
1171
- </template>
1172
- <template #content>
1173
- <p>페이지 내용입니다.</p>
1174
- </template>
1175
- </JPageContainer>
1176
- </template>
438
+ ### JImage
1177
439
 
1178
- <script setup>
1179
- import { JPageContainer } from '@/components/organisms'
1180
- </script>
440
+ ```vue
441
+ <JImage
442
+ src="https://example.com/image.jpg"
443
+ alt="이미지 설명"
444
+ width="300"
445
+ height="200"
446
+ />
1181
447
  ```
1182
448
 
1183
- ##### JSearchPanel - 검색 조건 패널
449
+ ---
450
+
451
+ ### JPopover
1184
452
 
1185
453
  ```vue
1186
- <template>
1187
- <JSearchPanel
1188
- :schema="searchSchema"
1189
- v-model="searchData"
1190
- @search="handleSearch"
1191
- />
1192
- </template>
454
+ <JPopover>
455
+ <template #trigger>
456
+ <JButton>팝오버 열기</JButton>
457
+ </template>
458
+ <template #content>
459
+ <div class="p-4">
460
+ <p>팝오버 내용입니다.</p>
461
+ </div>
462
+ </template>
463
+ </JPopover>
464
+ ```
1193
465
 
1194
- <script setup>
1195
- import { ref } from 'vue'
1196
- import { JSearchPanel } from '@/components/organisms'
466
+ ---
1197
467
 
1198
- const searchData = ref({})
1199
- const searchSchema = {
1200
- type: 'simple',
1201
- fields: [
1202
- { controlName: 'keyword', label: '검색어', type: 'input' },
1203
- { controlName: 'category', label: '카테고리', type: 'combo', options: [] },
1204
- ],
1205
- }
468
+ ### JSpinner
1206
469
 
1207
- const handleSearch = (data) => {
1208
- console.log('검색:', data)
1209
- }
1210
- </script>
470
+ ```vue
471
+ <JSpinner v-if="loading" />
472
+ <div v-else>콘텐츠</div>
1211
473
  ```
1212
474
 
1213
- ##### JSidebarAdvanced - 고급 사이드바
475
+ ---
1214
476
 
1215
- ```vue
1216
- <template>
1217
- <JSidebarAdvanced
1218
- :menu-items="menuItems"
1219
- :favorites="favorites"
1220
- @menu-click="handleMenuClick"
1221
- />
1222
- </template>
477
+ ### JTree
1223
478
 
1224
- <script setup>
1225
- import { JSidebarAdvanced } from '@/components/organisms'
479
+ ```vue
480
+ <JTree
481
+ :items="treeItems"
482
+ v-model:expanded-keys="expandedKeys"
483
+ v-model:active-key="activeKey"
484
+ :permissions="permissions"
485
+ :max-depth="10"
486
+ searchQuery=""
487
+ @node-click="handleNodeClick"
488
+ @expand-change="handleExpandChange"
489
+ />
490
+ ```
1226
491
 
1227
- const menuItems = [
1228
- { id: '1', label: '홈', path: '/', icon: 'house' },
1229
- { id: '2', label: '설정', path: '/settings', icon: 'settings' },
1230
- ]
492
+ ---
1231
493
 
1232
- const favorites = ['1']
494
+ ## Molecules Props 레퍼런스
1233
495
 
1234
- const handleMenuClick = (item) => {
1235
- console.log('메뉴 클릭:', item)
1236
- }
1237
- </script>
1238
- ```
496
+ ### JFormField
1239
497
 
1240
- ##### JSidebarSimple - 간단한 사이드바
498
+ > **권장**: 필드 구성 시 JFormField 사용. `type` prop으로 입력 컴포넌트 선택.
1241
499
 
1242
500
  ```vue
1243
- <template>
1244
- <JSidebarSimple
1245
- :menu-items="menuItems"
1246
- @menu-click="handleMenuClick"
1247
- />
1248
- </template>
501
+ <JFormField
502
+ type="input" <!-- 'input'|'textarea'|'checkbox'|'switch'|'combo'|'radio'|'searchCombo'|'datepicker' -->
503
+ label="이름"
504
+ description="설명 텍스트"
505
+ error-msg="에러 메시지"
506
+ v-model="value"
507
+ placeholder="입력하세요"
508
+ :required="true"
509
+ :disabled="false"
510
+ :readonly="false"
511
+ orientation="vertical" <!-- 'vertical'|'horizontal'|'responsive' -->
512
+ labelAlign="left" <!-- 'left'|'middle'|'right' -->
513
+ labelWidth="8rem"
514
+ inputType="text" <!-- type="input"일 때: 'text'|'email'|'password'|'number' -->
515
+ :options="[]" <!-- type이 combo/radio/searchCombo일 때 -->
516
+ :multiple="false" <!-- type이 combo/searchCombo일 때 -->
517
+ radioDirection="horizontal" <!-- type="radio"일 때: 'horizontal'|'vertical' -->
518
+ />
519
+ ```
1249
520
 
1250
- <script setup>
1251
- import { JSidebarSimple } from '@/components/organisms'
521
+ **장점:**
522
+ - `label` prop 라벨 자동 배치 + 접근성 연결
523
+ - `error-msg` prop → 에러 메시지 자동 표시
524
+ - `orientation` → 레이아웃 일관성
1252
525
 
1253
- const menuItems = [
1254
- { id: '1', label: '홈', path: '/' },
1255
- { id: '2', label: '설정', path: '/settings' },
1256
- ]
526
+ ---
1257
527
 
1258
- const handleMenuClick = (item) => {
1259
- console.log('메뉴 클릭:', item)
1260
- }
1261
- </script>
528
+ ### JCard
529
+
530
+ ```vue
531
+ <JCard title="카드 제목" description="설명" footer="푸터">
532
+ <p>카드 내용</p>
533
+ </JCard>
1262
534
  ```
1263
535
 
1264
536
  ---
1265
537
 
1266
- ## 고급 사용법
538
+ ### JAlert
1267
539
 
1268
- ### JToast/JToaster 사용법
540
+ ```vue
541
+ <JAlert
542
+ variant="default" <!-- 'default'|'destructive' -->
543
+ title="알림"
544
+ description="메시지 내용"
545
+ buttonText="확인"
546
+ :showFooter="true"
547
+ @confirm="handler"
548
+ />
549
+ ```
1269
550
 
1270
- Toast 알림은 전역적으로 메시지를 표시하는 데 사용됩니다. `JToast` 함수와 `JToaster` 컴포넌트를 함께 사용합니다.
551
+ **Slots**: `header`, default, `footer`
1271
552
 
1272
- #### 설치 및 설정
553
+ ---
1273
554
 
1274
- 앱의 루트 레벨에 `<JToaster />` 컴포넌트를 추가해야 합니다:
555
+ ### JTabs
1275
556
 
1276
557
  ```vue
1277
- <template>
1278
- <RouterView />
1279
- <JToaster />
1280
- </template>
558
+ <JTabs
559
+ :tabs="[
560
+ { id: 'tab1', label: '탭 1', closable: false },
561
+ { id: 'tab2', label: '탭 2', closable: true },
562
+ ]"
563
+ v-model:active-tab-id="activeTab"
564
+ @tab-change="handler"
565
+ @tab-close="handler"
566
+ >
567
+ <template #content-tab1>탭 1 내용</template>
568
+ <template #content-tab2>탭 2 내용</template>
569
+ </JTabs>
570
+ ```
1281
571
 
1282
- <script setup>
1283
- import { JToaster } from '@/components/atoms'
1284
- // 또는 NPM 패키지 사용 시
1285
- // import { JToaster } from '@j-solution/components'
1286
- </script>
572
+ ---
573
+
574
+ ### JAccordion
575
+
576
+ ```vue
577
+ <JAccordion
578
+ :items="[
579
+ { value: 'item-1', title: '제목 1', content: '내용 1' },
580
+ { value: 'item-2', title: '제목 2', content: '내용 2' },
581
+ ]"
582
+ type="single"
583
+ collapsible
584
+ />
1287
585
  ```
1288
586
 
1289
- #### 기본 사용법
587
+ ---
588
+
589
+ ### JBreadcrumb
1290
590
 
1291
591
  ```vue
1292
- <script setup>
1293
- import { JToast } from '@/components/atoms'
1294
- // 또는 NPM 패키지 사용
1295
- // import { JToast } from '@j-solution/components'
592
+ <JBreadcrumb :items="[
593
+ { label: '홈', href: '/' },
594
+ { label: '카테고리', href: '/category' },
595
+ { label: '현재 페이지' },
596
+ ]" />
597
+ ```
1296
598
 
1297
- // 기본 Toast
1298
- const showToast = () => {
1299
- JToast('메시지가 표시됩니다')
1300
- }
599
+ ---
1301
600
 
1302
- // Success Toast
1303
- const showSuccess = () => {
1304
- JToast.success('성공적으로 저장되었습니다')
1305
- }
601
+ ### JButtonGroup
1306
602
 
1307
- // Error Toast
1308
- const showError = () => {
1309
- JToast.error('오류가 발생했습니다')
1310
- }
603
+ ```vue
604
+ <JButtonGroup>
605
+ <JButton>저장</JButton>
606
+ <JButton>취소</JButton>
607
+ <JButton>삭제</JButton>
608
+ </JButtonGroup>
609
+ ```
1311
610
 
1312
- // Info Toast
1313
- const showInfo = () => {
1314
- JToast.info('정보 메시지입니다')
1315
- }
611
+ ---
1316
612
 
1317
- // Warning Toast
1318
- const showWarning = () => {
1319
- JToast.warning('경고 메시지입니다')
1320
- }
1321
- </script>
613
+ ### JTitlebar
614
+
615
+ ```vue
616
+ <JTitlebar title="페이지 제목" description="설명">
617
+ <template #actions>
618
+ <JButton>액션</JButton>
619
+ </template>
620
+ </JTitlebar>
1322
621
  ```
1323
622
 
1324
- #### 고급 사용법
623
+ ---
624
+
625
+ ### JContextMenu
1325
626
 
1326
627
  ```vue
1327
- <script setup>
1328
- import { JToast } from '@/components/atoms'
628
+ <JContextMenu :items="menuGroups" :disabled="false" @select="handler">
629
+ <div>우클릭 영역</div>
630
+ </JContextMenu>
631
+ ```
1329
632
 
1330
- // Description이 있는 Toast
1331
- const showWithDescription = () => {
1332
- JToast('제목', {
1333
- description: '추가 설명 텍스트',
1334
- })
1335
- }
633
+ ---
1336
634
 
1337
- // Action 버튼이 있는 Toast
1338
- const showWithAction = () => {
1339
- JToast('제목', {
1340
- description: '설명 텍스트',
1341
- action: {
1342
- label: '실행 취소',
1343
- onClick: () => {
1344
- console.log('실행 취소')
1345
- },
1346
- },
1347
- })
1348
- }
635
+ ### JGroupCombo
1349
636
 
1350
- // Promise Toast (비동기 작업 표시)
1351
- const showPromiseToast = async () => {
1352
- await JToast.promise(
1353
- fetch('/api/data').then(res => res.json()),
637
+ ```vue
638
+ <JGroupCombo
639
+ v-model="selected"
640
+ :groups="[
1354
641
  {
1355
- loading: '로딩 중...',
1356
- success: (data) => `${data.name}이(가) 생성되었습니다`,
1357
- error: '오류가 발생했습니다',
1358
- }
1359
- )
1360
- }
1361
- </script>
642
+ label: '그룹 1',
643
+ options: [
644
+ { value: '1-1', label: '옵션 1-1' },
645
+ { value: '1-2', label: '옵션 1-2' },
646
+ ],
647
+ },
648
+ {
649
+ label: '그룹 2',
650
+ options: [
651
+ { value: '2-1', label: '옵션 2-1' },
652
+ ],
653
+ },
654
+ ]"
655
+ placeholder="그룹에서 선택하세요"
656
+ />
1362
657
  ```
1363
658
 
1364
- **주요 특징:**
1365
- - `JToast`: Toast 메시지를 표시하는 함수 (`JToast.success()`, `JToast.error()` 등)
1366
- - `JToaster`: Toast를 렌더링하는 컨테이너 컴포넌트 (`<JToaster />`)
1367
- - 자동 사라짐: 기본적으로 몇 초 후 자동으로 사라집니다
1368
- - 여러 Toast 동시 표시 가능
1369
- - 위치 설정 가능: `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right`
1370
-
1371
- ### JFormField 사용 권장사항
659
+ ---
1372
660
 
1373
- 필드를 만들 때는 `JFormField`를 사용하세요. `label`, `error-msg` prop으로 값을 입력하면 UI 배치시 라벨과 에러 메시지가 포함됩니다. `type` prop으로 원하는 입력 컴포넌트를 선택할 수 있습니다:
661
+ ### JSearchAddr
1374
662
 
1375
663
  ```vue
1376
- <template>
1377
- <!-- type prop으로 입력 컴포넌트 선택 -->
1378
- <JFormField
1379
- type="input"
1380
- label="이름"
1381
- required
1382
- error-msg="이름을 입력해주세요"
1383
- v-model="name"
1384
- placeholder="이름을 입력하세요"
1385
- />
1386
-
1387
- <JFormField
1388
- type="combo"
1389
- label="카테고리"
1390
- v-model="category"
1391
- :options="categoryOptions"
1392
- />
1393
-
1394
- <JFormField
1395
- type="datepicker"
1396
- label="생년월일"
1397
- v-model="birthDate"
1398
- />
1399
- </template>
664
+ <JSearchAddr
665
+ v-model="address"
666
+ @select="handleAddressSelect"
667
+ />
1400
668
  ```
1401
669
 
1402
- **장점:**
1403
- - `type` prop으로 다양한 입력 컴포넌트 선택 가능 (input, textarea, combo, datepicker 등)
1404
- - `label` prop으로 라벨 값을 입력하면 UI 배치시 라벨이 포함되며, 입력 필드와 자동 연결 (접근성)
1405
- - `error-msg` prop으로 에러 메시지 값을 입력하면 UI 배치시 에러 메시지가 포함됨
1406
- - 레이아웃 일관성 유지
670
+ ---
1407
671
 
1408
- ### JDynamicForm 사용 권장사항
672
+ ## Organisms Props 레퍼런스
1409
673
 
1410
- JSON 스키마로 폼을 자동 생성할 수 있습니다:
674
+ ### JModal
1411
675
 
1412
676
  ```vue
1413
- <template>
1414
- <JDynamicForm
1415
- :schema="formSchema"
1416
- v-model="formData"
1417
- @submit="handleSubmit"
1418
- />
1419
- </template>
677
+ <JModal
678
+ :open="isOpen"
679
+ title="모달 제목"
680
+ description="설명"
681
+ size="md" <!-- 'sm'|'md'|'lg'|'xl'|'2xl'|'full' -->
682
+ buttonType="OkCancel" <!-- 'Ok'|'OkCancel' -->
683
+ confirmText="확인"
684
+ cancelText="취소"
685
+ confirmVariant="default" <!-- 'default'|'destructive'|'outline'|'secondary'|'ghost'|'link' -->
686
+ :confirmDisabled="false"
687
+ :showFormField="false"
688
+ formFieldType="input" <!-- 'input'|'textarea'|'checkbox'|'switch'|'combo'|'radio'|'searchCombo'|'datepicker' -->
689
+ formFieldLabel="필드 라벨"
690
+ formFieldInputType="text"
691
+ formFieldInputPlaceholder="입력"
692
+ :formFieldRequired="false"
693
+ :formFieldValue="''"
694
+ formFieldError=""
695
+ @confirm="handler" <!-- showFormField일 때 입력값 전달 -->
696
+ @cancel="handler"
697
+ @update:open="handler"
698
+ >
699
+ <template #body>
700
+ 커스텀 내용
701
+ </template>
702
+ </JModal>
703
+ ```
1420
704
 
1421
- <script setup>
705
+ ---
706
+
707
+ ### JDynamicForm
708
+
709
+ ```vue
710
+ <JDynamicForm
711
+ :schema="formSchema"
712
+ v-model="formData"
713
+ @submit="handler"
714
+ @change="handler" <!-- { field, value } -->
715
+ @error="handler"
716
+ />
717
+ ```
718
+
719
+ ```ts
1422
720
  const formSchema = {
1423
721
  type: 'simple', // 'simple' | 'sectioned' | 'wizard'
1424
722
  fields: [
1425
723
  {
1426
- controlName: 'name',
724
+ controlName: 'name', // 필드 키
1427
725
  label: '이름',
1428
- type: 'input',
1429
- isRequired: true
726
+ type: 'input', // 'input'|'textarea'|'checkbox'|'switch'|'combo'|'radio'|'searchCombo'|'datepicker'
727
+ isRequired: true,
728
+ inputType: 'text', // type='input'일 때
729
+ placeholder: '이름 입력',
730
+ options: [], // combo/radio/searchCombo일 때
731
+ disabled: false,
1430
732
  },
733
+ ],
734
+ // sectioned 타입일 때
735
+ sections: [
1431
736
  {
1432
- controlName: 'email',
1433
- label: '이메일',
1434
- type: 'input',
1435
- inputType: 'email'
1436
- }
1437
- ]
737
+ title: '섹션 제목',
738
+ fields: [/* fields 배열 */],
739
+ },
740
+ ],
1438
741
  }
1439
- </script>
1440
742
  ```
1441
743
 
1442
- **권장 사용:**
1443
- - `simple`: 단순한 폼
1444
- - `sectioned`: 섹션별로 구분된 폼
1445
- - `wizard`: 단계별 입력 폼
744
+ ---
745
+
746
+ ### JFormModal
747
+
748
+ ```vue
749
+ <JFormModal
750
+ :open="isOpen"
751
+ :schema="formSchema"
752
+ v-model="formData"
753
+ title="폼 모달"
754
+ size="md"
755
+ @confirm="handler"
756
+ @cancel="handler"
757
+ />
758
+ ```
1446
759
 
1447
- ### JDynamicTabs - 경로 기반 컴포넌트 로딩
760
+ ---
1448
761
 
1449
- `JDynamicTabs`는 런타임에 탭을 동적으로 추가/제거할 수 있는 컴포넌트입니다. 경로(`/user/info` 같은)만 가지고 있을 때 해당 경로의 Vue 컴포넌트를 탭 콘텐츠로 표시하는 방법에 대한 가이드입니다.
762
+ ### JSearchPanel
1450
763
 
1451
- #### 핵심 원칙
764
+ ```vue
765
+ <JSearchPanel
766
+ title="조회조건"
767
+ :schema="searchSchema"
768
+ v-model="searchData"
769
+ :defaultCollapsed="false"
770
+ :collapsible="true"
771
+ @submit="handleSearch"
772
+ @reset="handleReset"
773
+ />
774
+ ```
1452
775
 
1453
- **Route 통일성을 최우선으로 고려해야 합니다.**
776
+ > 비어있지 않은 필드는 조건 뱃지로 표시, 개별 필드 초기화 지원
1454
777
 
1455
- - ✅ Router가 있는 프로젝트: Router의 routes를 재사용하여 통일성 보장
1456
- - ✅ Router가 없는 프로젝트: 독립적인 컴포넌트 로딩 방식 사용
778
+ ---
1457
779
 
1458
- #### 방법 1: RouterView 사용 (Router가 있는 경우) ⭐ 권장
780
+ ### JDynamicTabs
1459
781
 
1460
- #### 언제 사용
1461
- - 프로젝트에 Vue Router가 설치되어 있는 경우
1462
- - Router의 routes에 경로-컴포넌트 매핑이 이미 정의되어 있는 경우
1463
- - Route 통일성을 보장해야 하는 경우
782
+ ```vue
783
+ <JDynamicTabs
784
+ ref="tabsRef"
785
+ :initial-tabs="initialTabs"
786
+ defaultActiveId="tab1"
787
+ :maxTabs="0" <!-- 0 = 무제한 -->
788
+ emptyMessage="탭을 추가해주세요."
789
+ @tab-add="handler"
790
+ @tab-change="handler"
791
+ @tab-close="handler"
792
+ >
793
+ <template #content-tab1>내용</template>
794
+ </JDynamicTabs>
795
+ ```
1464
796
 
1465
- #### 장점
1466
- - **Route 통일성 보장**: Router의 routes를 재사용하므로 경로-컴포넌트 매핑 중복 없음
1467
- - ✅ URL 동기화 자동: 브라우저 뒤로가기 지원
1468
- - ✅ 라우트 가드 활용: Router의 네비게이션 가드 사용 가능
1469
- - ✅ 코드 스플리팅 자동: Router의 lazy loading 활용
1470
- - ✅ 유지보수 용이: Router 설정만 수정하면 됨
797
+ **Exposed 메서드:**
798
+ - `tabsRef.value?.addTab({ id, label, closable, meta })`
799
+ - `tabsRef.value?.closeTab(id)`
800
+ - `tabsRef.value?.activateTab(id)`
801
+ - `tabsRef.value?.closeAllTabs()`
1471
802
 
1472
- #### 단점
1473
- - Router 의존적: Vue Router 필수
1474
- - RouterView는 Router의 현재 경로를 사용하므로, 각 탭에 독립적인 경로를 보장하려면 Router 관리 필요
803
+ ---
1475
804
 
1476
- #### 사용 예제
805
+ ### JHeader
1477
806
 
1478
807
  ```vue
1479
- <template>
1480
- <JDynamicTabs ref="tabsRef" :initial-tabs="initialTabs">
1481
- <!-- 경로 기반 동적 슬롯 - RouterView 사용 -->
1482
- <template
1483
- v-for="tab in allTabs"
1484
- :key="tab.id"
1485
- #[`content-${tab.id}`]
1486
- >
1487
- <!-- RouterView: Router의 routes를 재사용하여 통일성 보장 -->
1488
- <RouterView
1489
- v-if="tab.meta?.path"
1490
- :key="tab.id"
1491
- />
1492
- </template>
1493
- </JDynamicTabs>
1494
- </template>
808
+ <JHeader
809
+ :menu-items="menuItems"
810
+ :user="{ name: '홍길동', avatar: 'https://example.com/avatar.jpg' }"
811
+ @menu-click="handler"
812
+ />
813
+ ```
1495
814
 
1496
- <script setup>
1497
- import { ref } from 'vue'
1498
- import { RouterView, useRouter } from 'vue-router'
1499
- import JDynamicTabs from '@/components/organisms/JDynamicTabs.vue'
1500
- import type { DynamicTab } from '@/types/dynamic-tabs.types'
815
+ ---
1501
816
 
1502
- const router = useRouter()
1503
- const tabsRef = ref()
1504
- const allTabs = ref<DynamicTab[]>([])
817
+ ### JPageContainer
1505
818
 
1506
- // 메뉴 클릭 시 탭 추가
1507
- const handleMenuClick = (item) => {
1508
- const tabId = item.id || `tab-${item.path.replace(/[^a-zA-Z0-9]/g, '-')}`
1509
-
1510
- // addTab으로 탭 추가
1511
- tabsRef.value?.addTab({
1512
- id: tabId,
1513
- label: item.label,
1514
- closable: true,
1515
- meta: { path: item.path }, // 경로를 meta에 저장
1516
- })
1517
-
1518
- // allTabs에도 추가 (슬롯 이름 생성용)
1519
- allTabs.value.push({
1520
- id: tabId,
1521
- label: item.label,
1522
- meta: { path: item.path },
1523
- })
1524
-
1525
- // ⭐ Router의 경로로 이동 (Router에 이미 정의된 경로 사용)
1526
- router.push({ path: item.path })
1527
- }
1528
- </script>
819
+ ```vue
820
+ <JPageContainer>
821
+ <template #header>
822
+ <h1>페이지 제목</h1>
823
+ </template>
824
+ <template #content>
825
+ <p>페이지 내용</p>
826
+ </template>
827
+ </JPageContainer>
1529
828
  ```
1530
829
 
1531
- #### 방법 2: 경로 기반 동적 import (Router가 없는 경우)
830
+ ---
1532
831
 
1533
- #### 언제 사용
1534
- - 프로젝트에 Vue Router가 설치되어 있지 않은 경우
1535
- - Router의 routes와 완전히 독립적인 경로가 필요한 경우
1536
- - Router 없이도 동작해야 하는 경우
832
+ ### JSidebarSimple
1537
833
 
1538
- #### 장점
1539
- - ✅ Router 독립적: Vue Router 없이도 사용 가능
1540
- - ✅ 가벼움: 불필요한 라우팅 로직 없음
1541
- - 코드 스플리팅 자동: 동적 import로 성능 최적화
1542
- - 세밀한 제어: 탭별 독립적인 컴포넌트 관리
834
+ ```vue
835
+ <JSidebarSimple
836
+ :menu-items="[
837
+ { id: '1', label: '홈', path: '/' },
838
+ { id: '2', label: '설정', path: '/settings' },
839
+ ]"
840
+ @menu-click="handler"
841
+ />
842
+ ```
1543
843
 
1544
- #### 단점
1545
- - ❌ Router의 routes와 중복 정의 필요
1546
- - ❌ Route 통일성 보장 어려움
1547
- - ❌ 경로 변경 시 두 곳 모두 수정 필요 (Router가 나중에 추가되는 경우)
844
+ ---
1548
845
 
1549
- #### 사용 예제
846
+ ### JSidebarAdvanced
1550
847
 
1551
848
  ```vue
1552
- <template>
1553
- <JDynamicTabs ref="tabsRef" :initial-tabs="initialTabs">
1554
- <template
1555
- v-for="tab in allTabs"
1556
- :key="tab.id"
1557
- #[`content-${tab.id}`]
1558
- >
1559
- <component
1560
- v-if="tab.meta?.path"
1561
- :is="loadComponentByPath(tab.meta.path)"
1562
- :key="tab.id"
1563
- />
1564
- </template>
1565
- </JDynamicTabs>
1566
- </template>
849
+ <JSidebarAdvanced
850
+ :menu-items="[
851
+ { id: '1', label: '홈', path: '/', icon: 'house' },
852
+ { id: '2', label: '설정', path: '/settings', icon: 'settings' },
853
+ ]"
854
+ :favorites="['1']"
855
+ @menu-click="handler"
856
+ />
857
+ ```
1567
858
 
1568
- <script setup>
1569
- import { ref, defineAsyncComponent, h } from 'vue'
1570
- import JDynamicTabs from '@/components/organisms/JDynamicTabs.vue'
1571
- import type { DynamicTab } from '@/types/dynamic-tabs.types'
859
+ ---
1572
860
 
1573
- const tabsRef = ref()
1574
- const allTabs = ref<DynamicTab[]>([])
1575
-
1576
- // 경로 기반 컴포넌트 로드
1577
- const loadComponentByPath = (path: string) => {
1578
- // 경로를 컴포넌트 경로로 변환하는 매핑
1579
- // ⚠️ 주의: Router가 있다면 이 매핑은 Router의 routes와 중복될 수 있음
1580
- const componentMap: Record<string, () => Promise<any>> = {
1581
- '/user/info': () => import('@/pages/UserInfo.vue'),
1582
- '/user/list': () => import('@/pages/UserList.vue'),
1583
- '/product/detail': () => import('@/pages/ProductDetail.vue'),
1584
- }
1585
-
1586
- if (componentMap[path]) {
1587
- return defineAsyncComponent({
1588
- loader: componentMap[path],
1589
- loadingComponent: {
1590
- setup: () => () => h('div', { class: 'p-6' }, '로딩 중...'),
1591
- },
1592
- errorComponent: {
1593
- setup: () => () => h('div', { class: 'p-6 text-red-600' }, '컴포넌트 로드 실패'),
1594
- },
1595
- delay: 200,
1596
- timeout: 3000,
1597
- })
1598
- }
1599
-
1600
- // 매핑되지 않은 경로는 기본 컴포넌트 표시
1601
- return {
1602
- setup: () => () => h('div', { class: 'p-6' }, [
1603
- h('h2', { class: 'text-xl font-bold mb-4' }, '경로: ' + path),
1604
- h('p', { class: 'text-muted-foreground' }, '해당 경로의 컴포넌트를 매핑에 추가하세요.'),
1605
- ])
1606
- }
1607
- }
861
+ ## Templates Props 레퍼런스
1608
862
 
1609
- // 메뉴 클릭 시 탭 추가
1610
- const handleMenuClick = (item) => {
1611
- const tabId = item.id || `tab-${item.path.replace(/[^a-zA-Z0-9]/g, '-')}`
1612
-
1613
- tabsRef.value?.addTab({
1614
- id: tabId,
1615
- label: item.label,
1616
- closable: true,
1617
- meta: { path: item.path },
1618
- })
1619
-
1620
- allTabs.value.push({
1621
- id: tabId,
1622
- label: item.label,
1623
- meta: { path: item.path },
1624
- })
1625
- }
1626
- </script>
863
+ ### JLayout
864
+
865
+ ```vue
866
+ <JLayout>
867
+ <template #header>
868
+ <JHeader :menu-items="menuItems" />
869
+ </template>
870
+ <template #sidebar>
871
+ <JSidebarSimple :menu-items="menuItems" />
872
+ </template>
873
+ <template #content>
874
+ <RouterView />
875
+ </template>
876
+ </JLayout>
1627
877
  ```
1628
878
 
1629
- #### 선택 가이드 (Decision Tree)
879
+ ---
880
+
881
+ ### JLayoutSimple
1630
882
 
883
+ ```vue
884
+ <JLayoutSimple
885
+ :menu-items="menuItems"
886
+ :permissions="permissions"
887
+ :contentScroll="true"
888
+ >
889
+ <template #content>
890
+ <YourPageContent />
891
+ </template>
892
+ </JLayoutSimple>
1631
893
  ```
1632
- 프로젝트에 Vue Router가 있나요?
1633
-
1634
- ├─ 예 → 방법 1 (RouterView 또는 Router의 routes 재사용)
1635
- │ ✅ Route 통일성 보장
1636
- │ ✅ 중복 정의 없음
1637
- │ ✅ 유지보수 용이
1638
-
1639
- └─ 아니오 → 방법 2 (동적 import)
1640
- ✅ Router 독립적
1641
- ✅ 가벼움
1642
- ⚠️ 나중에 Router 추가 시 중복 주의
1643
- ```
1644
894
 
1645
- #### 주의사항
895
+ **Slots**: `header`, `sidebar`, `content`
896
+
897
+ ---
1646
898
 
1647
- ##### ⚠️ 방법 2를 사용 중인 프로젝트에서 Router 추가 시
899
+ ### JLayoutAdvanced
1648
900
 
1649
- 프로젝트에 나중에 Vue Router를 추가하는 경우:
901
+ ```vue
902
+ <JLayoutAdvanced
903
+ :menu-items="menuItems"
904
+ :favorites="favorites"
905
+ :permissions="permissions"
906
+ :contentScroll="true"
907
+ @menu-click="handler"
908
+ @tab-add="handler"
909
+ @tab-change="handler"
910
+ @tab-close="handler"
911
+ @favorite-change="handler"
912
+ >
913
+ <template #content-tab-{id}>
914
+ <RouterView />
915
+ </template>
916
+ </JLayoutAdvanced>
917
+ ```
1650
918
 
1651
- 1. **즉시 방법 1로 마이그레이션 권장**
1652
- - Router의 routes를 재사용하여 통일성 보장
1653
- - 중복 정의 제거
919
+ **Slots**: `header`, `sidebar`, `content`, `content-tab-{id}`
1654
920
 
1655
- 2. **마이그레이션이 어려운 경우**
1656
- - Router의 routes와 `componentMap`을 동기화 유지
1657
- - 경로 변경 시 두 곳 모두 수정
921
+ ---
1658
922
 
1659
- ##### ⚠️ 방법 1 사용 시 주의사항
923
+ ## Toast 사용법
1660
924
 
1661
- - RouterView는 Router의 현재 경로를 사용하므로, 각 탭에 독립적인 경로를 보장하려면 Router 관리 필요
1662
- - 내부에서 라우터 이동 시 전체 앱의 경로가 변경됨 (이를 방지하려면 추가 로직 필요)
925
+ ```vue
926
+ <!-- 루트에 JToaster 추가 -->
927
+ <template>
928
+ <RouterView />
929
+ <JToaster />
930
+ </template>
931
+ ```
1663
932
 
1664
- #### 요약
933
+ ```ts
934
+ import { JToast } from '@/components/atoms'
1665
935
 
1666
- | 항목 | 방법 1 (RouterView) | 방법 2 (동적 import) |
1667
- |------|---------------------|---------------------|
1668
- | **Router 필요** | 필수 | 불필요 |
1669
- | **Route 통일성** | ✅ 보장 (Router routes 재사용) | ❌ 보장 어려움 (중복 정의 필요) |
1670
- | **중복 코드** | 없음 | 경로-컴포넌트 매핑 중복 |
1671
- | **유지보수** | Router만 수정 | 두 곳 모두 수정 필요 |
1672
- | **권장 상황** | Router가 있는 경우 ⭐ | Router가 없는 경우 |
936
+ JToast('기본 메시지')
937
+ JToast.success('성공')
938
+ JToast.error('오류')
939
+ JToast.info('정보')
940
+ JToast.warning('경고')
941
+
942
+ // 옵션
943
+ JToast('제목', { description: '설명', action: { label: '취소', onClick: () => {} } })
944
+
945
+ // Promise
946
+ JToast.promise(fetchData(), {
947
+ loading: '로딩 중...',
948
+ success: (data) => `완료: ${data.name}`,
949
+ error: '오류 발생',
950
+ })
951
+ ```
1673
952
 
1674
953
  ---
1675
954
 
1676
- ## 레이아웃 템플릿 가이드
1677
-
1678
- 템플릿 컴포넌트는 전체 페이지 레이아웃을 구성하는 데 사용됩니다. 프로젝트의 요구사항에 따라 적절한 템플릿을 선택하세요.
955
+ ## 상황별 추천 컴포넌트
956
+
957
+ | 상황 | 컴포넌트 |
958
+ |------|---------|
959
+ | 단일 텍스트 입력 | `JInput` |
960
+ | 여러 줄 텍스트 | `JTextarea` |
961
+ | 날짜 선택 | `JDatepicker` |
962
+ | 옵션 적은 단일 선택 | `JCombo` |
963
+ | 옵션 많은 단일 선택 | `JSearchCombo` |
964
+ | 다중 선택 드롭다운 | `JCombo` `:multiple="true"` |
965
+ | ON/OFF 토글 | `JSwitch` |
966
+ | 체크박스 | `JCheckbox` |
967
+ | 라디오 선택 | `JRadio` |
968
+ | 라벨+에러 포함 폼 필드 | `JFormField` |
969
+ | 스키마 기반 폼 생성 | `JDynamicForm` |
970
+ | 데이터 테이블 | `JGrid` |
971
+ | 모달 다이얼로그 | `JModal` |
972
+ | 폼 모달 | `JFormModal` |
973
+ | 검색 조건 패널 | `JSearchPanel` |
974
+ | 전역 알림 | `JToast` + `JToaster` |
975
+ | 탭 전환 (정적) | `JTabs` |
976
+ | 탭 전환 (동적) | `JDynamicTabs` |
977
+ | 트리 뷰 | `JTree` |
978
+ | 전체 레이아웃 (탭 기반) | `JLayoutAdvanced` |
979
+ | 전체 레이아웃 (단일 페이지) | `JLayoutSimple` |
1679
980
 
1680
- ### JLayout - 기본 레이아웃
981
+ ---
1681
982
 
1682
- 커스텀 레이아웃을 구성할 때 사용합니다.
983
+ ## 주의사항
1683
984
 
1684
- ```vue
1685
- <template>
1686
- <JLayout>
1687
- <template #header>
1688
- <JHeader :menu-items="menuItems" />
1689
- </template>
1690
- <template #sidebar>
1691
- <JSidebarSimple :menu-items="menuItems" />
1692
- </template>
1693
- <template #content>
1694
- <RouterView />
1695
- </template>
1696
- </JLayout>
1697
- </template>
985
+ 1. **JCheckbox / JSwitch의 modelValue는 `'Y'` / `'N'` 문자열** (boolean 아님)
986
+ 2. **JSearchCombo의 modelValue는 `{ value, label }` 객체** (string 아님)
987
+ 3. **JRadio는 `options` 배열**로 항목 정의 (개별 나열 아님)
988
+ 4. **JDatepicker의 modelValue는 `string | null`** (ISO 8601: `'YYYY-MM-DD'`)
989
+ 5. **JGrid의 AG Grid Enterprise 기능** (`enableGrouping`, `enablePivot`, `enableExcelExport`)은 라이선스 필요
990
+ 6. **JDynamicForm 스키마의 필드 `type`과 JFormField의 `type`은 동일한 값 사용**
1698
991
 
1699
- <script setup>
1700
- import { JLayout, JHeader, JSidebarSimple } from '@/components'
1701
- </script>
1702
- ```
992
+ ---
1703
993
 
1704
- ### JLayoutAdvanced 사용
994
+ ## JDynamicTabs 경로 기반 컴포넌트 로딩
1705
995
 
1706
- - 기반 멀티 페이지 애플리케이션
1707
- - 메뉴 클릭 시 자동 탭 생성
1708
- - 검색, 즐겨찾기 기능 필요
1709
- - 권한 관리 필요
996
+ ### 방법 1: RouterView (Router 있는 경우) — 권장
1710
997
 
1711
998
  ```vue
1712
999
  <template>
1713
- <JLayoutAdvanced
1714
- :menu-items="menuItems"
1715
- :favorites="favorites"
1716
- :permissions="permissions"
1717
- >
1718
- <template #content-tab-{id}>
1719
- <RouterView />
1000
+ <JDynamicTabs ref="tabsRef" :initial-tabs="initialTabs">
1001
+ <template v-for="tab in allTabs" :key="tab.id" #[`content-${tab.id}`]>
1002
+ <RouterView v-if="tab.meta?.path" :key="tab.id" />
1720
1003
  </template>
1721
- </JLayoutAdvanced>
1004
+ </JDynamicTabs>
1722
1005
  </template>
1723
1006
 
1724
1007
  <script setup>
1725
- import { JLayoutAdvanced } from '@/components'
1008
+ import { ref } from 'vue'
1009
+ import { useRouter } from 'vue-router'
1010
+
1011
+ const router = useRouter()
1012
+ const tabsRef = ref()
1013
+ const allTabs = ref([])
1014
+
1015
+ const handleMenuClick = (item) => {
1016
+ const tabId = item.id || `tab-${item.path.replace(/[^a-zA-Z0-9]/g, '-')}`
1017
+ tabsRef.value?.addTab({ id: tabId, label: item.label, closable: true, meta: { path: item.path } })
1018
+ allTabs.value.push({ id: tabId, label: item.label, meta: { path: item.path } })
1019
+ router.push({ path: item.path })
1020
+ }
1726
1021
  </script>
1727
1022
  ```
1728
1023
 
1729
- ### JLayoutSimple 사용
1730
-
1731
- - 단일 페이지 애플리케이션
1732
- - 간단한 메뉴 구조
1733
- - 검색 기능만 필요
1024
+ ### 방법 2: 동적 import (Router 없는 경우)
1734
1025
 
1735
1026
  ```vue
1736
- <template>
1737
- <JLayoutSimple :menu-items="menuItems">
1738
- <template #content>
1739
- <YourPageContent />
1740
- </template>
1741
- </JLayoutSimple>
1742
- </template>
1743
-
1744
1027
  <script setup>
1745
- import { JLayoutSimple } from '@/components'
1028
+ import { defineAsyncComponent } from 'vue'
1029
+
1030
+ const componentMap = {
1031
+ '/user/info': () => import('@/pages/UserInfo.vue'),
1032
+ '/user/list': () => import('@/pages/UserList.vue'),
1033
+ }
1034
+
1035
+ const loadComponentByPath = (path) => {
1036
+ if (componentMap[path]) return defineAsyncComponent({ loader: componentMap[path] })
1037
+ return { setup: () => () => h('div', `경로 미등록: ${path}`) }
1038
+ }
1746
1039
  </script>
1747
1040
  ```
1748
1041
 
1749
- > 💡 **레이아웃 컴포넌트 상세 정보**: [LAYOUT_TEMPLATE_GUIDE.md](./LAYOUT_TEMPLATE_GUIDE.md)를 참고하세요.
1042
+ **선택 기준**: Router 있으면 방법 1, 없으면 방법 2
1750
1043
 
1751
1044
  ---
1752
1045
 
1753
- **문서 버전**: v1.2.1
1046
+ **문서 버전**: v1.3.0
1754
1047
  **최종 업데이트**: 2025년 12월
1755
-