@j-solution/components 1.1.1 → 1.2.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 (53) hide show
  1. package/USAGE_GUIDE.md +1068 -61
  2. package/assets/jwms-portal-frontend-D8DdrheA.css +1 -0
  3. package/assets/styles/j-components.css +1 -1
  4. package/components/atoms/JEditor.vue.cjs +7 -0
  5. package/components/atoms/JEditor.vue.cjs.map +1 -0
  6. package/components/atoms/JEditor.vue.js +13 -0
  7. package/components/atoms/JEditor.vue.js.map +1 -0
  8. package/components/atoms/JEditor.vue2.cjs +2 -0
  9. package/components/atoms/JEditor.vue2.cjs.map +1 -0
  10. package/components/atoms/JEditor.vue2.js +43 -0
  11. package/components/atoms/JEditor.vue2.js.map +1 -0
  12. package/components/atoms/JPreview.vue.cjs +7 -0
  13. package/components/atoms/JPreview.vue.cjs.map +1 -0
  14. package/components/atoms/JPreview.vue.js +13 -0
  15. package/components/atoms/JPreview.vue.js.map +1 -0
  16. package/components/atoms/JPreview.vue2.cjs +2 -0
  17. package/components/atoms/JPreview.vue2.cjs.map +1 -0
  18. package/components/atoms/JPreview.vue2.js +121 -0
  19. package/components/atoms/JPreview.vue2.js.map +1 -0
  20. package/components/molecules/JAlert.vue.cjs +1 -1
  21. package/components/molecules/JAlert.vue.cjs.map +1 -1
  22. package/components/molecules/JAlert.vue.js +24 -19
  23. package/components/molecules/JAlert.vue.js.map +1 -1
  24. package/components/molecules/JFormField.vue.cjs +1 -1
  25. package/components/molecules/JFormField.vue.cjs.map +1 -1
  26. package/components/molecules/JFormField.vue.js +7 -2
  27. package/components/molecules/JFormField.vue.js.map +1 -1
  28. package/components/molecules/JTitlebar.vue.cjs +1 -1
  29. package/components/molecules/JTitlebar.vue.cjs.map +1 -1
  30. package/components/molecules/JTitlebar.vue.js +20 -15
  31. package/components/molecules/JTitlebar.vue.js.map +1 -1
  32. package/components/organisms/JDynamicForm.vue2.cjs +1 -1
  33. package/components/organisms/JDynamicForm.vue2.cjs.map +1 -1
  34. package/components/organisms/JDynamicForm.vue2.js +7 -2
  35. package/components/organisms/JDynamicForm.vue2.js.map +1 -1
  36. package/components/organisms/JFormModal.vue.cjs +1 -1
  37. package/components/organisms/JFormModal.vue.cjs.map +1 -1
  38. package/components/organisms/JFormModal.vue.js +20 -15
  39. package/components/organisms/JFormModal.vue.js.map +1 -1
  40. package/components/organisms/JModal.vue.cjs +1 -1
  41. package/components/organisms/JModal.vue.cjs.map +1 -1
  42. package/components/organisms/JModal.vue.js +7 -2
  43. package/components/organisms/JModal.vue.js.map +1 -1
  44. package/components/organisms/JSearchPanel.vue2.cjs +1 -1
  45. package/components/organisms/JSearchPanel.vue2.cjs.map +1 -1
  46. package/components/organisms/JSearchPanel.vue2.js +31 -26
  47. package/components/organisms/JSearchPanel.vue2.js.map +1 -1
  48. package/index.cjs +1 -1
  49. package/index.js +82 -78
  50. package/package.json +3 -1
  51. package/tailwind.config.js +2 -1
  52. package/types/index.d.ts +137 -89
  53. package/assets/jwms-portal-frontend-BqyV9oqF.css +0 -1
package/USAGE_GUIDE.md CHANGED
@@ -59,7 +59,7 @@ import { JButton } from '@/components'
59
59
 
60
60
  ## 컴포넌트 목록
61
61
 
62
- ### Atoms (원자) - 22
62
+ ### Atoms (원자) - 25
63
63
 
64
64
  기본적인 UI 요소들로, 더 이상 분해할 수 없는 최소 단위의 컴포넌트입니다.
