@simplysm/solid 13.0.84 → 13.0.86

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 (180) hide show
  1. package/README.md +143 -28
  2. package/dist/components/data/list/ListItem.d.ts.map +1 -1
  3. package/dist/components/data/list/ListItem.js +11 -4
  4. package/dist/components/data/list/ListItem.js.map +2 -2
  5. package/dist/components/data/list/ListItem.styles.d.ts +2 -0
  6. package/dist/components/data/list/ListItem.styles.d.ts.map +1 -1
  7. package/dist/components/data/list/ListItem.styles.js +11 -1
  8. package/dist/components/data/list/ListItem.styles.js.map +1 -1
  9. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  10. package/dist/components/data/sheet/DataSheet.js +6 -9
  11. package/dist/components/data/sheet/DataSheet.js.map +2 -2
  12. package/dist/components/data/sheet/hooks/createDataSheetExpansion.d.ts.map +1 -1
  13. package/dist/components/data/sheet/hooks/createDataSheetExpansion.js +15 -17
  14. package/dist/components/data/sheet/hooks/createDataSheetExpansion.js.map +1 -1
  15. package/dist/components/data/sheet/hooks/createDataSheetReorder.d.ts.map +1 -1
  16. package/dist/components/data/sheet/hooks/createDataSheetReorder.js +12 -12
  17. package/dist/components/data/sheet/hooks/createDataSheetReorder.js.map +1 -1
  18. package/dist/components/data/sheet/hooks/createDataSheetSelection.d.ts.map +1 -1
  19. package/dist/components/data/sheet/hooks/createDataSheetSelection.js +9 -3
  20. package/dist/components/data/sheet/hooks/createDataSheetSelection.js.map +1 -1
  21. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  22. package/dist/components/disclosure/Dialog.js +3 -21
  23. package/dist/components/disclosure/Dialog.js.map +2 -2
  24. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  25. package/dist/components/disclosure/Dropdown.js +1 -11
  26. package/dist/components/disclosure/Dropdown.js.map +2 -2
  27. package/dist/components/disclosure/Tabs.d.ts.map +1 -1
  28. package/dist/components/disclosure/Tabs.js +1 -3
  29. package/dist/components/disclosure/Tabs.js.map +2 -2
  30. package/dist/components/features/crud-detail/CrudDetail.js +103 -102
  31. package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
  32. package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
  33. package/dist/components/features/crud-sheet/CrudSheet.js +10 -5
  34. package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
  35. package/dist/components/features/data-select-button/DataSelectButton.d.ts.map +1 -1
  36. package/dist/components/features/data-select-button/DataSelectButton.js +30 -26
  37. package/dist/components/features/data-select-button/DataSelectButton.js.map +2 -2
  38. package/dist/components/features/permission-table/PermissionTable.js +5 -1
  39. package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
  40. package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -1
  41. package/dist/components/feedback/busy/BusyContainer.js +1 -6
  42. package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
  43. package/dist/components/form-control/DropdownTrigger.styles.js +1 -1
  44. package/dist/components/form-control/checkbox/SelectableBase.d.ts.map +1 -1
  45. package/dist/components/form-control/checkbox/SelectableBase.js +2 -4
  46. package/dist/components/form-control/checkbox/SelectableBase.js.map +2 -2
  47. package/dist/components/form-control/combobox/Combobox.d.ts +19 -5
  48. package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
  49. package/dist/components/form-control/combobox/Combobox.js +2 -4
  50. package/dist/components/form-control/combobox/Combobox.js.map +1 -1
  51. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +2 -2
  52. package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
  53. package/dist/components/form-control/date-range-picker/DateRangePicker.js +11 -3
  54. package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
  55. package/dist/components/form-control/editor/RichTextEditor.d.ts +2 -2
  56. package/dist/components/form-control/editor/RichTextEditor.d.ts.map +1 -1
  57. package/dist/components/form-control/editor/RichTextEditor.js +2 -4
  58. package/dist/components/form-control/editor/RichTextEditor.js.map +2 -2
  59. package/dist/components/form-control/field/DatePicker.d.ts +2 -2
  60. package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
  61. package/dist/components/form-control/field/DatePicker.js.map +1 -1
  62. package/dist/components/form-control/field/DateTimePicker.d.ts +2 -2
  63. package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
  64. package/dist/components/form-control/field/DateTimePicker.js.map +1 -1
  65. package/dist/components/form-control/field/Field.styles.d.ts +6 -7
  66. package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
  67. package/dist/components/form-control/field/Field.styles.js.map +1 -1
  68. package/dist/components/form-control/field/NumberInput.d.ts +2 -2
  69. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  70. package/dist/components/form-control/field/NumberInput.js +7 -7
  71. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  72. package/dist/components/form-control/field/TextInput.d.ts +2 -2
  73. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  74. package/dist/components/form-control/field/TextInput.js.map +1 -1
  75. package/dist/components/form-control/field/Textarea.d.ts +2 -2
  76. package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
  77. package/dist/components/form-control/field/Textarea.js +1 -3
  78. package/dist/components/form-control/field/Textarea.js.map +2 -2
  79. package/dist/components/form-control/field/TimePicker.d.ts +2 -2
  80. package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
  81. package/dist/components/form-control/field/TimePicker.js.map +1 -1
  82. package/dist/components/form-control/numpad/Numpad.d.ts.map +1 -1
  83. package/dist/components/form-control/numpad/Numpad.js +4 -17
  84. package/dist/components/form-control/numpad/Numpad.js.map +2 -2
  85. package/dist/components/form-control/select/Select.d.ts +2 -0
  86. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  87. package/dist/components/form-control/select/Select.js +29 -15
  88. package/dist/components/form-control/select/Select.js.map +2 -2
  89. package/dist/components/form-control/state-preset/StatePreset.d.ts +1 -3
  90. package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
  91. package/dist/components/form-control/state-preset/StatePreset.js +69 -95
  92. package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
  93. package/dist/components/layout/FormGroup.js +1 -1
  94. package/dist/components/layout/FormGroup.js.map +1 -1
  95. package/dist/components/layout/FormTable.js +3 -3
  96. package/dist/components/layout/FormTable.js.map +1 -1
  97. package/dist/components/layout/sidebar/Sidebar.d.ts.map +1 -1
  98. package/dist/components/layout/sidebar/Sidebar.js +3 -6
  99. package/dist/components/layout/sidebar/Sidebar.js.map +2 -2
  100. package/dist/components/layout/topbar/Topbar.js +1 -3
  101. package/dist/components/layout/topbar/Topbar.js.map +2 -2
  102. package/dist/hooks/createControllableStore.d.ts.map +1 -1
  103. package/dist/hooks/createControllableStore.js +8 -5
  104. package/dist/hooks/createControllableStore.js.map +1 -1
  105. package/dist/hooks/useLocalStorage.d.ts.map +1 -1
  106. package/dist/hooks/useLocalStorage.js +3 -2
  107. package/dist/hooks/useLocalStorage.js.map +1 -1
  108. package/dist/hooks/useSyncConfig.d.ts.map +1 -1
  109. package/dist/hooks/useSyncConfig.js +5 -4
  110. package/dist/hooks/useSyncConfig.js.map +1 -1
  111. package/dist/providers/i18n/locales/en.d.ts +2 -3
  112. package/dist/providers/i18n/locales/en.d.ts.map +1 -1
  113. package/dist/providers/i18n/locales/en.js +3 -4
  114. package/dist/providers/i18n/locales/en.js.map +1 -1
  115. package/dist/providers/i18n/locales/ko.d.ts +2 -3
  116. package/dist/providers/i18n/locales/ko.d.ts.map +1 -1
  117. package/dist/providers/i18n/locales/ko.js +3 -4
  118. package/dist/providers/i18n/locales/ko.js.map +1 -1
  119. package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
  120. package/dist/providers/shared-data/SharedDataProvider.js +0 -1
  121. package/dist/providers/shared-data/SharedDataProvider.js.map +1 -1
  122. package/docs/display-feedback.md +279 -0
  123. package/docs/features.md +357 -213
  124. package/docs/form-controls.md +261 -403
  125. package/docs/layout-data.md +386 -0
  126. package/docs/providers-hooks.md +411 -0
  127. package/package.json +5 -5
  128. package/src/components/data/list/ListItem.styles.ts +14 -2
  129. package/src/components/data/list/ListItem.tsx +13 -4
  130. package/src/components/data/sheet/DataSheet.tsx +6 -10
  131. package/src/components/data/sheet/hooks/createDataSheetExpansion.ts +17 -18
  132. package/src/components/data/sheet/hooks/createDataSheetReorder.ts +12 -13
  133. package/src/components/data/sheet/hooks/createDataSheetSelection.ts +9 -3
  134. package/src/components/disclosure/Dialog.tsx +45 -59
  135. package/src/components/disclosure/Dropdown.tsx +4 -14
  136. package/src/components/disclosure/Tabs.tsx +12 -17
  137. package/src/components/features/crud-detail/CrudDetail.tsx +4 -4
  138. package/src/components/features/crud-sheet/CrudSheet.tsx +12 -5
  139. package/src/components/features/data-select-button/DataSelectButton.tsx +39 -32
  140. package/src/components/features/permission-table/PermissionTable.tsx +1 -1
  141. package/src/components/feedback/busy/BusyContainer.tsx +12 -18
  142. package/src/components/form-control/DropdownTrigger.styles.ts +1 -1
  143. package/src/components/form-control/checkbox/SelectableBase.tsx +10 -16
  144. package/src/components/form-control/combobox/Combobox.tsx +42 -16
  145. package/src/components/form-control/date-range-picker/DateRangePicker.tsx +7 -8
  146. package/src/components/form-control/editor/RichTextEditor.tsx +14 -16
  147. package/src/components/form-control/field/DatePicker.tsx +3 -2
  148. package/src/components/form-control/field/DateTimePicker.tsx +3 -2
  149. package/src/components/form-control/field/Field.styles.ts +6 -8
  150. package/src/components/form-control/field/NumberInput.tsx +9 -10
  151. package/src/components/form-control/field/TextInput.tsx +3 -2
  152. package/src/components/form-control/field/Textarea.tsx +14 -12
  153. package/src/components/form-control/field/TimePicker.tsx +3 -2
  154. package/src/components/form-control/numpad/Numpad.tsx +16 -18
  155. package/src/components/form-control/select/Select.tsx +41 -13
  156. package/src/components/form-control/state-preset/StatePreset.tsx +39 -71
  157. package/src/components/layout/FormGroup.tsx +1 -1
  158. package/src/components/layout/FormTable.tsx +3 -3
  159. package/src/components/layout/sidebar/Sidebar.tsx +2 -3
  160. package/src/components/layout/topbar/Topbar.tsx +2 -2
  161. package/src/hooks/createControllableStore.ts +8 -4
  162. package/src/hooks/useLocalStorage.ts +3 -2
  163. package/src/hooks/useSyncConfig.ts +5 -4
  164. package/src/providers/i18n/locales/en.ts +2 -3
  165. package/src/providers/i18n/locales/ko.ts +2 -3
  166. package/src/providers/shared-data/SharedDataProvider.tsx +0 -1
  167. package/tests/components/features/crud-detail/CrudDetail.spec.tsx +49 -0
  168. package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +62 -7
  169. package/tests/components/form-control/combobox/Combobox.spec.tsx +3 -3
  170. package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +56 -0
  171. package/tests/components/form-control/select/SelectItem.spec.tsx +5 -0
  172. package/tests/providers/shared-data/SharedDataProvider.spec.tsx +0 -104
  173. package/docs/data.md +0 -204
  174. package/docs/disclosure.md +0 -146
  175. package/docs/display.md +0 -125
  176. package/docs/feedback.md +0 -156
  177. package/docs/helpers.md +0 -173
  178. package/docs/hooks.md +0 -146
  179. package/docs/layout.md +0 -94
  180. package/docs/providers.md +0 -180
