@simplysm/solid 13.0.93 → 13.0.96

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.
@@ -2,14 +2,17 @@
2
2
 
3
3
  ## Card
4
4
 
5
- 그림자와 호버 효과가 있는 카드 컨테이너.
5
+ 그림자와 호버 효과가 있는 카드 컨테이너. 페이드 인 애니메이션 포함.
6
6
 
7
7
  ```tsx
8
8
  import { Card } from "@simplysm/solid";
9
9
 
10
10
  <Card>카드 내용</Card>
11
+ <Card class="p-4">커스텀 패딩</Card>
11
12
  ```
12
13
 
14
+ `<div>` HTML 속성을 모두 상속한다.
15
+
13
16
  ---
14
17
 
15
18
  ## Alert
@@ -21,8 +24,16 @@ import { Alert } from "@simplysm/solid";
21
24
 
22
25
  <Alert theme="success">저장되었습니다.</Alert>
23
26
  <Alert theme="danger">오류가 발생했습니다.</Alert>
27
+ <Alert theme="warning">주의가 필요합니다.</Alert>
28
+ <Alert theme="info">참고 사항입니다.</Alert>
24
29
  ```
25
30
 
31
+ | Prop | 타입 | 기본값 | 설명 |
32
+ |------|------|--------|------|
33
+ | `theme` | `SemanticTheme` | `"base"` | 색상 테마 |
34
+
35
+ `<div>` HTML 속성을 모두 상속한다.
36
+
26
37
  ---
27
38
 
28
39
  ## Icon
@@ -31,34 +42,82 @@ Tabler Icons 래퍼.
31
42
 
32
43
  ```tsx
33
44
  import { Icon } from "@simplysm/solid";
34
- import { IconUser } from "@tabler/icons-solidjs";
45
+ import { IconUser, IconSettings } from "@tabler/icons-solidjs";
35
46
 
36
47
  <Icon icon={IconUser} size="1.5em" />
48
+ <Icon icon={IconSettings} size={24} />
37
49
  ```
38
50
 
51
+ | Prop | 타입 | 기본값 | 설명 |
52
+ |------|------|--------|------|
53
+ | `icon` | `Component<TablerIconProps>` | (필수) | Tabler 아이콘 컴포넌트 |
54
+ | `size` | `string \| number` | `"1.25em"` | 아이콘 크기 |
55
+
56
+ Tabler `IconProps`의 나머지 속성(`class`, `color` 등)을 모두 상속한다.
57
+
39
58
  ---
40
59
 
41
- ## Link / Tag
60
+ ## Link
61
+
62
+ 테마 색상의 링크 컴포넌트.
42
63
 
43
64
  ```tsx
44
- import { Link, Tag } from "@simplysm/solid";
65
+ import { Link } from "@simplysm/solid";
45
66
 
46
67
  <Link href="/users">사용자 목록</Link>
47
- <Tag>라벨</Tag>
68
+ <Link theme="danger" onClick={handleDelete}>삭제</Link>
69
+ <Link disabled>비활성 링크</Link>
48
70
  ```
49
71
 
72
+ | Prop | 타입 | 기본값 | 설명 |
73
+ |------|------|--------|------|
74
+ | `theme` | `SemanticTheme` | `"primary"` | 색상 테마 |
75
+ | `disabled` | `boolean` | `false` | 비활성화 |
76
+
77
+ `<a>` HTML 속성을 모두 상속한다.
78
+
79
+ ---
80
+
81
+ ## Tag
82
+
83
+ 테마 색상의 태그/배지.
84
+
85
+ ```tsx
86
+ import { Tag } from "@simplysm/solid";
87
+
88
+ <Tag>기본</Tag>
89
+ <Tag theme="primary">Primary</Tag>
90
+ <Tag theme="success">완료</Tag>
91
+ <Tag theme="danger">긴급</Tag>
92
+ ```
93
+
94
+ | Prop | 타입 | 기본값 | 설명 |
95
+ |------|------|--------|------|
96
+ | `theme` | `SemanticTheme` | `"base"` | 색상 테마 |
97
+
98
+ `<span>` HTML 속성을 모두 상속한다.
99
+
50
100
  ---
51
101
 
52
102
  ## Barcode
53
103
 
54
- 바코드/QR 코드 생성.
104
+ 바코드/QR 코드 생성 (bwip-js 기반).
55
105
 
56
106
  ```tsx
