@ehfuse/mui-form-controls 3.1.52 → 3.1.53

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/README.md CHANGED
@@ -1,405 +1,405 @@
1
- # @ehfuse/mui-form-controls
2
-
3
- MUI 기반 폼 컨트롤과 텍스트 필드 컴포넌트 모음
4
-
5
- ## 컴포넌트
6
-
7
- | 컴포넌트 | 설명 | 문서 |
8
- | ----------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
9
- | **AddressTextField** | 다음 우편번호 API 연동 주소 검색 필드 | [API](./docs/ko/api.md#addresstextfield) |
10
- | **Autocomplete** | 자동완성 기능이 있는 입력 필드 | [API](./docs/ko/api.md#autocomplete) |
11
- | **BizNumTextField** | 사업자등록번호 입력 필드 (검증, 복사 기능) | [API](./docs/ko/api.md#biznumtextfield) |
12
- | **CardNumTextField** | 카드번호 4-4-4-4 입력 (2·3칸 마스킹, 브랜드 로고, 키보드 UX) | [API](./docs/ko/api.md#cardnumtextfield) |
13
- | **ClearTextField** | 클리어 버튼이 있는 기본 텍스트 필드 | [API](./docs/ko/api.md#cleartextfield) |
14
- | **DateRange** | 날짜 범위 입력 필드 (시작일, 종료일) | [API](./docs/ko/api.md#daterange) |
15
- | **DateTextField** | 달력 팝업, 다양한 날짜 포맷, 공휴일 표시 지원 날짜 필드 | [API](./docs/ko/api.md#datetextfield) |
16
- | **DateTimeTextField** | 날짜 + 시간 입력 필드 | [API](./docs/ko/api.md#datetimetextfield) |
17
- | **EmailTextField** | 도메인 자동완성, 유효성 검사 기능이 있는 이메일 필드 | [API](./docs/ko/api.md#emailtextfield) |
18
- | **JuminTextField** | 주민등록번호 입력 필드 (마스킹, 정보 추출) | [API](./docs/ko/api.md#jumintextfield) |
19
- | **NumberField** | 숫자 입력 (MUI OutlinedInput + 위·아래 스피너) | [API](./docs/ko/api.md#numberfield) |
20
- | **NumberStepper** | 가로 스테퍼 (`[-]` 숫자 `[+]`, 길게 누르기 가속) | [API](./docs/ko/api.md#numberstepper) |
21
- | **NumberSpinner** | 숫자 스피너 (좌우 버튼 + 라벨 드래그) | [API](./docs/ko/api.md#numberspinner) |
22
- | **NumberTextField** | 천 단위 구분자, 소수점, 음수 지원하는 숫자 필드 | [API](./docs/ko/api.md#numbertextfield) |
23
- | **PasswordTextField** | 비밀번호 보기/숨기기 토글, 유효성 검사 기능이 있는 비밀번호 필드 | [API](./docs/ko/api.md#passwordtextfield) |
24
- | **PhoneTextField** | 자동 포맷팅, prefix 고정 기능이 있는 전화번호 필드 | [API](./docs/ko/api.md#phonetextfield) |
25
- | **SearchTextField** | 클리어 버튼, 로딩 상태, 디바운스 기능이 있는 검색 필드 | [API](./docs/ko/api.md#searchtextfield) |
26
- | **TextArea** | 여러 줄 텍스트 입력 필드 | [API](./docs/ko/api.md#textarea) |
27
- | **TagsTextField** | 태그(칩) 목록 입력 · 드래그 정렬 · 선택적 삭제/클릭 콜백 | [API](./docs/ko/api.md#tagstextfield) |
28
- | **TextField** | MUI TextField 래퍼 (폼 통합) | [API](./docs/ko/api.md#textfield) |
29
- | **TimeTextField** | 시간 입력 필드 (자동 포맷팅, 시간 범위 제한) | [API](./docs/ko/api.md#timetextfield) |
30
- | **VerificationCodeTextField** | 인증번호 입력 필드 (숫자/영문/혼합) | [API](./docs/ko/api.md#verificationcodetextfield) |
31
-
32
- ## 폼 컨트롤 (Form Controls)
33
-
34
- 키보드 입력이 필요한 컴포넌트는 위 "컴포넌트"에, 마우스 조작만으로 선택/토글하는 컴포넌트는 여기서 분리해 정리했습니다.
35
-
36
- | 컴포넌트 | 설명 | 문서 |
37
- | --------------------- | ------------------------------------------------------------ | ----------------------------------------- |
38
- | **ButtonGroup** | 버튼 그룹 컴포넌트 | [API](./docs/ko/api.md#buttongroup) |
39
- | **Checkbox** | MUI Checkbox 래퍼 (폼 통합) | [API](./docs/ko/api.md#checkbox) |
40
- | **LabelSelect** | MUI Select 기반 선택 필드 (권장: 라벨 포함, form 바인딩) | [API](./docs/ko/api.md#labelselect) |
41
- | **RadioGroup** | MUI RadioGroup 래퍼 (폼 통합) | [API](./docs/ko/api.md#radiogroup) |
42
- | **Rating** | 평점 입력 컴포넌트 | [API](./docs/ko/api.md#rating) |
43
- | **Slider** | MUI Slider 래퍼 (폼 통합) | [API](./docs/ko/api.md#slider) |
44
- | **Stepper** | 스텝 진행 표시 컴포넌트 | [API](./docs/ko/api.md#stepper) |
45
- | **Switch** | MUI Switch 래퍼 (폼 통합) | [API](./docs/ko/api.md#switch) |
46
- | **ToggleButton** | 토글 버튼 컴포넌트 | [API](./docs/ko/api.md#togglebutton) |
47
- | **ToggleButtonGroup** | 옵션 기반 토글 그룹 필드 (`ToggleButtonGroup` + form 바인딩) | [API](./docs/ko/api.md#togglebuttongroup) |
48
-
49
- ## 훅 (Hooks)
50
-
51
- | 훅 | 설명 | 문서 |
52
- | -------------------------- | --------------------------------------------------- | ---------------------------------------------- |
53
- | **useKoreanHolidays** | 단일 연도의 한국 공휴일을 조회하는 커스텀 훅 | [API](./docs/ko/api.md#usekoreanholidays) |
54
- | **useKoreanHolidaysRange** | 여러 연도의 한국 공휴일을 한번에 조회하는 커스텀 훅 | [API](./docs/ko/api.md#usekoreanholidaysrange) |
55
-
56
- ## 설치
57
-
58
- ```bash
59
- npm install @ehfuse/mui-form-controls
60
- ```
61
-
62
- `AddressTextField`를 사용할 때만 다음 우편번호 의존성을 추가로 설치합니다.
63
-
64
- ```bash
65
- npm install react-daum-postcode
66
- ```
67
-
68
- ## 필수 의존성
69
-
70
- ```json
71
- {
72
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
73
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
74
- "@mui/material": "^5.0.0 || ^6.0.0 || ^7.0.0",
75
- "@mui/icons-material": "^5.0.0 || ^6.0.0 || ^7.0.0",
76
- "@emotion/react": "^11.0.0",
77
- "@emotion/styled": "^11.0.0",
78
- "@ehfuse/overlay-scrollbar": "^1.0.0"
79
- }
80
- ```
81
-
82
- ## 빠른 시작
83
-
84
- ```tsx
85
- import {
86
- SearchTextField,
87
- ClearTextField,
88
- PasswordTextField,
89
- PhoneTextField,
90
- EmailTextField,
91
- NumberTextField,
92
- JuminTextField,
93
- BizNumTextField,
94
- CardNumTextField,
95
- VerificationCodeTextField,
96
- DateTextField,
97
- TimeTextField,
98
- DateTimeTextField,
99
- TextField,
100
- Rating,
101
- ToggleButton,
102
- ToggleButtonGroup,
103
- ButtonGroup,
104
- Stepper,
105
- NumberField,
106
- NumberStepper,
107
- NumberSpinner,
108
- LabelSelect,
109
- Autocomplete,
110
- Checkbox,
111
- RadioGroup,
112
- Switch,
113
- ToggleButtonGroup,
114
- Slider,
115
- DateRange,
116
- TextArea,
117
- TagsTextField,
118
- useKoreanHolidays,
119
- useKoreanHolidaysRange,
120
- } from '@ehfuse/mui-form-controls';
121
-
122
- import { AddressTextField } from '@ehfuse/mui-form-controls/address';
123
-
124
- // 검색 필드
125
- <SearchTextField
126
- value={search}
127
- onChange={(e) => setSearch(e.target.value)}
128
- searchIcon
129
- loading={isSearching}
130
- />
131
-
132
- // 비밀번호 필드
133
- <PasswordTextField
134
- value={password}
135
- onChange={(e) => setPassword(e.target.value)}
136
- showToggle
137
- />
138
-
139
- // 전화번호 필드
140
- <PhoneTextField
141
- value={phone}
142
- onChange={(e) => setPhone(e.target.value)}
143
- prefix="010"
144
- />
145
-
146
- // 이메일 필드 (forma 호환)
147
- <EmailTextField
148
- name="email"
149
- value={email}
150
- onChange={handleFormChange}
151
- extraDomains={["mycompany.com"]}
152
- />
153
-
154
- // 숫자 필드
155
- <NumberTextField
156
- value={amount}
157
- onChange={(e) => setAmount(e.target.value)}
158
- prefix="₩"
159
- suffix="원"
160
- />
161
-
162
- // 날짜 필드
163
- <DateTextField
164
- value={date}
165
- onChange={(e) => setDate(e.target.value)}
166
- format="YYYY.MM.DD"
167
- selectedColor="secondary.main"
168
- />
169
-
170
- // 주소 필드
171
- <AddressTextField
172
- value={address}
173
- onChange={setAddress}
174
- />
175
-
176
- // 주민등록번호 필드
177
- <JuminTextField
178
- value={jumin}
179
- onChange={setJumin}
180
- mask
181
- />
182
-
183
- // 사업자등록번호 필드
184
- <BizNumTextField
185
- value={bizNum}
186
- onChange={setBizNum}
187
- validate
188
- copyIcon
189
- />
190
-
191
- // 카드번호 필드
192
- <CardNumTextField
193
- value={cardNum}
194
- onChange={(e) => setCardNum(e.target.value)}
195
- onCardBrandChange={(brand) => console.log('카드 브랜드:', brand)}
196
- />
197
-
198
- // 인증번호 필드
199
- <VerificationCodeTextField
200
- value={code}
201
- onChange={setCode}
202
- length={6}
203
- type="numeric"
204
- onComplete={(code) => console.log('완료:', code)}
205
- />
206
-
207
- // 시간 필드
208
- <TimeTextField
209
- value={time}
210
- onChange={(e) => setTime(e.target.value)}
211
- format="HH:mm"
212
- minTime="09:00"
213
- maxTime="18:00"
214
- />
215
-
216
- // 선택 필드 (권장: LabelSelect)
217
- <LabelSelect
218
- name="category"
219
- label="카테고리"
220
- form={form}
221
- options={[
222
- { value: 'a', label: 'A' },
223
- { value: 'b', label: 'B' },
224
- ]}
225
- />
226
-
227
- // 자동완성 필드
228
- <Autocomplete
229
- name="department"
230
- label="부서"
231
- form={form}
232
- options={[
233
- { value: 'dev', label: '개발' },
234
- { value: 'design', label: '디자인' },
235
- ]}
236
- />
237
-
238
- // 날짜 범위
239
- <DateRange
240
- form={form}
241
- startName="startDate"
242
- endName="endDate"
243
- startLabel="시작일"
244
- endLabel="종료일"
245
- />
246
-
247
- // 텍스트 에어리어
248
- <TextArea
249
- name="memo"
250
- label="메모"
251
- form={form}
252
- minRows={4}
253
- />
254
-
255
- // 태그(칩) 입력 — `onDelete`·`onTagClick`은 모두 선택. 지정하지 않으면 X는 즉시 삭제, 칩 클릭 핸들러 없음
256
- <TagsTextField
257
- name="tags"
258
- label="태그"
259
- form={form}
260
- placeholder="입력 후 콤마 또는 스페이스"
261
- onDeleteBefore={(e, tag, index) => {
262
- /* 선택: false 반환 시 X 삭제 전체 취소(onDelete·내부 삭제 모두 안 함) */
263
- }}
264
- onDelete={(e, tag, index) => {
265
- /* onDelete 지정 시 내부 삭제 없음 → 모달 확인 뒤 form.setFormValue("tags", ...) 등으로 배열 갱신 */
266
- }}
267
- onTagClick={(e, tag, index) => {
268
- /* 예: 상세 모달·라우팅 — 삭제 아이콘(X) 클릭과는 별도 */
269
- }}
270
- />
271
- ```
272
-
273
- > 선택 입력은 예제/문서 기준으로 `LabelSelect`를 사용합니다.
274
-
275
- ## TagsTextField (태그 입력)
276
-
277
- 문자열 배열을 태그 칩으로 표시하고, 콤마·스페이스 등으로 태그를 확정합니다. `draggable`(기본 `true`)일 때 드래그로 순서를 바꿀 수 있습니다.
278
-
279
- **여백·스타일:** 입력 칸 기준 좌우 패딩은 기본값에서 `theme.spacing`과 동일한 단위로 좌우 대칭(`px: 1.5`)입니다. 칩 줄만 손볼 때는 **`chipsSx`**, 아웃라인 안 레이아웃·패딩은 **`sx`**(이 컴포넌트에서는 `.MuiInputBase-root` 아래로 감싸져 적용됨). `slotProps` / `InputProps`는 MUI와 동일합니다. 자세한 셀렉터 예시는 [API — TagsTextField 스타일](./docs/ko/api.md#tagstextfield)을 참고하세요.
280
-
281
- ### 칩 X(삭제 아이콘) 클릭 시 순서
282
-
283
- 1. **`onDeleteBefore`**가 있으면 먼저 호출됩니다. **`false`를 반환하면 여기서 끝**이며, `onDelete`도 호출되지 않고 내부에서도 태그를 제거하지 않습니다.
284
- 2. 그다음 **`onDelete`가 있으면** 그것만 호출하고 **내부 자동 삭제는 하지 않습니다.** 모달에서 “삭제”를 누른 뒤 등, 반드시 `form.setFormValue` / `onChange`로 `string[]`에서 해당 태그를 빼 주어야 화면이 맞습니다.
285
- 3. **`onDelete`가 없으면** 컴포넌트가 곧바로 그 태그를 배열에서 제거합니다(기본 즉시 삭제).
286
-
287
- `onDelete`만 두고 `onDeleteBefore`는 생략할 수 있고, 그 반대로 **`onDeleteBefore`만** 두고 `onDelete`는 생략하면: 가드에서 막지 않았을 때만 **기본 즉시 삭제**가 실행됩니다.
288
-
289
- | 콜백 / 동작 | 옵션 여부 | 설명 |
290
- | ------------------ | --------- | ---- |
291
- | `onDeleteBefore` | 선택 | X 클릭 **가장 먼저**. `false`면 이후 단계 전부 취소. |
292
- | `onDelete` | 선택 | 가드 통과 후 호출 시 **전적으로 소비자가 배열 갱신**. 없으면 내부 즉시 삭제. |
293
- | `onTagClick` | 선택 | 칩 **본문** 클릭 시. 상세 모달·상세 페이지 등. `chipProps.onClick`이 먼저 호출되고, `event.defaultPrevented`이면 `onTagClick`은 호출되지 않음. |
294
-
295
- `draggable`이 `true`이면 짧은 클릭과 드래그 시작이 겹칠 수 있습니다(기본 포인터 센서는 약 5px 이동 후 드래그). 자세한 props는 [한국어 API](./docs/ko/api.md#tagstextfield)를 참고하세요.
296
-
297
- ## NumberField · NumberStepper · NumberSpinner
298
-
299
- 세 컴포넌트는 공용 훅 `useNumberBasic`을 기반으로 동일한 동작 규칙을 공유합니다.
300
-
301
- - 입력 중 `min`/`max` 초과 시 **즉시 경계값으로 고정** (clamp)
302
- - 천 단위 구분은 기본 적용(Intl·로케일), `thousandSeparator={false}`로 끔
303
- - `form`/`name` 지정 시 폼 연동
304
- - `clearWhenZero`로 0을 빈칸 표시 (**기본 `false` — 0을 0으로 표시**)
305
-
306
- 표시 형태만 다릅니다.
307
-
308
- | 컴포넌트 | 형태 |
309
- | -------- | ---- |
310
- | **NumberField** | MUI OutlinedInput + 오른쪽 위·아래 스피너 |
311
- | **NumberStepper** | `[-]` 숫자 `[+]` 가로 스테퍼 (길게 누르기 가속) |
312
- | **NumberSpinner** | 좌우 `[-]`/`[+]` 버튼 + 라벨 드래그(scrub) 증감 |
313
-
314
- ```tsx
315
- // NumberField — 기본 입력 + 위·아래 스피너
316
- <NumberField label="수량" defaultValue={2} min={0} max={99} />
317
-
318
- // 천 단위 구분은 기본 적용. 끄려면 thousandSeparator={false}
319
- <NumberField label="금액" defaultValue={1234567} min={0} max={99999999} />
320
-
321
- // NumberStepper — 가로 스테퍼 + 길게 누르기 가속 (CPS = 초당 증감 횟수)
322
- <NumberStepper
323
- defaultValue={1}
324
- min={0}
325
- max={99}
326
- aria-label="수량"
327
- stepperAccelerateHoldDelay={300}
328
- stepperAccelerateRampDuration={2000}
329
- stepperAccelerateCps={5}
330
- stepperAccelerateMaxCps={25}
331
- />
332
-
333
- // NumberSpinner — 좌우 버튼 + 라벨 드래그
334
- <NumberSpinner label="수량" defaultValue={2} min={0} max={99} />
335
- ```
336
-
337
- | prop | 설명 |
338
- | ---- | ---- |
339
- | `thousandSeparator` | `boolean \| string` — 기본 `true`. `false` 끔 · `string` 구분 문자 고정 · `true`일 때 Intl |
340
- | `clearWhenZero` | 0을 빈칸으로 표시. 기본 `false` |
341
- | `stepperAccelerateHoldDelay` | (NumberStepper) 누른 직후 1회 변경 후, 자동 반복까지 대기(ms) |
342
- | `stepperAccelerateRampDuration` | (NumberStepper) 시작 CPS→최대 CPS까지 걸리는 시간(ms) |
343
- | `stepperAccelerateCps` | (NumberStepper) 자동 반복 **시작** 속도 (초당 횟수) |
344
-
345
- `stepperEditable`, `stepperButtonDivider`(NumberStepper), `spinnerDivider`(NumberField) 등 전체 props는 [API](./docs/ko/api.md#numberfield)를 참고하세요.
346
-
347
- ## Boolean 필드 표준 패턴
348
-
349
- `Switch`를 boolean 입력의 기본 form-binding 컴포넌트로 사용합니다.
350
-
351
- ```tsx
352
- import { Switch } from "@ehfuse/mui-form-controls";
353
-
354
- // 1) 단일 스위치 (라벨 없음)
355
- <Switch form={form} name="deceased_disability_certificate" />
356
-
357
- // 2) 라벨 포함 스위치 (권장)
358
- <Switch
359
- form={form}
360
- name="deceased_disability_certificate"
361
- label="장애인 증명서 제출"
362
- />
363
-
364
- // 3) readOnly / disabled
365
- <Switch
366
- form={form}
367
- name="deceased_disability_certificate"
368
- label="수정 불가 항목"
369
- readonly
370
- />
371
- <Switch
372
- form={form}
373
- name="deceased_disability_certificate"
374
- label="비활성 항목"
375
- disabled
376
- />
377
- ```
378
-
379
- ## Toggle 그룹 표준 패턴
380
-
381
- ```tsx
382
- import { ToggleButtonGroup } from "@ehfuse/mui-form-controls";
383
-
384
- <ToggleButtonGroup
385
- form={form}
386
- name="contractor_gender"
387
- exclusive
388
- options={[
389
- { label: "남성", value: "M" },
390
- { label: "여성", value: "F" },
391
- ]}
392
- onDeselect="clear"
393
- fullWidth
394
- size="small"
395
- />;
396
- ```
397
-
398
- ## 문서 / Documentation
399
-
400
- - [한국어 문서](./docs/ko/getting-started.md)
401
- - [English Documentation](./docs/en/getting-started.md)
402
-
403
- ## 라이선스 / License
404
-
405
- MIT © 김영진 (Kim Young Jin)
1
+ # @ehfuse/mui-form-controls
2
+
3
+ MUI 기반 폼 컨트롤과 텍스트 필드 컴포넌트 모음
4
+
5
+ ## 컴포넌트
6
+
7
+ | 컴포넌트 | 설명 | 문서 |
8
+ | ----------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
9
+ | **AddressTextField** | 다음 우편번호 API 연동 주소 검색 필드 | [API](./docs/ko/api.md#addresstextfield) |
10
+ | **Autocomplete** | 자동완성 기능이 있는 입력 필드 | [API](./docs/ko/api.md#autocomplete) |
11
+ | **BizNumTextField** | 사업자등록번호 입력 필드 (검증, 복사 기능) | [API](./docs/ko/api.md#biznumtextfield) |
12
+ | **CardNumTextField** | 카드번호 4-4-4-4 입력 (2·3칸 마스킹, 브랜드 로고, 키보드 UX) | [API](./docs/ko/api.md#cardnumtextfield) |
13
+ | **ClearTextField** | 클리어 버튼이 있는 기본 텍스트 필드 | [API](./docs/ko/api.md#cleartextfield) |
14
+ | **DateRange** | 날짜 범위 입력 필드 (시작일, 종료일) | [API](./docs/ko/api.md#daterange) |
15
+ | **DateTextField** | 달력 팝업, 다양한 날짜 포맷, 공휴일 표시 지원 날짜 필드 | [API](./docs/ko/api.md#datetextfield) |
16
+ | **DateTimeTextField** | 날짜 + 시간 입력 필드 | [API](./docs/ko/api.md#datetimetextfield) |
17
+ | **EmailTextField** | 도메인 자동완성, 유효성 검사 기능이 있는 이메일 필드 | [API](./docs/ko/api.md#emailtextfield) |
18
+ | **JuminTextField** | 주민등록번호 입력 필드 (마스킹, 정보 추출) | [API](./docs/ko/api.md#jumintextfield) |
19
+ | **NumberField** | 숫자 입력 (MUI OutlinedInput + 위·아래 스피너) | [API](./docs/ko/api.md#numberfield) |
20
+ | **NumberStepper** | 가로 스테퍼 (`[-]` 숫자 `[+]`, 길게 누르기 가속) | [API](./docs/ko/api.md#numberstepper) |
21
+ | **NumberSpinner** | 숫자 스피너 (좌우 버튼 + 라벨 드래그) | [API](./docs/ko/api.md#numberspinner) |
22
+ | **NumberTextField** | 천 단위 구분자, 소수점, 음수 지원하는 숫자 필드 | [API](./docs/ko/api.md#numbertextfield) |
23
+ | **PasswordTextField** | 비밀번호 보기/숨기기 토글, 유효성 검사 기능이 있는 비밀번호 필드 | [API](./docs/ko/api.md#passwordtextfield) |
24
+ | **PhoneTextField** | 자동 포맷팅, prefix 고정 기능이 있는 전화번호 필드 | [API](./docs/ko/api.md#phonetextfield) |
25
+ | **SearchTextField** | 클리어 버튼, 로딩 상태, 디바운스 기능이 있는 검색 필드 | [API](./docs/ko/api.md#searchtextfield) |
26
+ | **TextArea** | 여러 줄 텍스트 입력 필드 | [API](./docs/ko/api.md#textarea) |
27
+ | **TagsTextField** | 태그(칩) 목록 입력 · 드래그 정렬 · 선택적 삭제/클릭 콜백 | [API](./docs/ko/api.md#tagstextfield) |
28
+ | **TextField** | MUI TextField 래퍼 (폼 통합) | [API](./docs/ko/api.md#textfield) |
29
+ | **TimeTextField** | 시간 입력 필드 (자동 포맷팅, 시간 범위 제한) | [API](./docs/ko/api.md#timetextfield) |
30
+ | **VerificationCodeTextField** | 인증번호 입력 필드 (숫자/영문/혼합) | [API](./docs/ko/api.md#verificationcodetextfield) |
31
+
32
+ ## 폼 컨트롤 (Form Controls)
33
+
34
+ 키보드 입력이 필요한 컴포넌트는 위 "컴포넌트"에, 마우스 조작만으로 선택/토글하는 컴포넌트는 여기서 분리해 정리했습니다.
35
+
36
+ | 컴포넌트 | 설명 | 문서 |
37
+ | --------------------- | ------------------------------------------------------------ | ----------------------------------------- |
38
+ | **ButtonGroup** | 버튼 그룹 컴포넌트 | [API](./docs/ko/api.md#buttongroup) |
39
+ | **Checkbox** | MUI Checkbox 래퍼 (폼 통합) | [API](./docs/ko/api.md#checkbox) |
40
+ | **LabelSelect** | MUI Select 기반 선택 필드 (권장: 라벨 포함, form 바인딩) | [API](./docs/ko/api.md#labelselect) |
41
+ | **RadioGroup** | MUI RadioGroup 래퍼 (폼 통합) | [API](./docs/ko/api.md#radiogroup) |
42
+ | **Rating** | 평점 입력 컴포넌트 | [API](./docs/ko/api.md#rating) |
43
+ | **Slider** | MUI Slider 래퍼 (폼 통합) | [API](./docs/ko/api.md#slider) |
44
+ | **Stepper** | 스텝 진행 표시 컴포넌트 | [API](./docs/ko/api.md#stepper) |
45
+ | **Switch** | MUI Switch 래퍼 (폼 통합) | [API](./docs/ko/api.md#switch) |
46
+ | **ToggleButton** | 토글 버튼 컴포넌트 | [API](./docs/ko/api.md#togglebutton) |
47
+ | **ToggleButtonGroup** | 옵션 기반 토글 그룹 필드 (`ToggleButtonGroup` + form 바인딩) | [API](./docs/ko/api.md#togglebuttongroup) |
48
+
49
+ ## 훅 (Hooks)
50
+
51
+ | 훅 | 설명 | 문서 |
52
+ | -------------------------- | --------------------------------------------------- | ---------------------------------------------- |
53
+ | **useKoreanHolidays** | 단일 연도의 한국 공휴일을 조회하는 커스텀 훅 | [API](./docs/ko/api.md#usekoreanholidays) |
54
+ | **useKoreanHolidaysRange** | 여러 연도의 한국 공휴일을 한번에 조회하는 커스텀 훅 | [API](./docs/ko/api.md#usekoreanholidaysrange) |
55
+
56
+ ## 설치
57
+
58
+ ```bash
59
+ npm install @ehfuse/mui-form-controls
60
+ ```
61
+
62
+ `AddressTextField`를 사용할 때만 다음 우편번호 의존성을 추가로 설치합니다.
63
+
64
+ ```bash
65
+ npm install react-daum-postcode
66
+ ```
67
+
68
+ ## 필수 의존성
69
+
70
+ ```json
71
+ {
72
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
73
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
74
+ "@mui/material": "^5.0.0 || ^6.0.0 || ^7.0.0",
75
+ "@mui/icons-material": "^5.0.0 || ^6.0.0 || ^7.0.0",
76
+ "@emotion/react": "^11.0.0",
77
+ "@emotion/styled": "^11.0.0",
78
+ "@ehfuse/overlay-scrollbar": "^1.0.0"
79
+ }
80
+ ```
81
+
82
+ ## 빠른 시작
83
+
84
+ ```tsx
85
+ import {
86
+ SearchTextField,
87
+ ClearTextField,
88
+ PasswordTextField,
89
+ PhoneTextField,
90
+ EmailTextField,
91
+ NumberTextField,
92
+ JuminTextField,
93
+ BizNumTextField,
94
+ CardNumTextField,
95
+ VerificationCodeTextField,
96
+ DateTextField,
97
+ TimeTextField,
98
+ DateTimeTextField,
99
+ TextField,
100
+ Rating,
101
+ ToggleButton,
102
+ ToggleButtonGroup,
103
+ ButtonGroup,
104
+ Stepper,
105
+ NumberField,
106
+ NumberStepper,
107
+ NumberSpinner,
108
+ LabelSelect,
109
+ Autocomplete,
110
+ Checkbox,
111
+ RadioGroup,
112
+ Switch,
113
+ ToggleButtonGroup,
114
+ Slider,
115
+ DateRange,
116
+ TextArea,
117
+ TagsTextField,
118
+ useKoreanHolidays,
119
+ useKoreanHolidaysRange,
120
+ } from '@ehfuse/mui-form-controls';
121
+
122
+ import { AddressTextField } from '@ehfuse/mui-form-controls/address';
123
+
124
+ // 검색 필드
125
+ <SearchTextField
126
+ value={search}
127
+ onChange={(e) => setSearch(e.target.value)}
128
+ searchIcon
129
+ loading={isSearching}
130
+ />
131
+
132
+ // 비밀번호 필드
133
+ <PasswordTextField
134
+ value={password}
135
+ onChange={(e) => setPassword(e.target.value)}
136
+ showToggle
137
+ />
138
+
139
+ // 전화번호 필드
140
+ <PhoneTextField
141
+ value={phone}
142
+ onChange={(e) => setPhone(e.target.value)}
143
+ prefix="010"
144
+ />
145
+
146
+ // 이메일 필드 (forma 호환)
147
+ <EmailTextField
148
+ name="email"
149
+ value={email}
150
+ onChange={handleFormChange}
151
+ extraDomains={["mycompany.com"]}
152
+ />
153
+
154
+ // 숫자 필드
155
+ <NumberTextField
156
+ value={amount}
157
+ onChange={(e) => setAmount(e.target.value)}
158
+ prefix="₩"
159
+ suffix="원"
160
+ />
161
+
162
+ // 날짜 필드
163
+ <DateTextField
164
+ value={date}
165
+ onChange={(e) => setDate(e.target.value)}
166
+ format="YYYY.MM.DD"
167
+ selectedColor="secondary.main"
168
+ />
169
+
170
+ // 주소 필드
171
+ <AddressTextField
172
+ value={address}
173
+ onChange={setAddress}
174
+ />
175
+
176
+ // 주민등록번호 필드
177
+ <JuminTextField
178
+ value={jumin}
179
+ onChange={setJumin}
180
+ mask
181
+ />
182
+
183
+ // 사업자등록번호 필드
184
+ <BizNumTextField
185
+ value={bizNum}
186
+ onChange={setBizNum}
187
+ validate
188
+ copyIcon
189
+ />
190
+
191
+ // 카드번호 필드
192
+ <CardNumTextField
193
+ value={cardNum}
194
+ onChange={(e) => setCardNum(e.target.value)}
195
+ onCardBrandChange={(brand) => console.log('카드 브랜드:', brand)}
196
+ />
197
+
198
+ // 인증번호 필드
199
+ <VerificationCodeTextField
200
+ value={code}
201
+ onChange={setCode}
202
+ length={6}
203
+ type="numeric"
204
+ onComplete={(code) => console.log('완료:', code)}
205
+ />
206
+
207
+ // 시간 필드
208
+ <TimeTextField
209
+ value={time}
210
+ onChange={(e) => setTime(e.target.value)}
211
+ format="HH:mm"
212
+ minTime="09:00"
213
+ maxTime="18:00"
214
+ />
215
+
216
+ // 선택 필드 (권장: LabelSelect)
217
+ <LabelSelect
218
+ name="category"
219
+ label="카테고리"
220
+ form={form}
221
+ options={[
222
+ { value: 'a', label: 'A' },
223
+ { value: 'b', label: 'B' },
224
+ ]}
225
+ />
226
+
227
+ // 자동완성 필드
228
+ <Autocomplete
229
+ name="department"
230
+ label="부서"
231
+ form={form}
232
+ options={[
233
+ { value: 'dev', label: '개발' },
234
+ { value: 'design', label: '디자인' },
235
+ ]}
236
+ />
237
+
238
+ // 날짜 범위
239
+ <DateRange
240
+ form={form}
241
+ startName="startDate"
242
+ endName="endDate"
243
+ startLabel="시작일"
244
+ endLabel="종료일"
245
+ />
246
+
247
+ // 텍스트 에어리어
248
+ <TextArea
249
+ name="memo"
250
+ label="메모"
251
+ form={form}
252
+ minRows={4}
253
+ />
254
+
255
+ // 태그(칩) 입력 — `onDelete`·`onTagClick`은 모두 선택. 지정하지 않으면 X는 즉시 삭제, 칩 클릭 핸들러 없음
256
+ <TagsTextField
257
+ name="tags"
258
+ label="태그"
259
+ form={form}
260
+ placeholder="입력 후 콤마 또는 스페이스"
261
+ onDeleteBefore={(e, tag, index) => {
262
+ /* 선택: false 반환 시 X 삭제 전체 취소(onDelete·내부 삭제 모두 안 함) */
263
+ }}
264
+ onDelete={(e, tag, index) => {
265
+ /* onDelete 지정 시 내부 삭제 없음 → 모달 확인 뒤 form.setFormValue("tags", ...) 등으로 배열 갱신 */
266
+ }}
267
+ onTagClick={(e, tag, index) => {
268
+ /* 예: 상세 모달·라우팅 — 삭제 아이콘(X) 클릭과는 별도 */
269
+ }}
270
+ />
271
+ ```
272
+
273
+ > 선택 입력은 예제/문서 기준으로 `LabelSelect`를 사용합니다.
274
+
275
+ ## TagsTextField (태그 입력)
276
+
277
+ 문자열 배열을 태그 칩으로 표시하고, 콤마·스페이스 등으로 태그를 확정합니다. `draggable`(기본 `true`)일 때 드래그로 순서를 바꿀 수 있습니다.
278
+
279
+ **여백·스타일:** 입력 칸 기준 좌우 패딩은 기본값에서 `theme.spacing`과 동일한 단위로 좌우 대칭(`px: 1.5`)입니다. 칩 줄만 손볼 때는 **`chipsSx`**, 아웃라인 안 레이아웃·패딩은 **`sx`**(이 컴포넌트에서는 `.MuiInputBase-root` 아래로 감싸져 적용됨). `slotProps` / `InputProps`는 MUI와 동일합니다. 자세한 셀렉터 예시는 [API — TagsTextField 스타일](./docs/ko/api.md#tagstextfield)을 참고하세요.
280
+
281
+ ### 칩 X(삭제 아이콘) 클릭 시 순서
282
+
283
+ 1. **`onDeleteBefore`**가 있으면 먼저 호출됩니다. **`false`를 반환하면 여기서 끝**이며, `onDelete`도 호출되지 않고 내부에서도 태그를 제거하지 않습니다.
284
+ 2. 그다음 **`onDelete`가 있으면** 그것만 호출하고 **내부 자동 삭제는 하지 않습니다.** 모달에서 “삭제”를 누른 뒤 등, 반드시 `form.setFormValue` / `onChange`로 `string[]`에서 해당 태그를 빼 주어야 화면이 맞습니다.
285
+ 3. **`onDelete`가 없으면** 컴포넌트가 곧바로 그 태그를 배열에서 제거합니다(기본 즉시 삭제).
286
+
287
+ `onDelete`만 두고 `onDeleteBefore`는 생략할 수 있고, 그 반대로 **`onDeleteBefore`만** 두고 `onDelete`는 생략하면: 가드에서 막지 않았을 때만 **기본 즉시 삭제**가 실행됩니다.
288
+
289
+ | 콜백 / 동작 | 옵션 여부 | 설명 |
290
+ | ------------------ | --------- | ---- |
291
+ | `onDeleteBefore` | 선택 | X 클릭 **가장 먼저**. `false`면 이후 단계 전부 취소. |
292
+ | `onDelete` | 선택 | 가드 통과 후 호출 시 **전적으로 소비자가 배열 갱신**. 없으면 내부 즉시 삭제. |
293
+ | `onTagClick` | 선택 | 칩 **본문** 클릭 시. 상세 모달·상세 페이지 등. `chipProps.onClick`이 먼저 호출되고, `event.defaultPrevented`이면 `onTagClick`은 호출되지 않음. |
294
+
295
+ `draggable`이 `true`이면 짧은 클릭과 드래그 시작이 겹칠 수 있습니다(기본 포인터 센서는 약 5px 이동 후 드래그). 자세한 props는 [한국어 API](./docs/ko/api.md#tagstextfield)를 참고하세요.
296
+
297
+ ## NumberField · NumberStepper · NumberSpinner
298
+
299
+ 세 컴포넌트는 공용 훅 `useNumberBasic`을 기반으로 동일한 동작 규칙을 공유합니다.
300
+
301
+ - 입력 중 `min`/`max` 초과 시 **즉시 경계값으로 고정** (clamp)
302
+ - 천 단위 구분은 기본 적용(Intl·로케일), `thousandSeparator={false}`로 끔
303
+ - `form`/`name` 지정 시 폼 연동
304
+ - `clearWhenZero`로 0을 빈칸 표시 (**기본 `false` — 0을 0으로 표시**)
305
+
306
+ 표시 형태만 다릅니다.
307
+
308
+ | 컴포넌트 | 형태 |
309
+ | -------- | ---- |
310
+ | **NumberField** | MUI OutlinedInput + 오른쪽 위·아래 스피너 |
311
+ | **NumberStepper** | `[-]` 숫자 `[+]` 가로 스테퍼 (길게 누르기 가속) |
312
+ | **NumberSpinner** | 좌우 `[-]`/`[+]` 버튼 + 라벨 드래그(scrub) 증감 |
313
+
314
+ ```tsx
315
+ // NumberField — 기본 입력 + 위·아래 스피너
316
+ <NumberField label="수량" defaultValue={2} min={0} max={99} />
317
+
318
+ // 천 단위 구분은 기본 적용. 끄려면 thousandSeparator={false}
319
+ <NumberField label="금액" defaultValue={1234567} min={0} max={99999999} />
320
+
321
+ // NumberStepper — 가로 스테퍼 + 길게 누르기 가속 (CPS = 초당 증감 횟수)
322
+ <NumberStepper
323
+ defaultValue={1}
324
+ min={0}
325
+ max={99}
326
+ aria-label="수량"
327
+ stepperAccelerateHoldDelay={300}
328
+ stepperAccelerateRampDuration={2000}
329
+ stepperAccelerateCps={5}
330
+ stepperAccelerateMaxCps={25}
331
+ />
332
+
333
+ // NumberSpinner — 좌우 버튼 + 라벨 드래그
334
+ <NumberSpinner label="수량" defaultValue={2} min={0} max={99} />
335
+ ```
336
+
337
+ | prop | 설명 |
338
+ | ---- | ---- |
339
+ | `thousandSeparator` | `boolean \| string` — 기본 `true`. `false` 끔 · `string` 구분 문자 고정 · `true`일 때 Intl |
340
+ | `clearWhenZero` | 0을 빈칸으로 표시. 기본 `false` |
341
+ | `stepperAccelerateHoldDelay` | (NumberStepper) 누른 직후 1회 변경 후, 자동 반복까지 대기(ms) |
342
+ | `stepperAccelerateRampDuration` | (NumberStepper) 시작 CPS→최대 CPS까지 걸리는 시간(ms) |
343
+ | `stepperAccelerateCps` | (NumberStepper) 자동 반복 **시작** 속도 (초당 횟수) |
344
+
345
+ `stepperEditable`, `stepperButtonDivider`(NumberStepper), `spinnerDivider`(NumberField) 등 전체 props는 [API](./docs/ko/api.md#numberfield)를 참고하세요.
346
+
347
+ ## Boolean 필드 표준 패턴
348
+
349
+ `Switch`를 boolean 입력의 기본 form-binding 컴포넌트로 사용합니다.
350
+
351
+ ```tsx
352
+ import { Switch } from "@ehfuse/mui-form-controls";
353
+
354
+ // 1) 단일 스위치 (라벨 없음)
355
+ <Switch form={form} name="deceased_disability_certificate" />
356
+
357
+ // 2) 라벨 포함 스위치 (권장)
358
+ <Switch
359
+ form={form}
360
+ name="deceased_disability_certificate"
361
+ label="장애인 증명서 제출"
362
+ />
363
+
364
+ // 3) readOnly / disabled
365
+ <Switch
366
+ form={form}
367
+ name="deceased_disability_certificate"
368
+ label="수정 불가 항목"
369
+ readonly
370
+ />
371
+ <Switch
372
+ form={form}
373
+ name="deceased_disability_certificate"
374
+ label="비활성 항목"
375
+ disabled
376
+ />
377
+ ```
378
+
379
+ ## Toggle 그룹 표준 패턴
380
+
381
+ ```tsx
382
+ import { ToggleButtonGroup } from "@ehfuse/mui-form-controls";
383
+
384
+ <ToggleButtonGroup
385
+ form={form}
386
+ name="contractor_gender"
387
+ exclusive
388
+ options={[
389
+ { label: "남성", value: "M" },
390
+ { label: "여성", value: "F" },
391
+ ]}
392
+ onDeselect="clear"
393
+ fullWidth
394
+ size="small"
395
+ />;
396
+ ```
397
+
398
+ ## 문서 / Documentation
399
+
400
+ - [한국어 문서](./docs/ko/getting-started.md)
401
+ - [English Documentation](./docs/en/getting-started.md)
402
+
403
+ ## 라이선스 / License
404
+
405
+ MIT © 김영진 (Kim Young Jin)