@idbrnd/design-system 3.1.0 → 3.2.0

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,6 +1,6 @@
1
1
  # @idbrnd/design-system
2
2
 
3
- ![Version](https://img.shields.io/badge/version-3.1.0-4B5FE1?style=flat-square)
3
+ ![Version](https://img.shields.io/badge/version-3.2.0-4B5FE1?style=flat-square)
4
4
  ![React](https://img.shields.io/badge/React-18.3.1-61DAFB?style=flat-square&logo=react&logoColor=white)
5
5
  ![Vite](https://img.shields.io/badge/Vite-7.3.0-646CFF?style=flat-square&logo=vite&logoColor=white)
6
6
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.9.3-3178C6?style=flat-square&logo=typescript&logoColor=white)
@@ -47,6 +47,7 @@ export default function App() {
47
47
  - [입력](#입력)
48
48
  - [Input](#input)
49
49
  - [SearchBar](#searchbar)
50
+ - [Textarea](#textarea)
50
51
  - [컨트롤](#컨트롤)
51
52
  - [CheckBox](#checkbox)
52
53
  - [Radio](#radio)
@@ -348,6 +349,82 @@ function SearchExample() {
348
349
  - `description`, `fixedDescriptionHeight` 등 나머지 보조 속성은 `Input`과 동일하게 사용할 수 있습니다.
349
350
  - helper 문구를 일부 케이스에서만 줄바꿈하고 싶다면 `errorMessage` 또는 `description`에 래퍼 노드를 넘겨 `maxWidth`나 `<br />`를 적용하면 됩니다.
350
351
 
352
+ ### Textarea
353
+
354
+ 두 줄 이상의 긴 텍스트를 입력받는 controlled 컴포넌트입니다. 한 줄 입력에는 [Input](#input)을 사용하세요.
355
+
356
+ 높이는 `size`와 `minRows`로 결정되며 내용이 길어져도 늘어나지 않습니다. 넘치는 내용은 입력 영역 내부에서 스크롤됩니다.
357
+
358
+ ```tsx
359
+ import { useState } from "react";
360
+ import { Textarea, TextButton, OutlineButton } from "@idbrnd/design-system";
361
+
362
+ function TextareaExample() {
363
+ const [content, setContent] = useState("");
364
+
365
+ return (
366
+ <Textarea
367
+ headingContent="문의 내용"
368
+ required
369
+ value={content}
370
+ onChange={(e) => setContent(e.target.value)}
371
+ description="접수 후 영업일 기준 2일 내에 답변드립니다."
372
+ bottom
373
+ leadingContent
374
+ maxLength={500}
375
+ trailingContent={
376
+ <>
377
+ <TextButton size="medium" variant="assistive">
378
+ 취소
379
+ </TextButton>
380
+ <OutlineButton size="xsmall" variant="secondary">
381
+ 등록
382
+ </OutlineButton>
383
+ </>
384
+ }
385
+ />
386
+ );
387
+ }
388
+ ```
389
+
390
+ | Prop | 타입 | 기본값 | 설명 |
391
+ | ----------------- | ------------------------------------------------------------------- | --------------------- | ---------------------------------------------------------------- |
392
+ | `designType` | `"outline"` \| `"fill"` | `"outline"` | 입력 영역 외형 |
393
+ | `size` | `"default"` \| `"large"` | `"default"` | 텍스트 크기와 컨테이너 높이 (108px / 112px) |
394
+ | `variant` | `"basic"` \| `"onFocus"` \| `"typed"` \| `"error"` \| `"success"` | 자동 계산 | 수동 상태 지정. `errorMessage`가 있으면 `error`가 우선 적용 |
395
+ | `minRows` | `number` | `2` | 노출할 줄 수 = 고정 높이. 넘치면 내부 스크롤 |
396
+ | `width` | `number \| string` | `"100%"` | 루트 너비 |
397
+ | `value` | `string` | — | **(필수)** 입력값 |
398
+ | `onChange` | `ChangeEventHandler<HTMLTextAreaElement>` | — | 값 변경 핸들러 |
399
+ | `heading` | `boolean` | `true` | 상단 라벨 표시 여부 |
400
+ | `headingContent` | `ReactNode` | `"Label"` | 상단 라벨 내용 |
401
+ | `required` | `boolean` | `false` | 필수 입력 표시 및 native `required` 속성 적용 |
402
+ | `description` | `ReactNode \| boolean` | `false` | 하단 안내 문구. `true`면 기본 안내 문구를 사용 |
403
+ | `errorMessage` | `ReactNode` | 기본 에러 문구 | 의미 있는 값이 전달되면 에러 상태가 자동 적용되고 해당 문구 표시 |
404
+ | `successMessage` | `ReactNode` | — | `variant="success"`일 때만 노출 |
405
+ | `bottom` | `boolean` | `false` | 입력 영역 내부 하단 영역(글자 수 · 보조 액션) 노출 여부 |
406
+ | `leadingContent` | `boolean` | `false` | 하단 좌측 글자 수 카운터 **상시** 노출 여부 |
407
+ | `maxLength` | `number` | `100` 또는 `1500` | 최대 입력 글자 수. 기본값은 `leadingContent`에 따라 달라짐 |
408
+ | `trailingContent` | `ReactNode` | — | 하단 우측 보조 액션. 전달한 노드를 그대로 렌더링 |
409
+ | `placeholder` | `string` | `"텍스트를 입력해 주세요."` | 플레이스홀더 |
410
+ | `readOnly` | `boolean` | `false` | 읽기 전용 |
411
+ | `disabled` | `boolean` | `false` | 비활성화 |
412
+ | `customStyle` | `CSSProperties` | `{}` | 루트 컨테이너에 적용할 추가 인라인 스타일 |
413
+
414
+ 유의사항:
415
+
416
+ - `defaultValue`는 지원하지 않습니다. 반드시 `value` + `onChange`로 제어해야 합니다.
417
+ - 컨테이너 높이는 `size`가 결정합니다(`default` 108px / `large` 112px). `trailingContent`나 카운터 유무로 높이가 달라지지 않습니다.
418
+ - 카운터와 보조 액션은 `bottom={true}`일 때만 노출됩니다. `bottom={false}`면 하단 영역 자체가 없습니다.
419
+ - **`leadingContent={false}`여도 한도까지 100자 이하로 남으면 카운터가 자동으로 나타납니다.** 곧 입력이 막힌다는 것을 사용자가 미리 알 수 있게 하기 위한 동작이며, 기본값 기준 1400자부터 `1400/1500`이 표시됩니다.
420
+ - `maxLength`를 지정하지 않으면 `leadingContent={true}`일 때 `100`, `false`일 때 `1500`이 적용됩니다. 즉 **입력 길이 제한은 항상 걸립니다.**
421
+ - `trailingContent`는 전달한 노드를 그대로 렌더링합니다. 버튼을 하나 넘기면 하나만, 둘 넘기면 둘 다 노출됩니다. 버튼이 두 개일 때는 위계가 높은 버튼을 우측에 둡니다.
422
+ - 하단 영역은 높이 32px을 차지합니다. 보조 액션 버튼은 이 높이에 맞춰 `TextButton size="medium"`, `OutlineButton size="xsmall"` 등을 사용하세요.
423
+ - `errorMessage`·`description`의 빈 문자열, 공백 문자열, 내용 없는 JSX 래퍼는 문구가 없는 것으로 처리합니다. 노출할 문구가 없으면 문구 영역 자체를 렌더링하지 않습니다.
424
+ - `variant="error"`면 적색 보더와 아이콘이, `variant="success"`면 녹색 체크 아이콘이 함께 표시됩니다.
425
+ - 사용자가 모서리를 드래그해 크기를 바꾸는 native resize는 비활성화되어 있습니다.
426
+ - `id`, `name`, `onFocus`, `onBlur`, `autoFocus` 등 대부분의 native `<textarea>` props도 함께 사용할 수 있습니다.
427
+
351
428
  ---
352
429
 
353
430
  ## 컨트롤
@@ -0,0 +1,21 @@
1
+ import type { TextareaProps } from './Textarea.types';
2
+ export type { TextareaProps, TextareaSize, TextareaVariant, DesignType } from './Textarea.types';
3
+ declare function Textarea({ designType, // 입력 영역 외형 타입
4
+ size, // 텍스트 크기
5
+ variant, // 상태 variant
6
+ minRows, // 노출할 줄 수 (고정 높이)
7
+ width, // 루트 너비
8
+ value, // 현재 값
9
+ heading, // 라벨 표시 여부
10
+ headingContent, // 라벨 내용
11
+ required, // 라벨 우측 *
12
+ description, // 하단 안내 문구
13
+ errorMessage, // 에러 상태 문구
14
+ successMessage, // 성공 상태 문구
15
+ bottom, // 입력 영역 내부 하단 영역 표시 여부
16
+ leadingContent, // 하단 좌측 글자 수 카운터 표시 여부
17
+ maxLength, // 최대 입력 글자 수 (카운터 분모)
18
+ trailingContent, // 하단 우측 슬롯
19
+ customStyle, // 루트 커스텀 스타일
20
+ placeholder, disabled, readOnly, id, ...rest }: TextareaProps): import("react/jsx-runtime").JSX.Element;
21
+ export default Textarea;
@@ -0,0 +1,133 @@
1
+ import type { CSSProperties, ReactNode, TextareaHTMLAttributes } from 'react';
2
+ import type { DesignType } from '../Input/Input.types';
3
+ export type { DesignType };
4
+ /**
5
+ * Textarea 높이·텍스트 크기 타입.
6
+ * @defaultValue `'default'`
7
+ */
8
+ export type TextareaSize = 'default' | 'large';
9
+ /**
10
+ * Textarea 상태 variant 타입.
11
+ * - `basic`: 기본 상태
12
+ * - `onFocus`: 포커스 진입 상태(보더 강조)
13
+ * - `typed`: 값이 입력된 상태
14
+ * - `error`: 유효성 오류 상태
15
+ * - `success`: 유효성 통과 상태
16
+ */
17
+ export type TextareaVariant = 'basic' | 'onFocus' | 'typed' | 'error' | 'success';
18
+ /** `Textarea` 컴포넌트 public props. */
19
+ export interface TextareaProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'style' | 'className' | 'cols' | 'rows' | 'required' | 'defaultValue' | 'value'> {
20
+ /**
21
+ * 입력 영역 외형 타입.
22
+ * - `outline`: 테두리로 영역 구분
23
+ * - `fill`: 배경 채움 + 테두리로 영역 구분
24
+ * @defaultValue `'outline'`
25
+ */
26
+ designType?: DesignType;
27
+ /**
28
+ * 입력 영역 텍스트 크기.
29
+ * - `default`: 14px
30
+ * - `large`: 16px
31
+ * @defaultValue `'default'`
32
+ */
33
+ size?: TextareaSize;
34
+ /**
35
+ * 상태 variant.
36
+ * `errorMessage`가 있으면 `error`가 우선 적용된다.
37
+ * 미지정 시 값 존재 여부에 따라 `basic`/`typed`를 자동 계산한다.
38
+ * 실제 포커스 시각 상태는 CSS `:focus-within`으로 처리하므로,
39
+ * `onFocus`는 포커스 상태를 강제로 보여줘야 할 때만 사용한다.
40
+ */
41
+ variant?: TextareaVariant;
42
+ /**
43
+ * 입력 영역에 노출할 줄 수.
44
+ * 이 값이 곧 고정 높이가 되며, 내용이 넘치면 내부 스크롤로 처리한다.
45
+ * Text Field와 구분하기 위해 2줄 미만은 권장하지 않는다.
46
+ * @defaultValue `2`
47
+ */
48
+ minRows?: number;
49
+ /**
50
+ * 루트 너비.
51
+ * 미지정 시 `100%`가 적용된다.
52
+ */
53
+ width?: number | string;
54
+ /**
55
+ * Textarea 값.
56
+ * 이 컴포넌트는 controlled 방식만 지원하므로 필수로 전달해야 한다.
57
+ */
58
+ value: string;
59
+ /**
60
+ * 상단 heading(라벨) 표시 여부.
61
+ * @defaultValue `true`
62
+ */
63
+ heading?: boolean;
64
+ /**
65
+ * 상단 heading(라벨) 텍스트/노드.
66
+ * @defaultValue `'Label'`
67
+ */
68
+ headingContent?: ReactNode;
69
+ /**
70
+ * 필수 입력 여부.
71
+ * `true`면 textarea의 `required` 속성을 적용하고, heading 노출 시 `*`를 함께 표시한다.
72
+ * @defaultValue `false`
73
+ */
74
+ required?: boolean;
75
+ /**
76
+ * 하단 안내 문구
77
+ * - `false`: 안내 문구 없음
78
+ * - `true`: 기본 안내 문구 사용
79
+ * - `ReactNode`: 커스텀 안내 문구 사용
80
+ *
81
+ * 주의: `error`/`success` 상태에서는 `errorMessage`/`successMessage`가 우선 노출된다.
82
+ * 노출할 문구가 없으면 문구 영역(`<p>`) 자체를 렌더링하지 않는다.
83
+ * @defaultValue `false`
84
+ */
85
+ description?: ReactNode | boolean;
86
+ /**
87
+ * 에러 문구.
88
+ * 전달되면 `variant` 지정 여부와 관계없이 `error` 상태가 우선 적용된다.
89
+ * 빈 문자열, 공백 문자열, 내용 없는 JSX 래퍼는 에러 문구로 간주하지 않는다.
90
+ */
91
+ errorMessage?: ReactNode;
92
+ /**
93
+ * 성공 문구.
94
+ * `variant === 'success'`일 때만 노출되며, 상태를 바꾸지는 않는다.
95
+ * (성공 판정 시점은 소비처의 유효성 검사 결과에 맡긴다)
96
+ */
97
+ successMessage?: ReactNode;
98
+ /**
99
+ * 입력 영역 내부 하단 영역(글자 수 · 보조 액션) 노출 여부.
100
+ * @defaultValue `false`
101
+ */
102
+ bottom?: boolean;
103
+ /**
104
+ * 하단 영역 좌측 글자 수 카운터(`현재/최대`)를 **상시** 노출할지 여부.
105
+ * (피그마 `Leading Content`에 대응)
106
+ *
107
+ * `false`여도 한도까지 남은 글자 수가 100자 이하가 되면 카운터가 나타난다.
108
+ * 곧 입력이 막힌다는 것을 사용자가 미리 인지할 수 있게 하기 위함이다.
109
+ * (기본값 기준 1400자부터 `1400/1500`)
110
+ *
111
+ * 주의:
112
+ * - `bottom`이 `false`면 하단 영역 자체가 없으므로 표시되지 않는다.
113
+ * - 분모가 필요하므로 `maxLength`가 항상 적용된다. 즉 입력 길이 제한이 늘 걸린다.
114
+ * @defaultValue `false`
115
+ */
116
+ leadingContent?: boolean;
117
+ /**
118
+ * 최대 입력 글자 수. (textarea 네이티브 속성)
119
+ * 도달하면 추가 입력이 차단되고, 카운터가 노출될 때 분모로 표시된다.
120
+ *
121
+ * 미지정 시 기본값은 `leadingContent`에 따라 달라진다.
122
+ * - `true`: `100` (짧은 입력을 전제)
123
+ * - `false`: `1500` (긴 글 입력을 전제)
124
+ */
125
+ maxLength?: number;
126
+ /**
127
+ * 하단 영역 우측 콘텐츠(보조 액션 버튼 등).
128
+ * 버튼이 두 개일 경우 위계가 높은 버튼을 우측에 둔다.
129
+ */
130
+ trailingContent?: ReactNode;
131
+ /** 루트 컨테이너에 적용할 추가 style */
132
+ customStyle?: CSSProperties;
133
+ }
package/dist/index.d.ts CHANGED
@@ -20,6 +20,8 @@ export { default as Input } from "./components/Input/Input";
20
20
  export { default as SearchBar } from "./components/SearchBar/SearchBar";
21
21
  export type { InputProps, InputSize, DesignType, InputVariant, } from "./components/Input/Input";
22
22
  export type { SearchBarProps, SearchBarSize, SearchBarVariant, } from "./components/SearchBar/SearchBar";
23
+ export { default as Textarea } from "./components/Textarea/Textarea";
24
+ export type { TextareaProps, TextareaSize, TextareaVariant, } from "./components/Textarea/Textarea";
23
25
  export { default as CheckBox } from "./components/Control/CheckBox/CheckBox";
24
26
  export { default as Radio } from "./components/Control/Radio/Radio";
25
27
  export { default as ToggleSwitch } from "./components/Control/ToggleSwitch/ToggleSwitch";