57
107
  import { Barcode } from "@simplysm/solid";
58
108
 
59
- <Barcode value="1234567890" />
109
+ <Barcode type="qrcode" value="https://example.com" />
110
+ <Barcode type="code128" value="1234567890" />
111
+ <Barcode type="ean13" value="4006381333931" />
60
112
  ```
61
113
 
114
+ | Prop | 타입 | 설명 |
115
+ |------|------|------|
116
+ | `type` | `BarcodeType` | 바코드 타입 (필수). `"qrcode"`, `"code128"`, `"ean13"` 등 100+ 타입 |
117
+ | `value` | `string` | 바코드 값 |
118
+
119
+ `<div>` HTML 속성을 모두 상속한다.
120
+
62
121
  ---
63
122
 
64
123
  ## Echarts
@@ -78,6 +137,13 @@ import { Echarts } from "@simplysm/solid";
78
137
  />
79
138
  ```
80
139
 
140
+ | Prop | 타입 | 설명 |
141
+ |------|------|------|
142
+ | `option` | `EChartsOption` | ECharts 옵션 (필수) |
143
+ | `busy` | `boolean` | 로딩 상태 (`showLoading`/`hideLoading`) |
144
+
145
+ `<div>` HTML 속성을 모두 상속한다. 내부적으로 SVG 렌더러를 사용하며, 컨테이너 크기 변경 시 자동 리사이즈한다.
146
+
81
147
  ---
82
148
 
83
149
  ## Dialog
@@ -96,16 +162,24 @@ import { Dialog } from "@simplysm/solid";
96
162
  </Dialog>
97
163
  ```
98
164
 
99
- | Prop | 타입 | 설명 |
100
- |------|------|------|
101
- | `open` | `boolean` | 열림 상태 |
102
- | `onOpenChange` | `(v: boolean) => void` | 상태 콜백 |
103
- | `mode` | `"float" \| "fill"` | 모드 |
104
- | `resizable` | `boolean` | 리사이즈 가능 |
105
- | `draggable` | `boolean` | 드래그 가능 |
106
- | `width`, `height` | `string` | 크기 |
107
- | `closeOnEscape` | `boolean` | ESC 닫기 |
108
- | `beforeClose` | `() => boolean` | 닫기 확인 |
165
+ | Prop | 타입 | 기본값 | 설명 |
166
+ |------|------|--------|------|
167
+ | `open` | `boolean` | | 열림 상태 |
168
+ | `onOpenChange` | `(v: boolean) => void` | | 상태 콜백 |
169
+ | `mode` | `"float" \| "fill"` | `"float"` | 모드 |
170
+ | `resizable` | `boolean` | `false` | 리사이즈 가능 |
171
+ | `draggable` | `boolean` | `false` | 드래그 가능 |
172
+ | `width` | `string` | | 너비 |
173
+ | `height` | `string` | | 높이 |
174
+ | `closeOnEscape` | `boolean` | `true` | ESC 닫기 |
175
+ | `beforeClose` | `() => boolean` | | 닫기 전 확인 (false 반환 시 취소) |
176
+
177
+ ### 서브 컴포넌트
178
+
179
+ | 컴포넌트 | 설명 |
180
+ |----------|------|
181
+ | `Dialog.Header` | 다이얼로그 헤더 (닫기 버튼 포함) |
182
+ | `Dialog.Action` | 하단 액션 영역 |
109
183
 
110
184
  ### 프로그래밍 방식 다이얼로그
111
185
 
@@ -139,6 +213,22 @@ import { Dropdown } from "@simplysm/solid";
139
213
  </Dropdown>
140
214
  ```
141
215
 