package/docs/features.md CHANGED
@@ -1,282 +1,426 @@
1
- # Features
1
+ # 기능 컴포넌트
2
2
 
3
- High-level, domain-specific components built on top of the core primitives.
3
+ 엔터프라이즈 비즈니스 로직을 캡슐화한 고수준 컴포넌트.
4
4
 
5
- ## CrudSheet
5
+ ## CRUD 컴포넌트 동작 모드
6
6
 
7
- ```typescript
8
- interface CrudSheetBaseProps<TItem, TFilter> {
9
- search(filter: TFilter, page: number | undefined, sorts: SortingDef[]): Promise<SearchResult<TItem>>;
10
- getItemKey(item: TItem): string | number | undefined;
11
- storageKey?: string;
12
- editable?: boolean;
13
- isItemEditable?: (item: TItem) => boolean;
14
- isItemDeletable?: (item: TItem) => boolean;
15
- isItemDeleted?: (item: TItem) => boolean;
16
- isItemSelectable?: (item: TItem) => boolean | string;
17
- lastModifiedAtProp?: string;
18
- lastModifiedByProp?: string;
19
- filterInitial?: TFilter;
20
- items?: TItem[];
21
- onItemsChange?: (items: TItem[]) => void;
22
- excel?: ExcelConfig<TItem>;
23
- selectionMode?: "single" | "multiple";
24
- onSelect?: (result: SelectResult<TItem>) => void;
25
- onSubmitComplete?: () => void;
26
- hideAutoTools?: boolean;
27
- close?: () => void;
28
- class?: string;
29
- children: JSX.Element;
30
- }
31
- ```
7
+ `CrudSheet`과 `CrudDetail`은 겸용 컴포넌트로, `close` prop 전달 여부와 `Topbar` 컨텍스트에 따라 3가지 모드로 동작한다. CRUD 기반 컴포넌트를 만들 때는 관례적으로 `close` prop을 받아 그대로 전달한다.
32
8
 