65
65
 
@@ -72,6 +72,7 @@ import { JButton } from '@/components'
72
72
  | **JCombo** | 드롭다운 선택 | 단일/다중 선택 |
73
73
  | **JDatepicker** | 날짜 선택기 | 날짜 입력 |
74
74
  | **JDivider** | 구분선 | 콘텐츠 구분 |
75
+ | **JEditor** | 마크다운 에디터 | 마크다운 문서 작성 |
75
76
  | **JGrid** | 데이터 그리드 | 테이블 데이터 표시 (AG Grid 기반) |
76
77
  | **JIcon** | 아이콘 | 시각적 표시 |
77
78
  | **JImage** | 이미지 | 이미지 표시 |
@@ -80,12 +81,14 @@ import { JButton } from '@/components'
80
81
  | **JLabel** | 라벨 | 폼 필드 라벨 |
81
82
  | **JLink** | 링크 | 네비게이션 |
82
83
  | **JPopover** | 팝오버 | 추가 정보 표시 |
84
+ | **JPreview** | 마크다운/HTML 뷰어 | 마크다운 또는 HTML 문서 표시 |
83
85
  | **JProgress** | 진행률 표시 | 작업 진행 상태 |
84
86
  | **JRadio** | 라디오 버튼 | 단일 선택 |
85
87
  | **JSearchCombo** | 검색 가능한 드롭다운 | 많은 옵션 중 검색 선택 |
86
88
  | **JSpinner** | 로딩 스피너 | 로딩 상태 표시 |
87
89
  | **JSwitch** | 토글 스위치 | ON/OFF 설정 |
88
90
  | **JTextarea** | 멀티라인 텍스트 입력 | 여러 라인 텍스트 입력 |
91
+ | **JToaster** | Toast 알림 컨테이너 | 전역 알림 메시지 표시 |
89
92
  | **JTooltip** | 툴팁 | 추가 설명 표시 |
90
93
 
91
94
  ### Molecules (분자) - 11개
@@ -278,6 +281,7 @@ Templates (템플릿)
278
281
  | 상황 | 추천 컴포넌트 | 이유 |
279
282
  |------|--------------|------|
280
283
  | 성공/경고/에러 메시지 | `JAlert` | 타입별 스타일 자동 적용 |
284
+ | 전역 알림 메시지 (Toast) | `JToast` (함수) + `JToaster` (컴포넌트) | 화면 상단/하단에 일시적으로 표시되는 알림 |
281
285
  | 추가 설명 | `JTooltip` | 호버 시 설명 표시 |
282
286
  | 추가 정보 | `JPopover` | 클릭 시 정보 표시 |
283
287
 
@@ -352,6 +356,1018 @@ const enabled = ref(false)
352
356
  </script>