216
+ | Prop | 타입 | 기본값 | 설명 |
217
+ |------|------|--------|------|
218
+ | `open` | `boolean` | | 열림 상태 |
219
+ | `onOpenChange` | `(v: boolean) => void` | | 상태 콜백 |
220
+ | `position` | `{ x: number; y: number }` | | 절대 위치 (컨텍스트 메뉴용) |
221
+ | `maxHeight` | `number` | `300` | 최대 높이 (px) |
222
+ | `disabled` | `boolean` | `false` | 비활성화 |
223
+ | `keyboardNav` | `boolean` | `false` | 키보드 네비게이션 (Select 등에서 사용) |
224
+
225
+ ### 서브 컴포넌트
226
+
227
+ | 컴포넌트 | 설명 |
228
+ |----------|------|
229
+ | `Dropdown.Trigger` | 드롭다운 트리거 |
230
+ | `Dropdown.Content` | 드롭다운 콘텐츠 |
231
+
142
232
  ---
143
233
 
144
234
  ## Collapse
@@ -148,11 +238,20 @@ import { Dropdown } from "@simplysm/solid";
148
238
  ```tsx
149
239
  import { Collapse } from "@simplysm/solid";
150
240
 
241
+ <Button aria-expanded={expanded()} onClick={() => setExpanded(!expanded())}>
242
+ 토글
243
+ </Button>
151
244
  <Collapse open={expanded()}>
152
245
  <div>접힌 내용</div>
153
246
  </Collapse>
154
247
  ```
155
248
 
249
+ | Prop | 타입 | 기본값 | 설명 |
250
+ |------|------|--------|------|
251
+ | `open` | `boolean` | `false` | 열림 상태 |
252
+
253
+ `<div>` HTML 속성을 모두 상속한다. 높이 애니메이션이 자동 적용된다.
254
+
156
255
  ---
157
256
 
158
257
  ## Tabs
@@ -168,6 +267,17 @@ import { Tabs } from "@simplysm/solid";
168
267
  </Tabs>
169
268
  ```
170
269
 
270
+ | Prop (Tabs) | 타입 | 설명 |
271
+ |------------|------|------|
272
+ | `value` | `string` | 선택된 탭 값 |
273
+ | `onValueChange` | `(v: string) => void` | 변경 콜백 |
274
+ | `size` | `ComponentSize` | 크기 |
275
+
276
+ | Prop (Tabs.Tab) | 타입 | 설명 |
277
+ |-----------------|------|------|
278
+ | `value` | `string` | 탭 식별 값 |
279
+ | `disabled` | `boolean` | 비활성화 |
280
+
171
281
  ---
172
282
 
173
283
  ## Notification
@@ -259,8 +369,23 @@ busy.hide();
259
369
  import { Progress } from "@simplysm/solid";
260
370
 
261
371
  <Progress value={progress()} />
372
+ <Progress value={75} theme="success" />
373
+
374
+ // 커스텀 콘텐츠
375
+ <Progress value={50} theme="primary" size="lg">
376
+ <span>50% 완료</span>
377
+ </Progress>
262
378
  ```
263
379
 
380
+ | Prop | 타입 | 기본값 | 설명 |
381
+ |------|------|--------|------|
382
+ | `value` | `number` | (필수) | 진행률 (0-100) |
383
+ | `theme` | `SemanticTheme` | `"primary"` | 색상 테마 |
384
+ | `size` | `ComponentSize` | `"md"` | 크기 |
385
+ | `inset` | `boolean` | `false` | 테두리 없음 |
386
+
387
+ `<div>` HTML 속성을 모두 상속한다. children이 있으면 퍼센트 텍스트 대신 커스텀 콘텐츠를 표시한다.
388
+
264
389
  ---
265
390
 
266
391
  ## Print
package/docs/features.md CHANGED
@@ -39,7 +39,7 @@ const result = await dialog.open(UserList);
39
39
 
40
40
  ## CrudSheet
41
41
 
42
- DataSheet 기반 CRUD 기능 통합 컴포넌트. 인라인/다이얼로그 편집, 검색, Excel 가져오기/내보내기, 배치 작업을 지원한다.
42
+ DataSheet 기반 CRUD 기능 통합 컴포넌트. 인라인/다이얼로그 편집, 검색, Excel 가져오기/내보내기, 선택 모드, 배치 작업을 지원한다.
43
43
 
44
44
  편집 방식은 `inlineEdit`과 `dialogEdit` 중 하나를 선택한다 (동시 사용 불가).