33
- Full CRUD data grid built on `DataSheet`. Supports two editing modes:
9
+ | 모드 | 조건 | 툴바 위치 | 저장 동작 |
10
+ |------|------|-----------|-------------|
11
+ | **Dialog** | `close` 전달됨 | Dialog 헤더에 refresh, 하단에 저장/삭제 액션바 | `close(true)`로 다이얼로그 닫기 |
12
+ | **Page** | `close` 미전달 + `Topbar` 내부 | `Topbar`에 저장/삭제/새로고침 버튼 주입 | 데이터 재조회 |
13
+ | **Control** | `close` 미전달 + `Topbar` 외부 | 자체 인라인 툴바 표시 | 데이터 재조회 |
34
14
 
35
- **Inline editing** — edit cells directly in the grid:
36
- ```typescript
37
- interface InlineEditConfig<TItem> {
38
- submit(diffs: ArrayOneWayDiffResult<TItem>[]): Promise<void>;
39
- newItem(): TItem;
40
- deleteProp?: keyof TItem & string;
41
- diffsExcludes?: string[];
15
+ ```tsx
16
+ // CRUD 기반 컴포넌트는 close prop을 받아 그대로 전달하는 것이 관례
17
+ function UserList(props: { close?: (result?: boolean) => void }) {
18
+ return (
19
+ <CrudSheet close={props.close} search={...} getItemKey={...} inlineEdit={...}>
20
+ ...
21
+ </CrudSheet>
22
+ );
42
23
  }
