@simplysm/solid 13.0.95 → 13.0.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/features.md DELETED
@@ -1,653 +0,0 @@
1
- # 기능 컴포넌트
2
-
3
- 엔터프라이즈 비즈니스 로직을 캡슐화한 고수준 컴포넌트.
4
-
5
- ## CRUD 컴포넌트 동작 모드
6
-
7
- `CrudSheet`과 `CrudDetail`은 겸용 컴포넌트로, `close` prop 전달 여부와 `Topbar` 컨텍스트에 따라 3가지 모드로 동작한다. CRUD 기반 컴포넌트를 만들 때는 관례적으로 `close` prop을 받아 그대로 전달한다.
8
-
9
- | 모드 | 조건 | 툴바 위치 | 저장 후 동작 |
10
- |------|------|-----------|-------------|
11
- | **Dialog** | `close` 전달됨 | Dialog 헤더에 refresh, 하단에 저장/삭제 액션바 | `close(true)`로 다이얼로그 닫기 |
12
- | **Page** | `close` 미전달 + `Topbar` 내부 | `Topbar`에 저장/삭제/새로고침 버튼 주입 | 데이터 재조회 |
13
- | **Control** | `close` 미전달 + `Topbar` 외부 | 자체 인라인 툴바 표시 | 데이터 재조회 |
14
-
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
- );
23
- }
24
-
25
- // Dialog 모드 -- close가 전달되어 모달로 동작
26
- const result = await dialog.open(UserList);
27
-
28
- // Page 모드 -- Topbar 하위 라우트에 배치, close 미전달
29
- <Route path="/users" component={UserList} />
30
-
31
- // Control 모드 -- 다른 페이지의 일부로 임베드, close 미전달
32
- <div class="grid grid-cols-2">
33
- <UserList />
34
- <OrderList />
35
- </div>
36
- ```
37
-
38
- ---
39
-
40
- ## CrudSheet
41
-
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
- );
70
- }
71
-
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
- restoreItems: (items) => restoreProducts(items),
84
- }}
85
- >
86
- <CrudSheet.Column header="상품명" key="name" editTrigger>
87
- {(ctx) => ctx.item.name}
88
- </CrudSheet.Column>
89
- </CrudSheet>
90
- );
91
- }
92
- ```
93
-
94
- ### CrudSheet Props
95
-
96
- | Prop | 타입 | 설명 |
97
- |------|------|------|
98
- | `search` | `(filter, page, sorts) => Promise<SearchResult<TItem>>` | 데이터 조회 함수. `page`가 `undefined`면 전체 조회 (Excel 등) |
99
- | `getItemKey` | `(item: TItem) => string \| number \| undefined` | 아이템 고유 키 반환 |
100
- | `close` | `() => void` | Dialog 모드 활성화. 전달 시 Dialog 헤더에 새로고침 버튼 표시 |
101
- | `inlineEdit` | `InlineEditConfig<TItem>` | 인라인 편집 설정 (`dialogEdit`과 동시 사용 불가) |
102
- | `dialogEdit` | `DialogEditConfig<TItem>` | 다이얼로그 편집 설정 (`inlineEdit`과 동시 사용 불가) |
103
- | `editable` | `boolean` | 편집 버튼 표시 여부 (기본값: `true`) |
104
- | `isItemEditable` | `(item: TItem) => boolean` | 아이템별 편집 가능 여부 (dialogEdit의 editTrigger 링크에 적용) |
105
- | `isItemDeletable` | `(item: TItem) => boolean` | 아이템별 삭제 가능 여부 (삭제 버튼/링크 비활성화) |
106
- | `isItemDeleted` | `(item: TItem) => boolean` | 아이템 삭제 상태 확인 (취소선 표시) |
107
- | `isItemSelectable` | `(item: TItem) => boolean \| string` | 아이템별 선택 가능 여부 |
108
- | `filterInitial` | `TFilter` | 필터 초기값 |
109
- | `items` | `TItem[]` | 제어 모드: 외부에서 아이템 배열 전달 |
110
- | `onItemsChange` | `(items: TItem[]) => void` | 제어 모드: 아이템 변경 콜백 |
111
- | `storageKey` | `string` | DataSheet 컬럼 너비 등 상태 저장 키 |
112
- | `lastModifiedAtProp` | `string` | 자동 "최종수정일시" 컬럼 추가 (DateTime 타입, hidden) |
113
- | `lastModifiedByProp` | `string` | 자동 "수정자" 컬럼 추가 (string 타입, hidden) |
114
- | `excel` | `ExcelConfig<TItem>` | Excel 가져오기/내보내기 설정 |
115
- | `selectionMode` | `"single" \| "multiple"` | 선택 모드 활성화 |
116
- | `selectedKeys` | `(string \| number)[]` | 제어 모드: 선택된 키 배열 |
117
- | `onSelectedKeysChange` | `(keys) => void` | 제어 모드: 선택 변경 콜백 |
118
- | `onSelect` | `(result: SelectResult<TItem>) => void` | 선택 확인 콜백 (single 모드는 클릭 시 자동 호출) |
119
- | `onSubmitComplete` | `() => void` | 저장 완료 후 콜백 |
120
- | `hideAutoTools` | `boolean` | 자동 생성 툴바 버튼 숨기기 |
121
- | `class` | `string` | CSS 클래스 |
122
-
123
- ### 설정 타입
124
-
125
- ```typescript
126
- // 인라인 편집 설정
127
- interface InlineEditConfig<TItem> {
128
- submit: (diffs: ArrayOneWayDiffResult<TItem>[]) => Promise<void>;
129
- newItem: () => TItem;
130
- deleteProp?: keyof TItem & string; // 삭제 플래그 프로퍼티 (예: "isDeleted")
131
- diffsExcludes?: string[]; // diff 비교 제외 프로퍼티
132
- }
133
-
134
- // 다이얼로그 편집 설정
135
- interface DialogEditConfig<TItem> {
136
- editItem: (item?: TItem) => Promise<boolean | undefined>; // 등록(item 없음)/수정 다이얼로그
137
- deleteItems?: (items: TItem[]) => Promise<boolean>; // 선택 항목 삭제
138
- restoreItems?: (items: TItem[]) => Promise<boolean>; // 선택 항목 복원
139
- }
140
-
141
- // Excel 설정
142
- interface ExcelConfig<TItem> {
143
- download: (items: TItem[]) => Promise<void>; // 전체 데이터 다운로드
144
- upload?: (file: File) => Promise<void>; // .xlsx 파일 업로드
145
- }
146
-
147
- // 조회 결과
148
- interface SearchResult<TItem> {
149
- items: TItem[];
150
- pageCount?: number; // 페이지 수 (미설정 시 페이징 없음)
151
- }
152
-
153
- // 선택 결과
154
- interface SelectResult<TItem> {
155
- items: TItem[];
156
- keys: (string | number)[];
157
- }
158
- ```
159
-
160
- ### 선택 모드
161
-
162
- `selectionMode`를 설정하면 체크박스 컬럼이 자동 추가된다. Dialog 모드에서 사용하면 선택 전용 UI로 전환된다.
163
-
164
- ```tsx
165
- // 다중 선택 (제어 모드)
166
- const [selectedKeys, setSelectedKeys] = createSignal<number[]>([]);
167
-
168
- <CrudSheet
169
- search={searchFn}
170
- getItemKey={(item) => item.id}
171
- selectionMode="multiple"
172
- selectedKeys={selectedKeys()}
173
- onSelectedKeysChange={setSelectedKeys}
174
- >
175
- <CrudSheet.Column key="name" header="이름">
176
- {(ctx) => <div>{ctx.item.name}</div>}
177
- </CrudSheet.Column>
178
- </CrudSheet>
179
-
180
- // Dialog에서 단일 선택 (클릭 시 자동 확정)
181
- <CrudSheet
182
- close={props.close}
183
- search={searchFn}
184
- getItemKey={(item) => item.id}
185
- selectionMode="single"
186
- onSelect={(result) => props.close?.({ selectedKeys: result.keys })}
187
- >
188
- ...
189
- </CrudSheet>
190
- ```
191
-
192
- ### Excel 가져오기/내보내기
193
-
194
- ```tsx
195
- <CrudSheet
196
- search={searchFn}
197
- getItemKey={(item) => item.id}
198
- excel={{
199
- download: async (items) => {
200
- // items는 전체 데이터 (페이징 무시하고 재조회)
201
- await exportToExcel(items);
202
- },
203
- upload: async (file) => {
204
- // .xlsx 파일 업로드 후 자동 새로고침
205
- await importFromExcel(file);
206
- },
207
- }}
208
- >
209
- ...
210
- </CrudSheet>
211
- ```
212
-
213
- ### 키보드 단축키
214
-
215
- | 단축키 | 동작 |
216
- |--------|------|
217
- | `Ctrl+S` | 저장 (인라인 편집 모드) |
218
- | `Ctrl+Alt+L` | 새로고침 |
219
-
220
- 활성화 조건: 해당 CrudSheet 영역에 포커스/클릭이 있어야 한다 (다중 CrudSheet 환경에서 충돌 방지).
221
-
222
- ### CrudSheet 서브 컴포넌트
223
-
224
- | 컴포넌트 | 설명 |
225
- |----------|------|
226
- | `CrudSheet.Column` | 컬럼 정의. `editTrigger`로 dialogEdit 시 클릭 편집 링크 표시 |
227
- | `CrudSheet.Filter` | 검색 필터 영역. render prop으로 `(filter, setFilter)` 전달 |
228
- | `CrudSheet.Tools` | 커스텀 툴바 버튼. render prop으로 `CrudSheetContext` 전달 |
229
- | `CrudSheet.Header` | 시트 상단 커스텀 헤더 영역 |
230
-
231
- ### CrudSheet.Column
232
-
233
- `DataSheetColumn`의 모든 props를 상속하며, 추가로 `editTrigger`와 CRUD 전용 셀 컨텍스트를 제공한다.
234
-
235
- ```tsx
236
- <CrudSheet.Column<TItem>
237
- key="name" // 컬럼 식별 키 (정렬, 저장에 사용)
238
- header="이름" // 헤더 텍스트 또는 JSX
239
- editTrigger // dialogEdit 시 클릭 편집 링크 표시
240
- fixed // 고정 컬럼
241
- hidden // 기본 숨김 (사용자가 토글 가능)
242
- collapse // 접기 가능
243
- width="150px" // 초기 너비
244
- sortable={true} // 정렬 가능 (기본: true)
245
- resizable={true} // 리사이즈 가능 (기본: true)
246
- summary={(items) => items.reduce((sum, item) => sum + item.amount, 0)}
247
- >
248
- {(ctx) => (
249
- // ctx: CrudSheetCellContext<TItem>
250
- // ctx.item: 현재 아이템
251
- // ctx.index: 배열 인덱스
252
- // ctx.row: 행 번호
253
- // ctx.depth: 트리 깊이
254
- // ctx.setItem(key, value): 인라인 편집 시 값 변경
255
- <TextInput value={ctx.item.name} onValueChange={(v) => ctx.setItem("name", v)} />
256
- )}
257
- </CrudSheet.Column>
258
- ```
259
-
260
- ### CrudSheet.Filter
261
-
262
- 검색 필터 영역. render prop으로 현재 `filter` 상태와 `setFilter` (SolidJS store setter)를 전달한다. 검색 버튼은 자동 생성된다.
263
-
264
- ```tsx
265
- <CrudSheet.Filter<{ searchText?: string; status?: string }>>
266
- {(filter, setFilter) => (
267
- <>
268
- <FormGroup.Item label="검색어">
269
- <TextInput value={filter.searchText ?? ""} onValueChange={(v) => setFilter("searchText", v)} />
270
- </FormGroup.Item>
271
- <FormGroup.Item label="상태">
272
- <Select
273
- value={filter.status}
274
- onValueChange={(v) => setFilter("status", v)}
275
- items={["active", "inactive"]}
276
- />
277
- </FormGroup.Item>
278
- </>
279
- )}
280
- </CrudSheet.Filter>
281
- ```
282
-
283
- ### CrudSheet.Tools
284
-
285
- 커스텀 툴바 버튼. render prop으로 `CrudSheetContext`를 전달하여 내부 상태 접근 및 액션 호출이 가능하다.
286
-
287
- ```tsx
288
- <CrudSheet.Tools<TItem>>
289
- {(ctx) => (
290
- <>
291
- <Button size="sm" onClick={() => void handleCustomAction(ctx.selection())}>
292
- 커스텀 액션 ({ctx.selection().length}건)
293
- </Button>
294
- <Show when={ctx.hasChanges()}>
295
- <span class="text-warning-500">변경사항 있음</span>
296
- </Show>
297
- </>
298
- )}
299
- </CrudSheet.Tools>
300
- ```
301
-
302
- #### CrudSheetContext API
303
-
304
- | 멤버 | 타입 | 설명 |
305
- |------|------|------|
306
- | `items()` | `TItem[]` | 현재 페이지 아이템 목록 |
307
- | `selection()` | `TItem[]` | 현재 선택된 아이템 목록 |
308
- | `page()` | `number` | 현재 페이지 번호 |
309
- | `sorts()` | `SortingDef[]` | 현재 정렬 정의 |
310
- | `busy()` | `boolean` | 로딩 중 여부 |
311
- | `hasChanges()` | `boolean` | 변경사항 존재 여부 (인라인 편집) |
312
- | `save()` | `Promise<void>` | 저장 실행 |
313
- | `refresh()` | `Promise<void>` | 데이터 새로고침 |
314
- | `addItem()` | `void` | 행 추가 (인라인 편집) |
315
- | `clearSelection()` | `void` | 선택 초기화 |
316
- | `setPage(page)` | `void` | 페이지 변경 |
317
- | `setSorts(sorts)` | `void` | 정렬 변경 |
318
-
319
- ### CrudSheet.Header
320
-
321
- 시트 상단에 커스텀 헤더 영역을 추가한다.
322
-
323
- ```tsx
324
- <CrudSheet.Header>
325
- <div class="p-2 text-lg font-bold">사용자 관리</div>
326
- </CrudSheet.Header>
327
- ```
328
-
329
- ---
330
-
331
- ## CrudDetail
332
-
333
- 단건 레코드 편집 폼. 저장/삭제/복원, 변경사항 자동 추적을 지원한다.
334
-
335
- ```tsx
336
- import { CrudDetail } from "@simplysm/solid";
337
-
338
- function UserDetail(props: { close?: (result?: boolean) => void; userId: number }) {
339
- return (
340
- <CrudDetail
341
- close={props.close}
342
- load={() => fetchUser(props.userId)}
343
- submit={(data) => saveUser(data)}
344
- toggleDelete={(del) => toggleDeleteUser(props.userId, del)}
345
- >
346
- {(ctx) => (
347
- <FormGroup>
348
- <FormGroup.Item label="이름">
349
- <TextInput value={ctx.data.name} onValueChange={(v) => ctx.setData("name", v)} />
350
- </FormGroup.Item>
351
- </FormGroup>
352
- )}
353
- </CrudDetail>
354
- );
355
- }
356
-
357
- // Dialog로 열기
358
- const result = await dialog.open(UserDetail, { userId: 123 });
359
- ```
360
-
361
- ### CrudDetail 서브 컴포넌트
362
-
363
- | 컴포넌트 | 설명 |
364
- |----------|------|
365
- | `CrudDetail.Tools` | 커스텀 툴바 버튼 |
366
- | `CrudDetail.Before` | 폼 위쪽 영역 (폼 외부) |
367
- | `CrudDetail.After` | 폼 아래쪽 영역 (폼 외부) |
368
-
369
- ---
370
-
371
- ## DataSelectButton
372
-
373
- 다이얼로그 기반 데이터 선택 버튼. 선택 다이얼로그를 열어 항목을 선택하고, 선택된 항목을 표시한다.
374
-
375
- ```tsx
376
- import { DataSelectButton, type SelectDialogBaseProps } from "@simplysm/solid";
377
-
378
- // 선택 다이얼로그 컴포넌트 정의
379
- function UserSelectDialog(props: SelectDialogBaseProps & { department?: string }) {
380
- // props.selectionMode: "single" | "multiple"
381
- // props.selectedKeys: 기존 선택된 키 배열
382
- // props.close: 결과 반환 함수
383
-
384
- const handleSelect = (userIds: number[]) => {
385
- props.close?.({ selectedKeys: userIds });
386
- };
387
-
388
- return <div>...</div>;
389
- }
390
-
391
- // 단일 선택
392
- <DataSelectButton
393
- value={selectedUserId()}
394
- onValueChange={setSelectedUserId}
395
- load={async (keys) => await loadUsers(keys)}
396
- renderItem={(user) => <span>{user.name}</span>}
397
- dialog={UserSelectDialog}
398
- dialogOptions={{ header: "사용자 선택", width: "600px" }}
399
- />
400
-
401
- // 다중 선택
402
- <DataSelectButton
403
- multiple
404
- value={selectedUserIds()}
405
- onValueChange={setSelectedUserIds}
406
- load={async (keys) => await loadUsers(keys)}
407
- renderItem={(user) => <span>{user.name}</span>}
408
- dialog={UserSelectDialog}
409
- dialogOptions={{ header: "사용자 선택" }}
410
- />
411
-
412
- // 다이얼로그에 추가 props 전달
413
- <DataSelectButton
414
- value={selectedUserId()}
415
- onValueChange={setSelectedUserId}
416
- load={loadUsers}
417
- renderItem={(user) => <span>{user.name}</span>}
418
- dialog={UserSelectDialog}
419
- dialogProps={{ department: "engineering" }}
420
- />
421
- ```
422
-
423
- ### SelectDialogBaseProps (선택 다이얼로그 필수 props)
424
-
425
- ```typescript
426
- interface SelectDialogBaseProps<TKey = string | number> {
427
- close?: (result?: DataSelectDialogResult<TKey>) => void;
428
- selectionMode: "single" | "multiple";
429
- selectedKeys: TKey[];
430
- }
431
-
432
- interface DataSelectDialogResult<TKey> {
433
- selectedKeys: TKey[];
434
- }
435
- ```
436
-
437
- ---
438
-
439
- ## SharedDataSelect
440
-
441
- SharedData 기반 Select 드롭다운. SharedDataAccessor를 연결하면 자동으로 아이템 목록, 검색, 트리 구조를 지원한다.
442
-
443
- ```tsx
444
- import { SharedDataSelect } from "@simplysm/solid";
445
-
446
- const sharedData = useSharedData<{ users: User }>();
447
-
448
- // 단일 선택
449
- <SharedDataSelect
450
- data={sharedData.users}
451
- value={selectedUserId()}
452
- onValueChange={setSelectedUserId}
453
- >
454
- <SharedDataSelect.ItemTemplate>
455
- {(item: User) => <span>{item.name}</span>}
456
- </SharedDataSelect.ItemTemplate>
457
- </SharedDataSelect>
458
-
459
- // 다중 선택
460
- <SharedDataSelect
461
- multiple
462
- data={sharedData.users}
463
- value={selectedUserIds()}
464
- onValueChange={setSelectedUserIds}
465
- >
466
- <SharedDataSelect.ItemTemplate>
467
- {(item: User) => <span>{item.name}</span>}
468
- </SharedDataSelect.ItemTemplate>
469
- </SharedDataSelect>
470
-
471
- // 다이얼로그 검색 연동
472
- <SharedDataSelect
473
- data={sharedData.users}
474
- value={selectedUserId()}
475
- onValueChange={setSelectedUserId}
476
- dialog={UserSearchDialog}
477
- dialogOptions={{ header: "사용자 검색" }}
478
- >
479
- <SharedDataSelect.ItemTemplate>
480
- {(item: User) => <span>{item.name}</span>}
481
- </SharedDataSelect.ItemTemplate>
482
- <SharedDataSelect.Action onClick={handleCustomAction}>
483
- <Icon icon={IconPlus} />
484
- </SharedDataSelect.Action>
485
- </SharedDataSelect>
486
-
487
- // 아이템 필터링
488
- <SharedDataSelect
489
- data={sharedData.departments}
490
- value={deptId()}
491
- onValueChange={setDeptId}
492
- filterFn={(item) => item.isActive}
493
- >
494
- <SharedDataSelect.ItemTemplate>
495
- {(item) => <span>{item.name}</span>}
496
- </SharedDataSelect.ItemTemplate>
497
- </SharedDataSelect>
498
- ```
499
-
500
- | Prop | 타입 | 설명 |
501
- |------|------|------|
502
- | `data` | `SharedDataAccessor<TItem>` | 공유 데이터 접근자 |
503
- | `value` / `onValueChange` | `TKey \| TKey[]` | 선택된 키 값 |
504
- | `multiple` | `boolean` | 다중 선택 모드 |
505
- | `filterFn` | `(item, index) => boolean` | 아이템 필터 |
506
- | `dialog` | `Component<TDialogProps>` | 검색 다이얼로그 컴포넌트 |
507
- | `dialogOptions` | `DialogShowOptions` | 다이얼로그 옵션 |
508
- | `required`, `disabled`, `size`, `inset` | | 공통 폼 컨트롤 props |
509
-
510
- ### 서브 컴포넌트
511
-
512
- | 컴포넌트 | 설명 |
513
- |----------|------|
514
- | `SharedDataSelect.ItemTemplate` | 드롭다운 아이템 렌더링 커스터마이징 |
515
- | `SharedDataSelect.Action` | 드롭다운 하단 커스텀 액션 버튼 |
516
-
517
- ---
518
-
519
- ## SharedDataSelectButton
520
-
521
- SharedData 기반 DataSelectButton. SharedDataAccessor와 다이얼로그를 연결한다.
522
-
523
- ```tsx
524
- import { SharedDataSelectButton } from "@simplysm/solid";
525
-
526
- <SharedDataSelectButton
527
- data={sharedData.users}
528
- value={selectedUserId()}
529
- onValueChange={setSelectedUserId}
530
- dialog={UserSearchDialog}
531
- dialogOptions={{ header: "사용자 선택" }}
532
- >
533
- {(user) => <span>{user.name}</span>}
534
- </SharedDataSelectButton>
535
-
536
- // 다중 선택
537
- <SharedDataSelectButton
538
- multiple
539
- data={sharedData.users}
540
- value={selectedUserIds()}
541
- onValueChange={setSelectedUserIds}
542
- dialog={UserSearchDialog}
543
- >
544
- {(user) => <span>{user.name}</span>}
545
- </SharedDataSelectButton>
546
- ```
547
-
548
- ---
549
-
550
- ## SharedDataSelectList
551
-
552
- SharedData 기반 목록 선택 UI. 검색, 필터, 페이징을 내장한다.
553
-
554
- ```tsx
555
- import { SharedDataSelectList } from "@simplysm/solid";
556
-
557
- <SharedDataSelectList
558
- data={sharedData.departments}
559
- value={selectedDept()}
560
- onValueChange={setSelectedDept}
561
- pageSize={20}
562
- header={<h3>부서 선택</h3>}
563
- >
564
- <SharedDataSelectList.ItemTemplate>
565
- {(dept: Department) => <span>{dept.name}</span>}
566
- </SharedDataSelectList.ItemTemplate>
567
- </SharedDataSelectList>
568
-
569
- // 커스텀 필터 UI
570
- <SharedDataSelectList data={sharedData.items} value={v()} onValueChange={setV}>
571
- <SharedDataSelectList.ItemTemplate>
572
- {(item) => <span>{item.name}</span>}
573
- </SharedDataSelectList.ItemTemplate>
574
- <SharedDataSelectList.Filter>
575
- <MyCustomFilter />
576
- </SharedDataSelectList.Filter>
577
- </SharedDataSelectList>
578
- ```
579
-
580
- | Prop | 타입 | 설명 |
581
- |------|------|------|
582
- | `data` | `SharedDataAccessor<TItem>` | 공유 데이터 접근자 |
583
- | `value` / `onValueChange` | `TItem \| undefined` | 선택된 항목 (아이템 참조) |
584
- | `filterFn` | `(item, index) => boolean` | 아이템 필터 |
585
- | `canChange` | `(item) => boolean \| Promise<boolean>` | 변경 가드 |
586
- | `pageSize` | `number` | 페이지 크기 (설정 시 페이지네이션 표시) |
587
- | `header` | `JSX.Element` | 상단 헤더 |
588
-
589
- ---
590
-
591
- ## PermissionTable
592
-
593
- `createAppStructure`에서 생성한 권한 트리를 DataSheet로 렌더링하는 권한 관리 테이블.
594
-
595
- ```tsx
596
- import { PermissionTable } from "@simplysm/solid";
597
-
598
- const app = useAppStructure();
599
-
600
- <PermissionTable
601
- items={app.usablePerms()}
602
- value={permissions()}
603
- onValueChange={setPermissions}
604
- modules={activeModules()}
605
- />
606
- ```
607
-
608
- | Prop | 타입 | 설명 |
609
- |------|------|------|
610
- | `items` | `AppPerm[]` | 권한 트리 (`createAppStructure`의 `usablePerms()`) |
611
- | `value` | `Record<string, boolean>` | 권한 레코드 (`"href/perm": true`) |
612
- | `onValueChange` | `(v: Record<string, boolean>) => void` | 변경 콜백 |
613
- | `modules` | `TModule[]` | 활성 모듈 필터 |
614
- | `disabled` | `boolean` | 비활성화 |
615
-
616
- 동작 규칙:
617
- - 기본 권한(`perms[0]`, 보통 `"use"`)이 꺼지면 하위 권한도 자동으로 꺼진다
618
- - 그룹 노드 체크 시 모든 자식에 대해 일괄 적용
619
- - 모듈 필터링: `modules` prop에 따라 해당 모듈의 항목만 표시
620
-
621
- ---
622
-
623
- ## AddressSearch
624
-
625
- 다음(카카오) 우편번호 API 기반 주소 검색.
626
-
627
- ```tsx
628
- import { AddressSearchContent, type AddressSearchResult } from "@simplysm/solid";
629
-
630
- // Dialog로 사용
631
- const dialog = useDialog();
632
- const result = await dialog.show<AddressSearchResult>(AddressSearchContent, {}, {
633
- header: "주소 검색",
634
- width: "500px",
635
- height: "500px",
636
- });
637
-
638
- if (result) {
639
- setPostCode(result.postNumber);
640
- setAddress(result.address);
641
- setBuildingName(result.buildingName);
642
- }
643
- ```
644
-
645
- ### AddressSearchResult
646
-
647
- ```typescript
648
- interface AddressSearchResult {
649
- postNumber: string | undefined; // 우편번호
650
- address: string | undefined; // 주소
651
- buildingName: string | undefined; // 건물명
652
- }
653
- ```