45
45
 
@@ -80,6 +80,7 @@ function ProductSheet(props: { close?: (result?: boolean) => void }) {
80
80
  dialogEdit={{
81
81
  editItem: (item) => dialog.open(ProductDetail, { item }),
82
82
  deleteItems: (items) => deleteProducts(items),
83
+ restoreItems: (items) => restoreProducts(items),
83
84
  }}
84
85
  >
85
86
  <CrudSheet.Column header="상품명" key="name" editTrigger>
@@ -90,15 +91,241 @@ function ProductSheet(props: { close?: (result?: boolean) => void }) {
90
91
  }
91
92
  ```
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
+
93
222
  ### CrudSheet 서브 컴포넌트
94
223
 
95
224
  | 컴포넌트 | 설명 |
96
225
  |----------|------|
97
226
  | `CrudSheet.Column` | 컬럼 정의. `editTrigger`로 dialogEdit 시 클릭 편집 링크 표시 |
98
- | `CrudSheet.Filter` | 검색 필터 영역 |
99
- | `CrudSheet.Tools` | 커스텀 툴바 버튼 (`CrudSheetContext` 전달) |
227
+ | `CrudSheet.Filter` | 검색 필터 영역. render prop으로 `(filter, setFilter)` 전달 |
228
+ | `CrudSheet.Tools` | 커스텀 툴바 버튼. render prop으로 `CrudSheetContext` 전달 |
100
229
  | `CrudSheet.Header` | 시트 상단 커스텀 헤더 영역 |
101
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
+
102
329
  ---
103
330
 
104
331
  ## CrudDetail
@@ -131,6 +358,46 @@ function UserDetail(props: { close?: (result?: boolean) => void; userId: number
131
358
  const result = await dialog.open(UserDetail, { userId: 123 });
132
359
  ```
133
360
 
361
+ ### CrudDetail Props
362
+
363
+ | Prop | 타입 | 설명 |
364
+ |------|------|------|
365
+ | `load` | `() => Promise<{ data: TData; info: CrudDetailInfo }>` | 데이터 로드 함수 (필수) |
366
+ | `children` | `(ctx: CrudDetailContext<TData>) => JSX.Element` | 폼 렌더링 (필수) |
367
+ | `submit` | `(data: TData) => Promise<boolean \| undefined>` | 저장 함수 |
368
+ | `toggleDelete` | `(del: boolean) => Promise<boolean \| undefined>` | 삭제/복원 토글 |
369
+ | `editable` | `boolean` | 편집 가능 여부 |
370
+ | `deletable` | `boolean` | 삭제 가능 여부 |
371
+ | `data` | `TData` | 제어 모드: 외부 데이터 |
372
+ | `onDataChange` | `(data: TData) => void` | 제어 모드: 데이터 변경 콜백 |
373
+ | `close` | `(result?: boolean) => void` | Dialog 모드 활성화 |
374
+ | `class` | `string` | CSS 클래스 |
375
+
376
+ ### CrudDetailInfo
377
+
378
+ ```typescript
379
+ interface CrudDetailInfo {
380
+ isNew: boolean; // 신규 레코드 여부
381
+ isDeleted: boolean; // 삭제 상태
382
+ lastModifiedAt?: DateTime; // 최종 수정 일시
383
+ lastModifiedBy?: string; // 최종 수정자
384
+ }
385
+ ```
386
+
387
+ ### CrudDetailContext
388
+
389
+ ```typescript
390
+ interface CrudDetailContext<TData> {
391
+ data: TData; // 현재 데이터 (store)
392
+ setData: SetStoreFunction<TData>; // 데이터 수정 (SolidJS store setter)
393
+ info: () => CrudDetailInfo; // 상세 정보
394
+ busy: () => boolean; // 로딩 중 여부
395
+ hasChanges: () => boolean; // 변경사항 존재 여부
396
+ save: () => Promise<void>; // 저장 실행
397
+ refresh: () => Promise<void>; // 데이터 새로고침
398
+ }
399
+ ```
400
+
134
401
  ### CrudDetail 서브 컴포넌트
135
402
 
136
403
  | 컴포넌트 | 설명 |