43
- ```
44
24
 
45
- **Dialog editing** edit records in a dialog:
46
- ```typescript
47
- interface DialogEditConfig<TItem> {
48
- editItem(item?: TItem): Promise<boolean | undefined>;
49
- deleteItems?(items: TItem[]): Promise<boolean>;
50
- restoreItems?(items: TItem[]): Promise<boolean>;
51
- }
52
- ```
25
+ // Dialog 모드 -- close가 전달되어 모달로 동작
26
+ const result = await dialog.open(UserList);
53
27
 
54
- **Sub-components:**
55
- - `CrudSheet.Column` extends `DataSheet.Column` with `editTrigger` prop
56
- - `CrudSheet.Filter` — filter form area
57
- - `CrudSheet.Tools` — custom toolbar buttons
58
- - `CrudSheet.Header` — custom header content
28
+ // Page 모드 -- Topbar 하위 라우트에 배치, close 미전달
29
+ <Route path="/users" component={UserList} />
59
30
 
60
- **Context:** `CrudSheetContext<TItem>` access internal state via context:
61
- ```typescript
62
- interface CrudSheetContext<TItem> {
63
- items(): TItem[];
64
- selection(): TItem[];
65
- page(): number;
66
- sorts(): SortingDef[];
67
- busy(): boolean;
68
- hasChanges(): boolean;
69
- save(): Promise<void>;
70
- refresh(): Promise<void>;
71
- addItem(): void;
72
- clearSelection(): void;
73
- setPage(page: number): void;
74
- setSorts(sorts: SortingDef[]): void;
75
- }
31
+ // Control 모드 -- 다른 페이지의 일부로 임베드, close 미전달
32
+ <div class="grid grid-cols-2">
33
+ <UserList />
34
+ <OrderList />
35
+ </div>
76
36
  ```
77
37
 
78
38
  ---
79
39
 
80
- ## CrudDetail
81
-
82
- ```typescript
83
- interface CrudDetailProps<TData extends object> {
84
- load(): Promise<{ data: TData; info: CrudDetailInfo }>;
85
- children(ctx: CrudDetailContext<TData>): JSX.Element;
86
- submit?(data: TData): Promise<boolean | undefined>;
87
- toggleDelete?(del: boolean): Promise<boolean | undefined>;
88
- editable?: boolean;
89
- deletable?: boolean;
90
- data?: TData;
91
- onDataChange?: (data: TData) => void;
92
- close?(result?: boolean): void;
93
- class?: string;
94
- }
40
+ ## CrudSheet
95
41
 