353
357
  ```
354
358
 
359
+ ### 이벤트 처리
360
+
361
+ 컴포넌트는 표준 Vue 이벤트를 발생시킵니다. `v-model`과 함께 이벤트 핸들러를 사용할 수 있습니다:
362
+
363
+ ```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>
398
+ ```
399
+
400
+ ### 컴포넌트별 기본 사용법
401
+
402
+ #### Atoms (원자 컴포넌트)
403
+
404
+ ##### JAvatar - 사용자 아바타
405
+
406
+ ```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>
425
+ ```
426
+
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
+ ```
441
+
442
+ ##### JCheckbox - 체크박스
443
+
444
+ ```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>
457
+ ```
458
+
459
+ ##### JCombo - 드롭다운 선택
460
+
461
+ ```vue
462
+ <template>
463
+ <JCombo
464
+ v-model="selected"
465
+ :options="options"
466
+ placeholder="선택하세요"
467
+ />
468
+ </template>
469
+
470
+ <script setup>
471
+ import { ref } from 'vue'
472
+ import { JCombo } from '@/components/atoms'
473
+
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 - 날짜 선택기
484
+
485
+ ```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>
499
+ ```
500
+
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>
516
+ ```
517
+
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
+ ]
536
+
537
+ const rowData = [
538
+ { name: '홍길동', age: 30, email: 'hong@example.com' },
539
+ { name: '김철수', age: 25, email: 'kim@example.com' },
540
+ ]
541
+ </script>
542
+ ```
543
+
544
+ ##### JIcon - 아이콘
545
+
546
+ ```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>
556
+ ```
557
+
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>
573
+ ```
574
+
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>
584
+
585
+ <script setup>
586
+ import { JKbd } from '@/components/atoms'
587
+ </script>
588
+ ```
589
+
590
+ ##### JLabel - 라벨
591
+
592
+ ```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>
604
+ ```
605
+
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>
638
+ ```
639
+
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'
651
+
652
+ const progress = ref(50)
653
+ </script>
654
+ ```
655
+
656
+ ##### JRadio - 라디오 버튼
657
+
658
+ ```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>
671
+ ```
672
+
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
+ ```
696
+
697
+ ##### JSpinner - 로딩 스피너
698
+
699
+ ```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>
711
+ ```
712
+
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>
726
+ ```
727
+
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
+ ```
746
+
747
+ ##### JTooltip - 툴팁
748
+
749
+ ```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>
759
+ ```
760
+
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
+ },
843
+ ]
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
+
861
+ <script setup>
862
+ import { JAlert } from '@/components/molecules'
863
+
864
+ const handleConfirm = () => {
865
+ console.log('확인 클릭')
866
+ }
867
+ </script>
868
+ ```
869
+
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
+ ```
887
+
888
+ ##### JButtonGroup - 버튼 그룹
889
+
890
+ ```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>
902
+ ```
903
+
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
+ ```
921
+
922
+ ##### JContextMenu - 우클릭 컨텍스트 메뉴
923
+
924
+ ```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>
943
+ ```
944
+
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
+ ```
979
+
980
+ ##### JSearchAddr - 주소 검색
981
+
982
+ ```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>
1000
+ ```
1001
+
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
+ ```
1029
+
1030
+ ##### JTitlebar - 타이틀바
1031
+
1032
+ ```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>
1048
+ ```
1049
+
1050
+ ##### JFormField - 폼 필드 래퍼
1051
+
1052
+ **주요 props**: `type` (input, textarea, checkbox, switch, combo, radio, searchCombo, datepicker), `label`, `description`, `errorMsg`, `modelValue`, `placeholder`, `disabled`, `required`, `orientation` (vertical, horizontal, responsive), `labelAlign`, `labelWidth`
1053
+
1054
+ **권장 사용 시나리오**:
1055
+ - `type` prop으로 원하는 입력 컴포넌트 선택
1056
+ - `label` prop으로 라벨 값을 입력하면 UI 배치시 라벨이 포함되며, 입력 필드와 자동 연결 (접근성)
1057
+ - `errorMsg` prop으로 에러 메시지 값을 입력하면 UI 배치시 에러 메시지가 포함됨
1058
+ - `orientation="horizontal"`: 레이블과 입력 필드를 가로로 배치
1059
+ - `orientation="vertical"`: 레이블과 입력 필드를 세로로 배치 (기본값)
1060
+ - `orientation="responsive"`: 화면 크기에 따라 자동 조정
1061
+ - 레이아웃 일관성 유지
1062
+
1063
+ #### Organisms (유기체 컴포넌트)
1064
+
1065
+ ##### JDynamicForm - 스키마 기반 동적 폼
1066
+
1067
+ **주요 props**: `schema` (JSON 스키마), `modelValue`, `type` (simple, sectioned, wizard)
1068
+
1069
+ **권장 사용 시나리오**:
1070
+ - `type="simple"`: 단순한 폼
1071
+ - `type="sectioned"`: 섹션별로 구분된 폼
1072
+ - `type="wizard"`: 단계별 입력 폼
1073
+ - JSON 스키마로 폼 자동 생성
1074
+ - `schema.fields` 배열에 필드 정의: `controlName`, `label`, `type`, `isRequired` 등
1075
+ - Props 기반의 사용을 권장합니다
1076
+
1077
+ ##### JModal - 모달 다이얼로그
1078
+
1079
+ **주요 props**: `open`, `size` (sm, md, lg, xl, 2xl, full), `title`, `description`, `show-footer`, `confirm-text`, `cancel-text`
1080
+
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 기반의 사용을 권장합니다
1094
+
1095
+ ##### JFormModal - 폼 모달
1096
+
1097
+ ```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>
1108
+
1109
+ <script setup>
1110
+ import { ref } from 'vue'
1111
+ import { JFormModal } from '@/components/organisms'
1112
+
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
+ }
1122
+
1123
+ const handleConfirm = () => {
1124
+ console.log('확인:', formData.value)
1125
+ isOpen.value = false
1126
+ }
1127
+
1128
+ const handleCancel = () => {
1129
+ isOpen.value = false
1130
+ }
1131
+ </script>
1132
+ ```
1133
+
1134
+ ##### JHeader - 애플리케이션 헤더
1135
+
1136
+ ```vue
1137
+ <template>
1138
+ <JHeader
1139
+ :menu-items="menuItems"
1140
+ :user="user"
1141
+ @menu-click="handleMenuClick"
1142
+ />
1143
+ </template>
1144
+
1145
+ <script setup>
1146
+ import { JHeader } from '@/components/organisms'
1147
+
1148
+ const menuItems = [
1149
+ { id: '1', label: '홈', path: '/' },
1150
+ { id: '2', label: '설정', path: '/settings' },
1151
+ ]
1152
+
1153
+ const user = {
1154
+ name: '홍길동',
1155
+ avatar: 'https://example.com/avatar.jpg',
1156
+ }
1157
+
1158
+ const handleMenuClick = (item) => {
1159
+ console.log('메뉴 클릭:', item)
1160
+ }
1161
+ </script>
1162
+ ```
1163
+
1164
+ ##### JPageContainer - 페이지 컨테이너
1165
+
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>
1177
+
1178
+ <script setup>
1179
+ import { JPageContainer } from '@/components/organisms'
1180
+ </script>
1181
+ ```
1182
+
1183
+ ##### JSearchPanel - 검색 조건 패널
1184
+
1185
+ ```vue
1186
+ <template>
1187
+ <JSearchPanel
1188
+ :schema="searchSchema"
1189
+ v-model="searchData"
1190
+ @search="handleSearch"
1191
+ />
1192
+ </template>
1193
+
1194
+ <script setup>
1195
+ import { ref } from 'vue'
1196
+ import { JSearchPanel } from '@/components/organisms'
1197
+
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
+ }
1206
+
1207
+ const handleSearch = (data) => {
1208
+ console.log('검색:', data)
1209
+ }
1210
+ </script>
1211
+ ```
1212
+
1213
+ ##### JSidebarAdvanced - 고급 사이드바
1214
+
1215
+ ```vue
1216
+ <template>
1217
+ <JSidebarAdvanced
1218
+ :menu-items="menuItems"
1219
+ :favorites="favorites"
1220
+ @menu-click="handleMenuClick"
1221
+ />
1222
+ </template>
1223
+
1224
+ <script setup>
1225
+ import { JSidebarAdvanced } from '@/components/organisms'
1226
+
1227
+ const menuItems = [
1228
+ { id: '1', label: '홈', path: '/', icon: 'house' },
1229
+ { id: '2', label: '설정', path: '/settings', icon: 'settings' },
1230
+ ]
1231
+
1232
+ const favorites = ['1']
1233
+
1234
+ const handleMenuClick = (item) => {
1235
+ console.log('메뉴 클릭:', item)
1236
+ }
1237
+ </script>
1238
+ ```
1239
+
1240
+ ##### JSidebarSimple - 간단한 사이드바
1241
+
1242
+ ```vue
1243
+ <template>
1244
+ <JSidebarSimple
1245
+ :menu-items="menuItems"
1246
+ @menu-click="handleMenuClick"
1247
+ />
1248
+ </template>
1249
+
1250
+ <script setup>
1251
+ import { JSidebarSimple } from '@/components/organisms'
1252
+
1253
+ const menuItems = [
1254
+ { id: '1', label: '홈', path: '/' },
1255
+ { id: '2', label: '설정', path: '/settings' },
1256
+ ]
1257
+
1258
+ const handleMenuClick = (item) => {
1259
+ console.log('메뉴 클릭:', item)
1260
+ }
1261
+ </script>
1262
+ ```
1263
+
1264
+ ---
1265
+
1266
+ ## 고급 사용법
1267
+
1268
+ ### JToast/JToaster 사용법
1269
+
1270
+ Toast 알림은 전역적으로 메시지를 표시하는 데 사용됩니다. `JToast` 함수와 `JToaster` 컴포넌트를 함께 사용합니다.
1271
+
1272
+ #### 설치 및 설정
1273
+
1274
+ 앱의 루트 레벨에 `<JToaster />` 컴포넌트를 추가해야 합니다:
1275
+
1276
+ ```vue
1277
+ <template>
1278
+ <RouterView />
1279
+ <JToaster />
1280
+ </template>
1281
+
1282
+ <script setup>
1283
+ import { JToaster } from '@/components/atoms'
1284
+ // 또는 NPM 패키지 사용 시
1285
+ // import { JToaster } from '@j-solution/components'
1286
+ </script>
1287
+ ```
1288
+
1289
+ #### 기본 사용법
1290
+
1291
+ ```vue
1292
+ <script setup>
1293
+ import { JToast } from '@/components/atoms'
1294
+ // 또는 NPM 패키지 사용 시
1295
+ // import { JToast } from '@j-solution/components'
1296
+
1297
+ // 기본 Toast
1298
+ const showToast = () => {
1299
+ JToast('메시지가 표시됩니다')
1300
+ }
1301
+
1302
+ // Success Toast
1303
+ const showSuccess = () => {
1304
+ JToast.success('성공적으로 저장되었습니다')
1305
+ }
1306
+
1307
+ // Error Toast
1308
+ const showError = () => {
1309
+ JToast.error('오류가 발생했습니다')
1310
+ }
1311
+
1312
+ // Info Toast
1313
+ const showInfo = () => {
1314
+ JToast.info('정보 메시지입니다')
1315
+ }
1316
+
1317
+ // Warning Toast
1318
+ const showWarning = () => {
1319
+ JToast.warning('경고 메시지입니다')
1320
+ }
1321
+ </script>
1322
+ ```
1323
+
1324
+ #### 고급 사용법
1325
+
1326
+ ```vue
1327
+ <script setup>
1328
+ import { JToast } from '@/components/atoms'
1329
+
1330
+ // Description이 있는 Toast
1331
+ const showWithDescription = () => {
1332
+ JToast('제목', {
1333
+ description: '추가 설명 텍스트',
1334
+ })
1335
+ }
1336
+
1337
+ // Action 버튼이 있는 Toast
1338
+ const showWithAction = () => {
1339
+ JToast('제목', {
1340
+ description: '설명 텍스트',
1341
+ action: {
1342
+ label: '실행 취소',
1343
+ onClick: () => {
1344
+ console.log('실행 취소')
1345
+ },
1346
+ },
1347
+ })
1348
+ }
1349
+
1350
+ // Promise Toast (비동기 작업 표시)
1351
+ const showPromiseToast = async () => {
1352
+ await JToast.promise(
1353
+ fetch('/api/data').then(res => res.json()),
1354
+ {
1355
+ loading: '로딩 중...',
1356
+ success: (data) => `${data.name}이(가) 생성되었습니다`,
1357
+ error: '오류가 발생했습니다',
1358
+ }
1359
+ )
1360
+ }
1361
+ </script>
1362
+ ```
1363
+
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
+
355
1371
  ### JFormField 사용 권장사항
