@simplysm/solid 13.0.96 → 13.0.98

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.
@@ -1,587 +0,0 @@
1
- # 폼 컨트롤
2
-
3
- ## Button
4
-
5
- ```tsx
6
- import { Button } from "@simplysm/solid";
7
-
8
- <Button theme="primary" variant="solid" size="md" onClick={handleClick}>
9
- 저장
10
- </Button>
11
- <Button variant="outline" disabled>취소</Button>
12
- <Button variant="ghost" inset>아이콘 버튼</Button>
13
- ```
14
-
15
- | Prop | 타입 | 기본값 | 설명 |
16
- |------|------|--------|------|
17
- | `theme` | `SemanticTheme` | `"base"` | 색상 테마 |
18
- | `variant` | `"solid" \| "outline" \| "ghost"` | `"outline"` | 스타일 변형 |
19
- | `size` | `ComponentSize` | `"md"` | 크기 |
20
- | `inset` | `boolean` | `false` | 테두리/라운드 없음 |
21
- | `disabled` | `boolean` | `false` | 비활성화 |
22
-
23
- `<button>` HTML 속성을 모두 상속한다. `type` 기본값은 `"button"`.
24
-
25
- ---
26
-
27
- ## TextInput
28
-
29
- ```tsx
30
- import { TextInput } from "@simplysm/solid";
31
-
32
- <TextInput value={name()} onValueChange={setName} placeholder="이름 입력" />
33
-
34
- // 접두사 슬롯
35
- <TextInput value={phone()} onValueChange={setPhone} format="XXX-XXXX-XXXX">
36
- <TextInput.Prefix>+82</TextInput.Prefix>
37
- </TextInput>
38
- ```
39
-
40
- | Prop | 타입 | 설명 |
41
- |------|------|------|
42
- | `value` | `string` | 값 |
43
- | `onValueChange` | `(v: string) => void` | 변경 콜백 |
44
- | `type` | `"text" \| "password" \| "email"` | 입력 타입 (기본: `"text"`) |
45
- | `format` | `string` | 형식 마스크 (예: `"XXX-XXXX-XXXX"`) |
46
- | `placeholder` | `string` | 플레이스홀더 |
47
- | `title` | `string` | 툴팁 |
48
- | `autocomplete` | `string` | autocomplete 속성 (기본: `"one-time-code"`) |
49
- | `size` | `ComponentSize` | 크기 |
50
- | `inset` | `boolean` | 테두리 없음 |
51
- | `disabled` | `boolean` | 비활성화 |
52
- | `readOnly` | `boolean` | 읽기 전용 |
53
- | `required` | `boolean` | 필수 입력 |
54
- | `minLength` | `number` | 최소 길이 |
55
- | `maxLength` | `number` | 최대 길이 |
56
- | `pattern` | `string \| RegExp` | 입력 패턴 (정규식) |
57
- | `validate` | `(v: string) => string \| undefined` | 커스텀 유효성 검증 |
58
- | `lazyValidation` | `boolean` | blur 시 검증 |
59
-
60
- ### 서브 컴포넌트
61
-
62
- | 컴포넌트 | 설명 |
63
- |----------|------|
64
- | `TextInput.Prefix` | 입력 필드 앞에 표시할 접두사 슬롯 |
65
-
66
- ---
67
-
68
- ## NumberInput
69
-
70
- ```tsx
71
- import { NumberInput } from "@simplysm/solid";
72
-
73
- <NumberInput value={amount()} onValueChange={setAmount} min={0} max={100} />
74
- <NumberInput value={price()} onValueChange={setPrice} useGrouping minimumFractionDigits={2} />
75
-
76
- // 접두사 슬롯
77
- <NumberInput value={price()} onValueChange={setPrice}>
78
- <NumberInput.Prefix>$</NumberInput.Prefix>
79
- </NumberInput>
80
- ```
81
-
82
- | Prop | 타입 | 설명 |
83
- |------|------|------|
84
- | `value` | `number` | 값 |
85
- | `onValueChange` | `(v: number \| undefined) => void` | 변경 콜백 |
86
- | `useGrouping` | `boolean` | 천단위 구분자 (기본: `true`) |
87
- | `minimumFractionDigits` | `number` | 최소 소수점 자릿수 |
88
- | `placeholder` | `string` | 플레이스홀더 |
89
- | `title` | `string` | 툴팁 |
90
- | `size` | `ComponentSize` | 크기 |
91
- | `inset` | `boolean` | 테두리 없음 |
92
- | `disabled` | `boolean` | 비활성화 |
93
- | `readOnly` | `boolean` | 읽기 전용 |
94
- | `required` | `boolean` | 필수 입력 |
95
- | `min` | `number` | 최솟값 |
96
- | `max` | `number` | 최댓값 |
97
- | `validate` | `(v: number \| undefined) => string \| undefined` | 커스텀 유효성 검증 |
98
- | `lazyValidation` | `boolean` | blur 시 검증 |
99
-
100
- ### 서브 컴포넌트
101
-
102
- | 컴포넌트 | 설명 |
103
- |----------|------|
104
- | `NumberInput.Prefix` | 입력 필드 앞에 표시할 접두사 슬롯 |
105
-
106
- ---
107
-
108
- ## Textarea
109
-
110
- ```tsx
111
- import { Textarea } from "@simplysm/solid";
112
-
113
- <Textarea value={memo()} onValueChange={setMemo} minRows={3} />
114
- ```
115
-
116
- Alt+Enter로 줄바꿈. `minRows`로 최소 높이 설정.
117
-
118
- | Prop | 타입 | 설명 |
119
- |------|------|------|
120
- | `value` | `string` | 값 |
121
- | `onValueChange` | `(v: string) => void` | 변경 콜백 |
122
- | `placeholder` | `string` | 플레이스홀더 |
123
- | `title` | `string` | 툴팁 |
124
- | `minRows` | `number` | 최소 행 수 (기본: `1`) |
125
- | `size` | `ComponentSize` | 크기 |
126
- | `inset` | `boolean` | 테두리 없음 |
127
- | `disabled` | `boolean` | 비활성화 |
128
- | `readOnly` | `boolean` | 읽기 전용 |
129
- | `required` | `boolean` | 필수 입력 |
130
- | `minLength` | `number` | 최소 길이 |
131
- | `maxLength` | `number` | 최대 길이 |
132
- | `validate` | `(v: string) => string \| undefined` | 커스텀 유효성 검증 |
133
- | `lazyValidation` | `boolean` | blur 시 검증 |
134
-
135
- ---
136
-
137
- ## DatePicker
138
-
139
- ```tsx
140
- import { DatePicker } from "@simplysm/solid";
141
-
142
- <DatePicker value={date()} onValueChange={setDate} />
143
- <DatePicker value={month()} onValueChange={setMonth} unit="month" />
144
- <DatePicker value={year()} onValueChange={setYear} unit="year" />
145
- ```
146
-
147
- | Prop | 타입 | 설명 |
148
- |------|------|------|
149
- | `value` | `DateOnly` | 값 (`@simplysm/core-common`의 `DateOnly`) |
150
- | `onValueChange` | `(v: DateOnly \| undefined) => void` | 변경 콜백 |
151
- | `unit` | `"year" \| "month" \| "date"` | 선택 단위 (기본: `"date"`) |
152
- | `min` | `DateOnly` | 최소 날짜 |
153
- | `max` | `DateOnly` | 최대 날짜 |
154
- | `title` | `string` | 툴팁 |
155
- | `size` | `ComponentSize` | 크기 |
156
- | `inset` | `boolean` | 테두리 없음 |
157
- | `disabled` | `boolean` | 비활성화 |
158
- | `readOnly` | `boolean` | 읽기 전용 |
159
- | `required` | `boolean` | 필수 입력 |
160
- | `validate` | `(v: DateOnly \| undefined) => string \| undefined` | 커스텀 유효성 검증 |
161
- | `lazyValidation` | `boolean` | blur 시 검증 |
162
-
163
- ---
164
-
165
- ## DateTimePicker
166
-
167
- ```tsx
168
- import { DateTimePicker } from "@simplysm/solid";
169
-
170
- <DateTimePicker value={dt()} onValueChange={setDt} unit="minute" />
171
- ```
172
-
173
- | Prop | 타입 | 설명 |
174
- |------|------|------|
175
- | `value` | `DateTime` | 값 (`@simplysm/core-common`의 `DateTime`) |
176
- | `onValueChange` | `(v: DateTime \| undefined) => void` | 변경 콜백 |
177
- | `unit` | `"minute" \| "second"` | 시간 단위 (기본: `"minute"`) |
178
- | `min` | `DateTime` | 최소 일시 |
179
- | `max` | `DateTime` | 최대 일시 |
180
- | `title` | `string` | 툴팁 |
181
- | `size`, `inset`, `disabled`, `readOnly`, `required` | | 공통 |
182
- | `validate` | `(v: DateTime \| undefined) => string \| undefined` | 커스텀 유효성 검증 |
183
- | `lazyValidation` | `boolean` | blur 시 검증 |
184
-
185
- ---
186
-
187
- ## TimePicker
188
-
189
- ```tsx
190
- import { TimePicker } from "@simplysm/solid";
191
-
192
- <TimePicker value={time()} onValueChange={setTime} />
193
- <TimePicker value={time()} onValueChange={setTime} unit="second" />
194
- ```
195
-
196
- | Prop | 타입 | 설명 |
197
- |------|------|------|
198
- | `value` | `Time` | 값 (`@simplysm/core-common`의 `Time`) |
199
- | `onValueChange` | `(v: Time \| undefined) => void` | 변경 콜백 |
200
- | `unit` | `"minute" \| "second"` | 시간 단위 (기본: `"minute"`) |
201
- | `min` | `Time` | 최소 시간 |
202
- | `max` | `Time` | 최대 시간 |
203
- | `title` | `string` | 툴팁 |
204
- | `size`, `inset`, `disabled`, `readOnly`, `required` | | 공통 |
205
- | `validate` | `(v: Time \| undefined) => string \| undefined` | 커스텀 유효성 검증 |
206
- | `lazyValidation` | `boolean` | blur 시 검증 |
207
-
208
- ---
209
-
210
- ## DateRangePicker
211
-
212
- 기간 유형(일/월/범위) 선택과 시작/종료일 입력을 조합한 날짜 범위 선택기.
213
-
214
- ```tsx
215
- import { DateRangePicker, type DateRangePeriodType } from "@simplysm/solid";
216
-
217
- const [periodType, setPeriodType] = createSignal<DateRangePeriodType>("range");
218
- const [from, setFrom] = createSignal<DateOnly>();
219
- const [to, setTo] = createSignal<DateOnly>();
220
-
221
- <DateRangePicker
222
- periodType={periodType()}
223
- onPeriodTypeChange={setPeriodType}
224
- from={from()}
225
- onFromChange={setFrom}
226
- to={to()}
227
- onToChange={setTo}
228
- required
229
- />
230
- ```
231
-
232
- | Prop | 타입 | 설명 |
233
- |------|------|------|
234
- | `periodType` | `"day" \| "month" \| "range"` | 기간 유형 |
235
- | `onPeriodTypeChange` | `(v: DateRangePeriodType) => void` | 기간 유형 변경 콜백 |
236
- | `from` | `DateOnly` | 시작일 |
237
- | `onFromChange` | `(v: DateOnly \| undefined) => void` | 시작일 변경 콜백 |
238
- | `to` | `DateOnly` | 종료일 |
239
- | `onToChange` | `(v: DateOnly \| undefined) => void` | 종료일 변경 콜백 |
240
-
241
- 기간 유형에 따라 자동으로 from/to를 조정한다:
242
- - `"day"`: from = to (동일)
243
- - `"month"`: from = 월 첫째 날, to = 월 마지막 날
244
- - `"range"`: 시작일/종료일 독립 선택
245
-
246
- ---
247
-
248
- ## Checkbox / Radio
249
-
250
- ```tsx
251
- import { Checkbox, Radio, RadioGroup } from "@simplysm/solid";
252
-
253
- <Checkbox checked={active()} onCheckedChange={setActive}>활성</Checkbox>
254
- <Checkbox checked={agreed()} onCheckedChange={setAgreed} required>약관 동의</Checkbox>
255
-
256
- <RadioGroup value={role()} onValueChange={setRole}>
257
- <Radio value="admin">관리자</Radio>
258
- <Radio value="user">사용자</Radio>
259
- </RadioGroup>
260
- ```
261
-
262
- ### Checkbox Props
263
-
264
- | Prop | 타입 | 설명 |
265
- |------|------|------|
266
- | `checked` | `boolean` | 체크 상태 |
267
- | `onCheckedChange` | `(v: boolean) => void` | 변경 콜백 |
268
- | `disabled` | `boolean` | 비활성화 |
269
- | `size` | `ComponentSize` | 크기 |
270
- | `inset` | `boolean` | 테두리 없음 |
271
- | `inline` | `boolean` | 인라인 배치 |
272
- | `required` | `boolean` | 필수 체크 |
273
- | `validate` | `(v: boolean) => string \| undefined` | 커스텀 유효성 검증 |
274
- | `lazyValidation` | `boolean` | blur 시 검증 |
275
-
276
- ### RadioGroup Props
277
-
278
- | Prop | 타입 | 설명 |
279
- |------|------|------|
280
- | `value` | `TValue` | 선택 값 |
281
- | `onValueChange` | `(v: TValue) => void` | 변경 콜백 |
282
- | `disabled` | `boolean` | 비활성화 |
283
- | `inline` | `boolean` | 가로 배치 |
284
- | `inset` | `boolean` | 테두리 없음 |
285
- | `required` | `boolean` | 필수 선택 |
286
- | `validate` | `(v: TValue \| undefined) => string \| undefined` | 커스텀 유효성 검증 |
287
-
288
- ---
289
-
290
- ## CheckboxGroup
291
-
292
- 다중 선택을 위한 체크박스 그룹.
293
-
294
- ```tsx
295
- import { CheckboxGroup } from "@simplysm/solid";
296
-
297
- const [selectedTags, setSelectedTags] = createSignal<string[]>([]);
298
-
299
- <CheckboxGroup value={selectedTags()} onValueChange={setSelectedTags} required>
300
- <CheckboxGroup.Item value="frontend">프론트엔드</CheckboxGroup.Item>
301
- <CheckboxGroup.Item value="backend">백엔드</CheckboxGroup.Item>
302
- <CheckboxGroup.Item value="devops">DevOps</CheckboxGroup.Item>
303
- </CheckboxGroup>
304
- ```
305
-
306
- | Prop | 타입 | 설명 |
307
- |------|------|------|
308
- | `value` | `TValue[]` | 선택된 값 배열 |
309
- | `onValueChange` | `(v: TValue[]) => void` | 변경 콜백 |
310
- | `disabled` | `boolean` | 비활성화 |
311
- | `inline` | `boolean` | 가로 배치 |
312
- | `inset` | `boolean` | 테두리 없음 |
313
- | `required` | `boolean` | 최소 1개 선택 필수 |
314
- | `validate` | `(v: TValue[]) => string \| undefined` | 커스텀 유효성 검증 |
315
-
316
- ---
317
-
318
- ## Select
319
-
320
- ```tsx
321
- import { Select } from "@simplysm/solid";
322
-
323
- // 단일 선택 (items 모드)
324
- <Select
325
- value={selected()}
326
- onValueChange={setSelected}
327
- items={options}
328
- renderValue={(item) => <span>{item.label}</span>}
329
- itemSearchText={(item) => item.label}
330
- />
331
-
332
- // 다중 선택
333
- <Select
334
- multiple
335
- value={selectedList()}
336
- onValueChange={setSelectedList}
337
- items={options}
338
- renderValue={(item) => <span>{item.label}</span>}
339
- tagDirection="horizontal"
340
- />
341
-
342
- // 트리 구조
343
- <Select
344
- value={selected()}
345
- onValueChange={setSelected}
346
- items={categories}
347
- itemChildren={(item) => item.children}
348
- renderValue={(item) => <span>{item.name}</span>}
349
- />
350
-
351
- // Children 모드
352
- <Select value={v()} onValueChange={setV} renderValue={(v) => <span>{v}</span>}>
353
- <Select.Item value="a">옵션 A</Select.Item>
354
- <Select.Item value="b">옵션 B</Select.Item>
355
- </Select>
356
-
357
- // ItemTemplate (items 모드에서 드롭다운 렌더링 커스터마이징)
358
- <Select items={users} renderValue={(u) => <span>{u.name}</span>}>
359
- <Select.ItemTemplate>
360
- {(item, _index, _depth) => <span>{item.name} ({item.email})</span>}
361
- </Select.ItemTemplate>
362
- </Select>
363
-
364
- // Header (드롭다운 상단 커스텀 영역)
365
- <Select value={v()} onValueChange={setV} renderValue={(v) => <span>{v}</span>}>
366
- <Select.Header><div>그룹 헤더</div></Select.Header>
367
- <Select.Item value="a">옵션 A</Select.Item>
368
- </Select>
369
-
370
- // Action (트리거 옆 커스텀 액션 버튼)
371
- <Select items={users} renderValue={(u) => <span>{u.name}</span>}>
372
- <Select.Action onClick={handleSearch}>
373
- <Icon icon={IconSearch} />
374
- </Select.Action>
375
- </Select>
376
- ```
377
-
378
- ### Select Props
379
-
380
- | Prop | 타입 | 설명 |
381
- |------|------|------|
382
- | `value` | `TValue \| TValue[]` | 선택 값 (multiple 시 배열) |
383
- | `onValueChange` | `(v) => void` | 변경 콜백 |
384
- | `multiple` | `boolean` | 다중 선택 모드 |
385
- | `items` | `TValue[]` | 아이템 배열 (items 모드) |
386
- | `itemChildren` | `(item, index, depth) => TValue[] \| undefined` | 트리 자식 접근자 |
387
- | `renderValue` | `(v: TValue) => JSX.Element` | 선택된 값 렌더링 |
388
- | `itemSearchText` | `(item: TValue) => string` | 검색 텍스트 추출 (설정 시 검색 입력 표시) |
389
- | `isItemHidden` | `(item: TValue) => boolean` | 아이템 숨김 여부 |
390
- | `tagDirection` | `"horizontal" \| "vertical"` | 다중 선택 태그 방향 |
391
- | `hideSelectAll` | `boolean` | 전체 선택 버튼 숨김 (다중 선택) |
392
- | `placeholder` | `string` | 플레이스홀더 |
393
- | `size` | `ComponentSize` | 크기 |
394
- | `inset` | `boolean` | 테두리 없음 |
395
- | `disabled` | `boolean` | 비활성화 |
396
- | `required` | `boolean` | 필수 선택 |
397
- | `validate` | `(v: unknown) => string \| undefined` | 커스텀 유효성 검증 |
398
- | `lazyValidation` | `boolean` | blur 시 검증 |
399
-
400
- ### 서브 컴포넌트
401
-
402
- | 컴포넌트 | 설명 |
403
- |----------|------|
404
- | `Select.Item` | 드롭다운 선택 항목. props: `value`, `disabled` |
405
- | `Select.Item.Children` | 중첩 아이템 슬롯 (트리 구조) |
406
- | `Select.ItemTemplate` | items 모드에서 드롭다운 아이템 렌더링 커스터마이징 |
407
- | `Select.Header` | 드롭다운 상단 커스텀 영역 |
408
- | `Select.Action` | 트리거 옆 커스텀 액션 버튼 |
409
-
410
- ---
411
-
412
- ## Combobox
413
-
414
- 비동기 검색 기반 자동완성 컴포넌트.
415
-
416
- ```tsx
417
- import { Combobox } from "@simplysm/solid";
418
-
419
- <Combobox
420
- value={user()}
421
- onValueChange={setUser}
422
- loadItems={async (query) => await searchUsers(query)}
423
- renderValue={(u) => <span>{u.name}</span>}
424
- debounceMs={300}
425
- >
426
- <Combobox.ItemTemplate>
427
- {(item) => <span>{item.name} ({item.email})</span>}
428
- </Combobox.ItemTemplate>
429
- </Combobox>
430
-
431
- // 커스텀 값 허용
432
- <Combobox
433
- allowsCustomValue
434
- parseCustomValue={(text) => ({ id: 0, name: text })}
435
- loadItems={loadItems}
436
- renderValue={(v) => <span>{v.name}</span>}
437
- />
438
-
439
- // Children 모드
440
- <Combobox loadItems={loadItems} renderValue={(v) => v.name}>
441
- <For each={items()}>
442
- {(item) => <Combobox.Item value={item}>{item.name}</Combobox.Item>}
443
- </For>
444
- </Combobox>
445
- ```
446
-
447
- ### Combobox Props
448
-
449
- | Prop | 타입 | 설명 |
450
- |------|------|------|
451
- | `value` | `TValue` | 선택 값 |
452
- | `onValueChange` | `(v: TValue) => void` | 변경 콜백 |
453
- | `loadItems` | `(query: string) => TValue[] \| Promise<TValue[]>` | 아이템 검색 함수 (필수) |
454
- | `renderValue` | `(v: TValue) => JSX.Element` | 선택된 값 렌더링 (필수) |
455
- | `debounceMs` | `number` | 검색 디바운스 (기본: `300`) |
456
- | `allowsCustomValue` | `boolean` | 커스텀 값 입력 허용 |
457
- | `parseCustomValue` | `(text: string) => TValue` | 텍스트를 값으로 변환 |
458
- | `placeholder` | `string` | 플레이스홀더 |
459
- | `size` | `ComponentSize` | 크기 |
460
- | `inset` | `boolean` | 테두리 없음 |
461
- | `disabled` | `boolean` | 비활성화 |
462
- | `required` | `boolean` | 필수 선택 |
463
- | `validate` | `(v: TValue \| undefined) => string \| undefined` | 커스텀 유효성 검증 |
464
- | `lazyValidation` | `boolean` | blur 시 검증 |
465
-
466
- ### 서브 컴포넌트
467
-
468
- | 컴포넌트 | 설명 |
469
- |----------|------|
470
- | `Combobox.Item` | 드롭다운 선택 항목. props: `value`, `disabled` |
471
- | `Combobox.ItemTemplate` | 아이템 렌더링 커스터마이징 |
472
-
473
- ---
474
-
475
- ## ColorPicker
476
-
477
- ```tsx
478
- import { ColorPicker } from "@simplysm/solid";
479
-
480
- <ColorPicker value={color()} onValueChange={setColor} />
481
- // value: "#RRGGBB" 형식
482
- ```
483
-
484
- ---
485
-
486
- ## RichTextEditor
487
-
488
- Tiptap 기반 리치 텍스트 에디터. 서식, 텍스트 스타일, 정렬, 테이블, 이미지, 하이라이트 도구 포함.
489
-
490
- ```tsx
491
- import { RichTextEditor } from "@simplysm/solid";
492
-
493
- <RichTextEditor value={html()} onValueChange={setHtml} />
494
- ```
495
-
496
- ---
497
-
498
- ## Numpad
499
-
500
- 터치 환경용 숫자 키패드 컴포넌트.
501
-
502
- ```tsx
503
- import { Numpad } from "@simplysm/solid";
504
-
505
- <Numpad value={quantity()} onValueChange={setQuantity} />
506
-
507
- // Enter 버튼 + 마이너스 버튼 포함
508
- <Numpad
509
- value={amount()}
510
- onValueChange={setAmount}
511
- withEnterButton
512
- withMinusButton
513
- onEnterButtonClick={() => submit()}
514
- required
515
- />
516
-
517
- // 직접 입력 비활성화 (키패드만 사용)
518
- <Numpad value={code()} onValueChange={setCode} inputDisabled />
519
- ```
520
-
521
- | Prop | 타입 | 설명 |
522
- |------|------|------|
523
- | `value` | `number` | 값 |
524
- | `onValueChange` | `(v: number \| undefined) => void` | 변경 콜백 |
525
- | `withEnterButton` | `boolean` | Enter 버튼 표시 |
526
- | `withMinusButton` | `boolean` | 마이너스 토글 버튼 표시 |
527
- | `onEnterButtonClick` | `() => void` | Enter 버튼 클릭 콜백 |
528
- | `inputDisabled` | `boolean` | NumberInput 직접 입력 비활성화 |
529
- | `required` | `boolean` | 필수 입력 |
530
- | `size` | `ComponentSize` | 크기 |
531
-
532
- ---
533
-
534
- ## StatePreset
535
-
536
- 필터/설정 상태를 프리셋으로 저장하고 복원하는 컴포넌트. localStorage에 자동 저장된다.
537
-
538
- ```tsx
539
- import { StatePreset } from "@simplysm/solid";
540
-
541
- const [filterState, setFilterState] = createSignal({ status: "active", keyword: "" });
542
-
543
- <StatePreset
544
- storageKey="user-list-filter"
545
- value={filterState()}
546
- onValueChange={setFilterState}
547
- />
548
- ```
549
-
550
- | Prop | 타입 | 설명 |
551
- |------|------|------|
552
- | `storageKey` | `string` | localStorage 저장 키 |
553
- | `value` | `TValue` | 현재 상태 값 |
554
- | `onValueChange` | `(v: TValue) => void` | 상태 복원 콜백 |
555
- | `size` | `ComponentSize` | 크기 |
556
-
557
- 프리셋 칩 UI를 제공하며, 클릭으로 복원, 저장(덮어쓰기), 삭제가 가능하다. 삭제/덮어쓰기 시 실행 취소 알림을 표시한다.
558
-
559
- ---
560
-
561
- ## ThemeToggle
562
-
563
- 라이트/다크/시스템 모드 전환 버튼.
564
-
565
- ```tsx
566
- import { ThemeToggle } from "@simplysm/solid";
567
-
568
- <ThemeToggle />
569
- ```
570
-
571
- ---
572
-
573
- ## 유효성 검증
574
-
575
- 모든 폼 컨트롤은 `validate`, `required`, `lazyValidation` prop을 지원한다.
576
-
577
- ```tsx
578
- <TextInput
579
- value={email()}
580
- onValueChange={setEmail}
581
- required
582
- validate={(v) => v.includes("@") ? undefined : "이메일 형식이 아닙니다"}
583
- lazyValidation // blur 시 검증
584
- />
585
- ```
586
-
587
- `Invalid` 컴포넌트로 검증 에러를 감싸서 표시할 수 있다.