96
- interface CrudDetailContext<TData> {
97
- data: TData;
98
- setData: SetStoreFunction<TData>;
99
- info(): CrudDetailInfo;
100
- busy(): boolean;
101
- hasChanges(): boolean;
102
- save(): Promise<void>;
103
- refresh(): Promise<void>;
42
+ DataSheet 기반 CRUD 기능 통합 컴포넌트. 인라인/다이얼로그 편집, 검색, Excel 가져오기/내보내기, 배치 작업을 지원한다.
43
+
44
+ 편집 방식은 `inlineEdit`과 `dialogEdit` 중 하나를 선택한다 (동시 사용 불가).
45
+
46
+ - **inlineEdit**: 셀 직접 편집, 행 추가/삭제, diff 기반 일괄 저장
47
+ - **dialogEdit**: 행 클릭 시 다이얼로그로 편집, 선택 항목 일괄 삭제/복원
48
+
49
+ ```tsx
50
+ import { CrudSheet } from "@simplysm/solid";
51
+
52
+ // 인라인 편집
53
+ function UserSheet(props: { close?: (result?: boolean) => void }) {
54
+ return (
55
+ <CrudSheet
56
+ close={props.close}
57
+ search={(filter, page, sorts) => fetchUsers(filter, page, sorts)}
58
+ getItemKey={(item) => item.id}
59
+ inlineEdit={{
60
+ submit: (diffs) => saveUsers(diffs),
61
+ newItem: () => ({ id: undefined, name: "", email: "" }),
62
+ deleteProp: "isDeleted",
63
+ }}
64
+ >
65
+ <CrudSheet.Column header="이름" key="name">
66
+ {(ctx) => <TextInput value={ctx.item.name} onValueChange={(v) => ctx.setItem("name", v)} />}
67
+ </CrudSheet.Column>
68
+ </CrudSheet>
69
+ );
104
70
  }
105
71
 
106
- interface CrudDetailInfo {
107
- isNew: boolean;
108
- isDeleted: boolean;
109
- lastModifiedAt?: DateTime;
110
- lastModifiedBy?: string;
72
+ // 다이얼로그 편집
73
+ function ProductSheet(props: { close?: (result?: boolean) => void }) {
74
+ const dialog = useDialog();
75
+ return (
76
+ <CrudSheet
77
+ close={props.close}
78
+ search={(filter, page, sorts) => fetchProducts(filter, page, sorts)}
79
+ getItemKey={(item) => item.id}
80
+ dialogEdit={{
81
+ editItem: (item) => dialog.open(ProductDetail, { item }),
82
+ deleteItems: (items) => deleteProducts(items),
83
+ }}
84
+ >
85
+ <CrudSheet.Column header="상품명" key="name" editTrigger>
86
+ {(ctx) => ctx.item.name}
87
+ </CrudSheet.Column>
88
+ </CrudSheet>
89
+ );
111
90
  }
112
91
  ```
113
92
 
114
- Single-record CRUD detail form. `load` fetches data, `submit` saves changes, `toggleDelete` handles soft-delete. The `children` render prop receives a context with reactive data and mutation helpers.
93
+ ### CrudSheet 서브 컴포넌트
115
94
 
116
- **Sub-components:**
117
- - `CrudDetail.Tools` — custom toolbar
118
- - `CrudDetail.Before` content before the form
119
- - `CrudDetail.After` content after the form
95
+ | 컴포넌트 | 설명 |
96
+ |----------|------|
97
+ | `CrudSheet.Column` | 컬럼 정의. `editTrigger`로 dialogEdit 시 클릭 편집 링크 표시 |
98
+ | `CrudSheet.Filter` | 검색 필터 영역 |
99
+ | `CrudSheet.Tools` | 커스텀 툴바 버튼 (`CrudSheetContext` 전달) |
100
+ | `CrudSheet.Header` | 시트 상단 커스텀 헤더 영역 |
120
101
 
121
102
  ---
122
103
 
123
- ## SharedDataSelect
104
+ ## CrudDetail
124
105
 
125
- ```typescript
126
- interface SharedDataSelectCommonProps<TItem, TKey, TDialogProps> {
127
- data: SharedDataAccessor<TItem>;
128
- required?: boolean;
129
- disabled?: boolean;
130
- size?: ComponentSize;
131
- inset?: boolean;
132
- filterFn?: (item: TItem, index: number) => boolean;
133
- dialog?: Component<TDialogProps>;
134
- dialogOptions?: DialogShowOptions;
135
- children: JSX.Element;
106
+ 단건 레코드 편집 폼. 저장/삭제/복원, 변경사항 자동 추적을 지원한다.
107
+
108
+ ```tsx
109
+ import { CrudDetail } from "@simplysm/solid";
110
+
111
+ function UserDetail(props: { close?: (result?: boolean) => void; userId: number }) {
112
+ return (
113
+ <CrudDetail
114
+ close={props.close}
115
+ load={() => fetchUser(props.userId)}
116
+ submit={(data) => saveUser(data)}
117
+ toggleDelete={(del) => toggleDeleteUser(props.userId, del)}
118
+ >
119
+ {(ctx) => (
120
+ <FormGroup>
121
+ <FormGroup.Item label="이름">
122
+ <TextInput value={ctx.data.name} onValueChange={(v) => ctx.setData("name", v)} />
123
+ </FormGroup.Item>
124
+ </FormGroup>
125
+ )}
126
+ </CrudDetail>
127
+ );
136
128
  }
137
129
 
138
- // Single: { multiple?: false; value?: TKey; onValueChange?: (value: TKey | undefined) => void }
139
- // Multiple: { multiple: true; value?: TKey[]; onValueChange?: (value: TKey[]) => void }
130
+ // Dialog로 열기
131
+ const result = await dialog.open(UserDetail, { userId: 123 });
140
132
  ```
141
133
 
142
- Select component bound to `SharedDataProvider` data. Automatically uses the shared data's search text and hidden item logic. Optionally opens a dialog for advanced selection.
134
+ ### CrudDetail 서브 컴포넌트
143
135
 
144
- **Sub-components:** `SharedDataSelect.ItemTemplate`, `SharedDataSelect.Action`
136
+ | 컴포넌트 | 설명 |
137
+ |----------|------|
138
+ | `CrudDetail.Tools` | 커스텀 툴바 버튼 |
139
+ | `CrudDetail.Before` | 폼 위쪽 영역 (폼 외부) |
140
+ | `CrudDetail.After` | 폼 아래쪽 영역 (폼 외부) |
145
141
 
146
142
  ---
147
143
 
148
- ## SharedDataSelectButton
144
+ ## DataSelectButton
149
145
 
150
- ```typescript
151
- interface SharedDataSelectButtonCommonProps<TItem, TDialogProps> {
152
- data: SharedDataAccessor<TItem>;
153
- required?: boolean;
154
- disabled?: boolean;
155
- size?: ComponentSize;
156
- inset?: boolean;
157
- dialog: Component<TDialogProps>;
158
- dialogOptions?: DialogShowOptions;
159
- children(item: TItem): JSX.Element;
146
+ 다이얼로그 기반 데이터 선택 버튼. 선택 다이얼로그를 열어 항목을 선택하고, 선택된 항목을 표시한다.
147
+
148
+ ```tsx
149
+ import { DataSelectButton, type SelectDialogBaseProps } from "@simplysm/solid";
150
+
151
+ // 선택 다이얼로그 컴포넌트 정의
152
+ function UserSelectDialog(props: SelectDialogBaseProps & { department?: string }) {
153
+ // props.selectionMode: "single" | "multiple"
154
+ // props.selectedKeys: 기존 선택된 키 배열
155
+ // props.close: 결과 반환 함수
156
+
157
+ const handleSelect = (userIds: number[]) => {
158
+ props.close?.({ selectedKeys: userIds });
159
+ };
160
+
161
+ return <div>...</div>;
160
162
  }
163
+
164
+ // 단일 선택
165
+ <DataSelectButton
166
+ value={selectedUserId()}
167
+ onValueChange={setSelectedUserId}
168
+ load={async (keys) => await loadUsers(keys)}
169
+ renderItem={(user) => <span>{user.name}</span>}
170
+ dialog={UserSelectDialog}
171
+ dialogOptions={{ header: "사용자 선택", width: "600px" }}
172
+ />
173
+
174
+ // 다중 선택
175
+ <DataSelectButton
176
+ multiple
177
+ value={selectedUserIds()}
178
+ onValueChange={setSelectedUserIds}
179
+ load={async (keys) => await loadUsers(keys)}
180
+ renderItem={(user) => <span>{user.name}</span>}
181
+ dialog={UserSelectDialog}
182
+ dialogOptions={{ header: "사용자 선택" }}
183
+ />
184
+
185
+ // 다이얼로그에 추가 props 전달
186
+ <DataSelectButton
187
+ value={selectedUserId()}
188
+ onValueChange={setSelectedUserId}
189
+ load={loadUsers}
190
+ renderItem={(user) => <span>{user.name}</span>}
191
+ dialog={UserSelectDialog}
192
+ dialogProps={{ department: "engineering" }}
193
+ />
161
194
  ```
162
195
 
163
- Button that opens a dialog for selecting shared data items. Displays the selected item(s) using the `children` render prop.
196
+ ### SelectDialogBaseProps (선택 다이얼로그 필수 props)
197
+
198
+ ```typescript
199
+ interface SelectDialogBaseProps<TKey = string | number> {
200
+ close?: (result?: DataSelectDialogResult<TKey>) => void;
201
+ selectionMode: "single" | "multiple";
202
+ selectedKeys: TKey[];
203
+ }
204
+
205
+ interface DataSelectDialogResult<TKey> {
206
+ selectedKeys: TKey[];
207
+ }
208
+ ```
164
209
 
165
210
  ---
166
211
 
167
- ## SharedDataSelectList
212
+ ## SharedDataSelect
168
213
 
169
- ```typescript
170
- interface SharedDataSelectListProps<TItem> {
171
- data: SharedDataAccessor<TItem>;
172
- value?: TItem;
173
- onValueChange?: (value: TItem | undefined) => void;
174
- required?: boolean;
175
- disabled?: boolean;
176
- filterFn?: (item: TItem, index: number) => boolean;
177
- canChange?: (item: TItem | undefined) => boolean | Promise<boolean>;
178
- pageSize?: number;
179
- header?: JSX.Element;
180
- children?: JSX.Element;
181
- class?: string;
182
- style?: JSX.CSSProperties;
183
- }
214
+ SharedData 기반 Select 드롭다운. SharedDataAccessor를 연결하면 자동으로 아이템 목록, 검색, 트리 구조를 지원한다.
215
+
216
+ ```tsx
217
+ import { SharedDataSelect } from "@simplysm/solid";
218
+
219
+ const sharedData = useSharedData<{ users: User }>();
220
+
221
+ // 단일 선택
222
+ <SharedDataSelect
223
+ data={sharedData.users}
224
+ value={selectedUserId()}
225
+ onValueChange={setSelectedUserId}
226
+ >
227
+ <SharedDataSelect.ItemTemplate>
228
+ {(item: User) => <span>{item.name}</span>}
229
+ </SharedDataSelect.ItemTemplate>
230
+ </SharedDataSelect>
231
+
232
+ // 다중 선택
233
+ <SharedDataSelect
234
+ multiple
235
+ data={sharedData.users}
236
+ value={selectedUserIds()}
237
+ onValueChange={setSelectedUserIds}
238
+ >
239
+ <SharedDataSelect.ItemTemplate>
240
+ {(item: User) => <span>{item.name}</span>}
241
+ </SharedDataSelect.ItemTemplate>
242
+ </SharedDataSelect>
243
+
244
+ // 다이얼로그 검색 연동
245
+ <SharedDataSelect
246
+ data={sharedData.users}
247
+ value={selectedUserId()}
248
+ onValueChange={setSelectedUserId}
249
+ dialog={UserSearchDialog}
250
+ dialogOptions={{ header: "사용자 검색" }}
251
+ >
252
+ <SharedDataSelect.ItemTemplate>
253
+ {(item: User) => <span>{item.name}</span>}
254
+ </SharedDataSelect.ItemTemplate>
255
+ <SharedDataSelect.Action onClick={handleCustomAction}>
256
+ <Icon icon={IconPlus} />
257
+ </SharedDataSelect.Action>
258
+ </SharedDataSelect>
259
+
260
+ // 아이템 필터링
261
+ <SharedDataSelect
262
+ data={sharedData.departments}
263
+ value={deptId()}
264
+ onValueChange={setDeptId}
265
+ filterFn={(item) => item.isActive}
266
+ >
267
+ <SharedDataSelect.ItemTemplate>
268
+ {(item) => <span>{item.name}</span>}
269
+ </SharedDataSelect.ItemTemplate>
270
+ </SharedDataSelect>
184
271
  ```
185
272
 
186
- List view for selecting from shared data. Supports pagination, filtering, and custom item templates.
273
+ | Prop | 타입 | 설명 |
274
+ |------|------|------|
275
+ | `data` | `SharedDataAccessor<TItem>` | 공유 데이터 접근자 |
276
+ | `value` / `onValueChange` | `TKey \| TKey[]` | 선택된 키 값 |
277
+ | `multiple` | `boolean` | 다중 선택 모드 |
278
+ | `filterFn` | `(item, index) => boolean` | 아이템 필터 |
279
+ | `dialog` | `Component<TDialogProps>` | 검색 다이얼로그 컴포넌트 |
280
+ | `dialogOptions` | `DialogShowOptions` | 다이얼로그 옵션 |
281
+ | `required`, `disabled`, `size`, `inset` | | 공통 폼 컨트롤 props |
282
+
283
+ ### 서브 컴포넌트
187
284
 
188
- **Sub-components:** `SharedDataSelectList.ItemTemplate`, `SharedDataSelectList.Filter`
285
+ | 컴포넌트 | 설명 |
286
+ |----------|------|
287
+ | `SharedDataSelect.ItemTemplate` | 드롭다운 아이템 렌더링 커스터마이징 |
288
+ | `SharedDataSelect.Action` | 드롭다운 하단 커스텀 액션 버튼 |
189
289
 
190
290
  ---
191
291
 
192
- ## DataSelectButton
292
+ ## SharedDataSelectButton
193
293
 
194
- ```typescript
195
- interface DataSelectButtonCommonProps<TItem, TKey, TDialogProps> {
196
- load(keys: TKey[]): TItem[] | Promise<TItem[]>;
197
- renderItem(item: TItem): JSX.Element;
198
- dialog: Component<TDialogProps>;
199
- dialogOptions?: DialogShowOptions;
200
- required?: boolean;
201
- disabled?: boolean;
202
- size?: ComponentSize;
203
- inset?: boolean;
204
- validate?: (value: unknown) => string | undefined;
205
- lazyValidation?: boolean;
206
- }
294
+ SharedData 기반 DataSelectButton. SharedDataAccessor와 다이얼로그를 연결한다.
207
295
 
208
- // Single: { multiple?: false; value?: TKey; onValueChange?: (value: TKey | undefined) => void }
209
- // Multiple: { multiple: true; value?: TKey[]; onValueChange?: (value: TKey[]) => void }
210
- ```
296
+ ```tsx
297
+ import { SharedDataSelectButton } from "@simplysm/solid";
211
298
 
212
- Button that opens a dialog for selecting data items. Unlike `SharedDataSelect`, this is not tied to `SharedDataProvider` — it uses a custom `load` function to resolve selected keys to items, and a custom `dialog` for the selection UI.
299
+ <SharedDataSelectButton
300
+ data={sharedData.users}
301
+ value={selectedUserId()}
302
+ onValueChange={setSelectedUserId}
303
+ dialog={UserSearchDialog}
304
+ dialogOptions={{ header: "사용자 선택" }}
305
+ >
306
+ {(user) => <span>{user.name}</span>}
307
+ </SharedDataSelectButton>
308
+
309
+ // 다중 선택
310
+ <SharedDataSelectButton
311
+ multiple
312
+ data={sharedData.users}
313
+ value={selectedUserIds()}
314
+ onValueChange={setSelectedUserIds}
315
+ dialog={UserSearchDialog}
316
+ >
317
+ {(user) => <span>{user.name}</span>}
318
+ </SharedDataSelectButton>
319
+ ```
213
320
 
214
321
  ---
215
322
 
216
- ## AddressSearch
323
+ ## SharedDataSelectList
217
324
 
218
- ```typescript
219
- interface AddressSearchResult {
220
- postNumber?: string;
221
- address?: string;
222
- buildingName?: string;
223
- }
325
+ SharedData 기반 목록 선택 UI. 검색, 필터, 페이징을 내장한다.
326
+
327
+ ```tsx
328
+ import { SharedDataSelectList } from "@simplysm/solid";
329
+
330
+ <SharedDataSelectList
331
+ data={sharedData.departments}
332
+ value={selectedDept()}
333
+ onValueChange={setSelectedDept}
334
+ pageSize={20}
335
+ header={<h3>부서 선택</h3>}
336
+ >
337
+ <SharedDataSelectList.ItemTemplate>
338
+ {(dept: Department) => <span>{dept.name}</span>}
339
+ </SharedDataSelectList.ItemTemplate>
340
+ </SharedDataSelectList>
341
+
342
+ // 커스텀 필터 UI
343
+ <SharedDataSelectList data={sharedData.items} value={v()} onValueChange={setV}>
344
+ <SharedDataSelectList.ItemTemplate>
345
+ {(item) => <span>{item.name}</span>}
346
+ </SharedDataSelectList.ItemTemplate>
347
+ <SharedDataSelectList.Filter>
348
+ <MyCustomFilter />
349
+ </SharedDataSelectList.Filter>
350
+ </SharedDataSelectList>
224
351
  ```
225
352
 
226
- Korean address search dialog content component. Use with `dialog.show(AddressSearchContent, {})` to open. Returns `AddressSearchResult` on selection.
353
+ | Prop | 타입 | 설명 |
354
+ |------|------|------|
355
+ | `data` | `SharedDataAccessor<TItem>` | 공유 데이터 접근자 |
356
+ | `value` / `onValueChange` | `TItem \| undefined` | 선택된 항목 (아이템 참조) |
357
+ | `filterFn` | `(item, index) => boolean` | 아이템 필터 |
358
+ | `canChange` | `(item) => boolean \| Promise<boolean>` | 변경 가드 |
359
+ | `pageSize` | `number` | 페이지 크기 (설정 시 페이지네이션 표시) |
360
+ | `header` | `JSX.Element` | 상단 헤더 |
227
361
 
228
362
  ---
229
363
 
230
364
  ## PermissionTable
231
365
 
232
- ```typescript
233
- interface PermissionTableProps<TModule> {
234
- items?: AppPerm<TModule>[];
235
- value?: Record<string, boolean>;
236
- onValueChange?: (value: Record<string, boolean>) => void;
237
- modules?: TModule[];
238
- disabled?: boolean;
239
- class?: string;
240
- style?: JSX.CSSProperties;
241
- }
366
+ `createAppStructure`에서 생성한 권한 트리를 DataSheet로 렌더링하는 권한 관리 테이블.
367
+
368
+ ```tsx
369
+ import { PermissionTable } from "@simplysm/solid";
370
+
371
+ const app = useAppStructure();
372
+
373
+ <PermissionTable
374
+ items={app.usablePerms()}
375
+ value={permissions()}
376
+ onValueChange={setPermissions}
377
+ modules={activeModules()}
378
+ />
242
379
  ```
243
380
 
244
- Renders a permission matrix from `AppPerm` tree structure. `value` is a `Record<permCode, boolean>` map.
381
+ | Prop | 타입 | 설명 |
382
+ |------|------|------|
383
+ | `items` | `AppPerm[]` | 권한 트리 (`createAppStructure`의 `usablePerms()`) |
384
+ | `value` | `Record<string, boolean>` | 권한 레코드 (`"href/perm": true`) |
385
+ | `onValueChange` | `(v: Record<string, boolean>) => void` | 변경 콜백 |
386
+ | `modules` | `TModule[]` | 활성 모듈 필터 |
387
+ | `disabled` | `boolean` | 비활성화 |
245
388
 
246
- **Utility functions:**
247
- - `collectAllPerms(items)` extracts all permission codes from the tree
248
- - `filterByModules(items, modules)` filters permissions by active modules
249
- - `changePermCheck(value, item, perm, checked)` returns new value with cascading check/uncheck logic
389
+ 동작 규칙:
390
+ - 기본 권한(`perms[0]`, 보통 `"use"`)이 꺼지면 하위 권한도 자동으로 꺼진다
391
+ - 그룹 노드 체크 모든 자식에 대해 일괄 적용
392
+ - 모듈 필터링: `modules` prop에 따라 해당 모듈의 항목만 표시
250
393
 
251
394
  ---
252
395
 
253
- ## Usage Examples
396
+ ## AddressSearch
397
+
398
+ 다음(카카오) 우편번호 API 기반 주소 검색.
399
+
400
+ ```tsx
401
+ import { AddressSearchContent, type AddressSearchResult } from "@simplysm/solid";
402
+
403
+ // Dialog로 사용
404
+ const dialog = useDialog();
405
+ const result = await dialog.show<AddressSearchResult>(AddressSearchContent, {}, {
406
+ header: "주소 검색",
407
+ width: "500px",
408
+ height: "500px",
409
+ });
410
+
411
+ if (result) {
412
+ setPostCode(result.postNumber);
413
+ setAddress(result.address);
414
+ setBuildingName(result.buildingName);
415
+ }
416
+ ```
417
+
418
+ ### AddressSearchResult
254
419
 
255
420
  ```typescript
256
- import { CrudSheet, CrudDetail, SharedDataSelect } from "@simplysm/solid";
257
-
258
- // CrudSheet with inline editing
259
- <CrudSheet
260
- search={(filter, page, sorts) => api.searchUsers(filter, page, sorts)}
261
- getItemKey={(item) => item.id}
262
- inlineEdit={{
263
- submit: (diffs) => api.saveUsers(diffs),
264
- newItem: () => ({ id: undefined, name: "", age: 0 }),
265
- }}
266
- storageKey="users"
267
- >
268
- <CrudSheet.Filter>
269
- <TextInput value={filter.name} onValueChange={(v) => setFilter("name", v)} />
270
- </CrudSheet.Filter>
271
- <CrudSheet.Column key="name" header="Name" sortable editTrigger>
272
- {(ctx) => <TextInput value={ctx.item.name} onValueChange={(v) => ctx.setItem("name", v)} inset />}
273
- </CrudSheet.Column>
274
- </CrudSheet>
275
-
276
- // SharedDataSelect
277
- <SharedDataSelect data={sharedData.departments} value={deptId()} onValueChange={setDeptId}>
278
- <SharedDataSelect.ItemTemplate>
279
- {(item) => item.name}
280
- </SharedDataSelect.ItemTemplate>
281
- </SharedDataSelect>
421
+ interface AddressSearchResult {
422
+ postNumber: string | undefined; // 우편번호
423
+ address: string | undefined; // 주소
424
+ buildingName: string | undefined; // 건물명
425
+ }
282
426
  ```