356
1372
 
357
1373
  폼 필드를 만들 때는 `JFormField`를 사용하세요. `label`, `error-msg` prop으로 값을 입력하면 UI 배치시 라벨과 에러 메시지가 포함됩니다. `type` prop으로 원하는 입력 컴포넌트를 선택할 수 있습니다:
@@ -428,51 +1444,6 @@ const formSchema = {
428
1444
  - `sectioned`: 섹션별로 구분된 폼
429
1445
  - `wizard`: 단계별 입력 폼
430
1446
 
431
- ### 이벤트 처리
432
-
433
- 컴포넌트는 표준 Vue 이벤트를 발생시킵니다. `v-model`과 함께 이벤트 핸들러를 사용할 수 있습니다:
434
-
435
- ```vue
436
- <template>
437
- <JButton @click="handleClick">클릭</JButton>
438
- <JInput v-model="name" @change="handleChange" />
439
- <JModal
440
- :open="isOpen"
441
- @confirm="handleConfirm"
442
- @cancel="handleCancel"
443
- />
444
- </template>
445
-
446
- <script setup>
447
- import { ref } from 'vue'
448
-
449
- const name = ref('')
450
- const isOpen = ref(false)
451
-
452
- const handleClick = () => {
453
- console.log('버튼 클릭')
454
- }
455
-
456
- const handleChange = (value) => {
457
- console.log('값 변경:', value)
458
- }
459
-
460
- const handleConfirm = () => {
461
- console.log('확인')
462
- isOpen.value = false
463
- }
464
-
465
- const handleCancel = () => {
466
- console.log('취소')
467
- isOpen.value = false
468
- }
469
- </script>
470
- ```
471
-
472
- ---
473
-
474
- ## 고급 사용법
475
-
476
1447
  ### JDynamicTabs - 경로 기반 컴포넌트 로딩
477
1448
 
478
1449
  `JDynamicTabs`는 런타임에 탭을 동적으로 추가/제거할 수 있는 컴포넌트입니다. 경로(`/user/info` 같은)만 가지고 있을 때 해당 경로의 Vue 컴포넌트를 탭 콘텐츠로 표시하는 방법에 대한 가이드입니다.
@@ -706,6 +1677,30 @@ const handleMenuClick = (item) => {
706
1677
 
707
1678
  템플릿 컴포넌트는 전체 페이지 레이아웃을 구성하는 데 사용됩니다. 프로젝트의 요구사항에 따라 적절한 템플릿을 선택하세요.
708
1679
 
1680
+ ### JLayout - 기본 레이아웃
1681
+
1682
+ 커스텀 레이아웃을 구성할 때 사용합니다.
1683
+
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>
1698
+
1699
+ <script setup>
1700
+ import { JLayout, JHeader, JSidebarSimple } from '@/components'
1701
+ </script>
1702
+ ```
1703
+
709
1704
  ### JLayoutAdvanced 사용 시
710
1705
 
711
1706
  - 탭 기반 멀티 페이지 애플리케이션
@@ -714,15 +1709,21 @@ const handleMenuClick = (item) => {
714
1709
  - 권한 관리 필요
715
1710
 
716
1711
  ```vue
717
- <JLayoutAdvanced
718
- :menu-items="menuItems"
719
- :favorites="favorites"
720
- :permissions="permissions"
721
- >
722
- <template #content-tab-{id}>
723
- <RouterView />
724
- </template>
725
- </JLayoutAdvanced>
1712
+ <template>
1713
+ <JLayoutAdvanced
1714
+ :menu-items="menuItems"
1715
+ :favorites="favorites"
1716
+ :permissions="permissions"
1717
+ >
1718
+ <template #content-tab-{id}>
1719
+ <RouterView />
1720
+ </template>
1721
+ </JLayoutAdvanced>
1722
+ </template>
1723
+
1724
+ <script setup>
1725
+ import { JLayoutAdvanced } from '@/components'
1726
+ </script>
726
1727
  ```
727
1728
 
728
1729
  ### JLayoutSimple 사용 시
@@ -732,17 +1733,23 @@ const handleMenuClick = (item) => {
732
1733
  - 검색 기능만 필요
733
1734
 
734
1735
  ```vue
735
- <JLayoutSimple :menu-items="menuItems">
736
- <template #content>
737
- <YourPageContent />
738
- </template>
739
- </JLayoutSimple>
1736
+ <template>
1737
+ <JLayoutSimple :menu-items="menuItems">
1738
+ <template #content>
1739
+ <YourPageContent />
1740
+ </template>
1741
+ </JLayoutSimple>
1742
+ </template>
1743
+
1744
+ <script setup>
1745
+ import { JLayoutSimple } from '@/components'
1746
+ </script>
740
1747
  ```
741
1748
 
742
1749
  > 💡 **레이아웃 컴포넌트 상세 정보**: [LAYOUT_TEMPLATE_GUIDE.md](./LAYOUT_TEMPLATE_GUIDE.md)를 참고하세요.
743
1750
 
744
1751
  ---
745
1752
 
746
- **문서 버전**: v1.0.1
1753
+ **문서 버전**: v1.2.0
747
1754
  **최종 업데이트**: 2025년 12월
748
1755