sellmate-design-system-react 9.0.0-beta.8 → 9.0.0-beta.9

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/AGENTS.md CHANGED
@@ -27,7 +27,7 @@
27
27
  | 분류 | 컴포넌트 |
28
28
  | --- | --- |
29
29
  | **버튼·링크** | `SButton` `SGhostButton` `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
30
- | **입력 (폼)** | `SForm` `SField` `SInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
30
+ | **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
31
31
  | **날짜·시간** | `SCalendar` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
32
32
  | **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
33
33
  | **레이아웃** | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
@@ -60,6 +60,7 @@ AI 에이전트는 코드를 생성하기 전에 이 목록을 반드시 지킨
60
60
  | --- | --- |
61
61
  | `<button>` | `SButton`, `SGhostButton`, `SDropdownButton`, `STextLink` |
62
62
  | `<input type="text/password/...">` | `SInput` |
63
+ | `<input type="search">` | `SSearchInput` |
63
64
  | `<input type="number">` | `SNumberInput` |
64
65
  | `<input type="checkbox">` | `SCheckbox`, `SToggle`, `SSwitch` |
65
66
  | `<input type="radio">` | `SRadio`, `SRadioButton` |
@@ -138,7 +139,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
138
139
  | --- | --- | --- |
139
140
  | **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) |
140
141
  | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
141
- | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
142
+ | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
142
143
  | **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
143
144
  | **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
144
145
 
@@ -439,6 +440,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
439
440
  | --- | --- | --- |
440
441
  | 한 줄 텍스트를 받는다 | `SInput` | §3-7-1 |
441
442
  | 여러 줄 텍스트를 받는다 | `STextarea` | §3-7-1 |
443
+ | 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
442
444
  | 숫자(수량·금액)를 받는다 | `SNumberInput` | |
443
445
  | 바코드를 스캔해 받는다 | `SBarcodeInput` | |
444
446
  | 목록에서 하나 고르게 한다 | `SSelect` | §3-7-2 |
@@ -1019,16 +1021,19 @@ const columns: STableColumn[] = [
1019
1021
 
1020
1022
  > §3-0 라우팅에서 이 절을 가리키는 자리들이다. <!-- TODO(디자인): 전체 검수·확정 -->
1021
1023
 
1022
- #### 3-7-1. SInput vs STextarea
1024
+ #### 3-7-1. SInput vs STextarea vs SSearchInput
1023
1025
 
1024
- **줄 수가 아니라 값의 성격으로 고른다.** 값의 길이를 미리 있으면 `SInput`, 없으면 `STextarea` 다.
1026
+ **먼저 "그 값이 저장되는가"를 본다.** 저장되면 필드(`SInput`·`STextarea`), 화면을 좁히기만 하고 사라지면 `SSearchInput` 이다.
1025
1027
 
1026
1028
  | 값 | 사용 |
1027
1029
  | --- | --- |
1028
1030
  | 이름·코드·전화번호·URL 처럼 형식이 정해진 값 | `SInput` |
1029
1031
  | 메모·사유·설명처럼 길이가 예측되지 않는 문장 | `STextarea` |
1032
+ | 지금 보이는 목록·결과를 좁히는 검색어 | `SSearchInput` |
1030
1033
 
1031
- 값이 길어질 수 있는데 `SInput` 을 쓰면 사용자가 자기가 쓴 것을 다시 읽지 못한다 — 한 줄 안에서 좌우로 스크롤해야 하기 때문이다. 반대로 짧은 값에 `STextarea` 를 쓰면 빈 공간이 남아 입력량을 잘못 기대하게 한다.
1034
+ 폼 필드 둘은 **줄 수가 아니라 값의 성격으로** 갈린다. 값이 길어질 수 있는데 `SInput` 을 쓰면 사용자가 자기가 쓴 것을 다시 읽지 못한다 — 한 줄 안에서 좌우로 스크롤해야 하기 때문이다. 반대로 짧은 값에 `STextarea` 를 쓰면 빈 공간이 남아 입력량을 잘못 기대하게 한다.
1035
+
1036
+ `SSearchInput` 은 폼 필드가 아니다 — 라벨·힌트·유효성 규칙·에러 메시지를 받지 않고, `SForm` 의 제출 검증 대상에도 들어가지 않는다. 돋보기 아이콘이 항상 앞에 붙어 "여기는 검색"임을 스스로 밝히므로 라벨을 따로 붙이지 않는다. 검색 실행은 `onSearch`(Enter) 로 받고, 값이 바뀔 때마다 좁히는 실시간 필터라면 `onValueChange` 만 쓴다. 반대로 검색어를 **저장하거나 검증해야 한다면** 그것은 폼 값이므로 `SInput` 이다.
1032
1037
 
1033
1038
  #### 3-7-2. 하나를 고르게 하는 다섯 — SSelect vs SRadioGroup vs SRadioButton vs STabs vs SRadio
1034
1039
 
@@ -1287,7 +1292,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
1287
1292
  topContent={
1288
1293
  /* 남는 폭 전체를 받는다 — 왼쪽은 그대로, 오른쪽 끝은 ml-auto */
1289
1294
  <div className="flex w-full items-center gap-sd-8">
1290
- <SInput value={keyword} onValueChange={setKeyword} placeholder="통합 검색" />
1295
+ <SSearchInput value={keyword} onValueChange={setKeyword} onSearch={runSearch} placeholder="통합 검색" />
1291
1296
  <SButton size="sm" color="neutral" outline label="내 계정" className="ml-auto" onClick={openAccount} />
1292
1297
  </div>
1293
1298
  }
@@ -1304,7 +1309,7 @@ import { SModalOutlet } from 'sellmate-design-system-react';
1304
1309
  {/* 접히면 menuTop·menuFooter 가 함께 빠지므로, 폴드 레일에 남길 것만 foldedTop 으로 따로 준다 */}
1305
1310
  <SGnb
1306
1311
  items={MENU} value={current} onValueChange={navigate} useRail
1307
- menuTop={<SInput value={keyword} onValueChange={setKeyword} placeholder="메뉴 검색" />}
1312
+ menuTop={<SSearchInput value={keyword} onValueChange={setKeyword} placeholder="메뉴 검색" />}
1308
1313
  menuFooter={<AccountRow />}
1309
1314
  foldedTop={<SGhostButton icon="search" size="sm" ariaLabel="메뉴 검색" onClick={openSearch} />}
1310
1315
  />
@@ -61,6 +61,7 @@
61
61
  - [SFilePicker](../SFilePicker)
62
62
  - [SInput](../SInput)
63
63
  - [SNumberInput](../SNumberInput)
64
+ - [SSearchInput](../SSearchInput)
64
65
  - [SSelect](../SSelect)
65
66
  - [STextarea](../STextarea)
66
67
  - [STimePicker](../STimePicker)
@@ -84,6 +85,7 @@ graph TD;
84
85
  SFilePicker --> SField
85
86
  SInput --> SField
86
87
  SNumberInput --> SField
88
+ SSearchInput --> SField
87
89
  SSelect --> SField
88
90
  STextarea --> SField
89
91
  STimePicker --> SField
@@ -44,6 +44,7 @@
44
44
  - [SOverlayHeader](../SOverlayHeader)
45
45
  - [SPage](../SPage)
46
46
  - [SPopover](../SPopover)
47
+ - [SSearchInput](../SSearchInput)
47
48
  - [SSelect](../SSelect)
48
49
  - [STimePicker](../STimePicker)
49
50
  - [STimeRangePicker](../STimeRangePicker)
@@ -75,6 +76,7 @@ graph TD;
75
76
  SOverlayHeader --> SGhostButton
76
77
  SPage --> SGhostButton
77
78
  SPopover --> SGhostButton
79
+ SSearchInput --> SGhostButton
78
80
  SSelect --> SGhostButton
79
81
  STimePicker --> SGhostButton
80
82
  STimeRangePicker --> SGhostButton
@@ -43,6 +43,7 @@
43
43
  - [SNumberInput](../SNumberInput)
44
44
  - [SPagination](../SPagination)
45
45
  - [SPopover](../SPopover)
46
+ - [SSearchInput](../SSearchInput)
46
47
  - [SSectionHeaderCard](../SSectionHeaderCard)
47
48
  - [SSelect](../SSelect)
48
49
  - [SStepper](../SStepper)
@@ -81,6 +82,7 @@ graph TD;
81
82
  SNumberInput --> SIcon
82
83
  SPagination --> SIcon
83
84
  SPopover --> SIcon
85
+ SSearchInput --> SIcon
84
86
  SSectionHeaderCard --> SIcon
85
87
  SSelect --> SIcon
86
88
  SStepper --> SIcon
@@ -0,0 +1,48 @@
1
+ # SSearchInput
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SSearchInput
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `value?` | `string \| number` | — | 값 (제어) |
12
+ | `size?` | `SSearchInputSize` | `'sm'` | 크기 |
13
+ | `placeholder?` | `string` | `'결과 내 검색'` | 플레이스홀더 |
14
+ | `clearable?` | `boolean` | `false` | 지우기 버튼 — 값이 있을 때만 나타난다 |
15
+ | `disabled?` | `boolean` | `false` | 비활성 |
16
+ | `width?` | `number \| string` | — | 컨트롤 너비 (숫자=px). 미지정 시 부모 폭을 채운다 |
17
+ | `focused?` | `boolean` | — | 포커스 상태 (제어/반영) |
18
+ | `hovered?` | `boolean` | — | 호버 상태 (제어/반영) |
19
+ | `inputClass?` | `string` | — | 내부 input 요소 className |
20
+ | `inputStyle?` | `CSSProperties` | — | 내부 input 요소 style |
21
+ | `className?` | `string` | — | |
22
+ | `style?` | `CSSProperties` | — | |
23
+
24
+ #### Events
25
+
26
+ | Event | Type | Description |
27
+ |-------|------|-------------|
28
+ | `onValueChange` | `(value: string) => void` | 값 변경 — 문자열 전달 |
29
+ | `onChange` | `InputHTMLAttributes<HTMLInputElement>['onChange']` | 네이티브 onChange (form-agnostic 연동용, RHF 등) |
30
+ | `onSearch` | `(value: string) => void` | 검색 실행 — Enter 키에서 현재 값과 함께 호출된다. 한글 조합 중의 Enter(IME 확정)는 검색으로 치지 않는다. |
31
+
32
+ ## Dependencies
33
+
34
+ ### Depends on
35
+
36
+ - [SField](../SField)
37
+ - [SGhostButton](../SGhostButton)
38
+ - [SIcon](../SIcon)
39
+
40
+ ### Graph
41
+
42
+ ```mermaid
43
+ graph TD;
44
+ SSearchInput --> SField
45
+ SSearchInput --> SGhostButton
46
+ SSearchInput --> SIcon
47
+ style SSearchInput fill:#f9f,stroke:#333,stroke-width:4px
48
+ ```
@@ -0,0 +1,46 @@
1
+ import { type CSSProperties, type InputHTMLAttributes } from 'react';
2
+ import { type SFieldSize } from '../SField';
3
+ export type SSearchInputSize = SFieldSize;
4
+ export interface SSearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange' | 'type' | 'prefix' | 'suffix'> {
5
+ /** 값 (제어) */
6
+ value?: string | number;
7
+ /** 값 변경 — 문자열 전달 */
8
+ onValueChange?: (value: string) => void;
9
+ /** 네이티브 onChange (form-agnostic 연동용, RHF 등) */
10
+ onChange?: InputHTMLAttributes<HTMLInputElement>['onChange'];
11
+ /**
12
+ * 검색 실행 — Enter 키에서 현재 값과 함께 호출된다.
13
+ * 한글 조합 중의 Enter(IME 확정)는 검색으로 치지 않는다.
14
+ */
15
+ onSearch?: (value: string) => void;
16
+ /** 크기 */
17
+ size?: SSearchInputSize;
18
+ /** 플레이스홀더 */
19
+ placeholder?: string;
20
+ /** 지우기 버튼 — 값이 있을 때만 나타난다 */
21
+ clearable?: boolean;
22
+ /** 비활성 */
23
+ disabled?: boolean;
24
+ /** 컨트롤 너비 (숫자=px). 미지정 시 부모 폭을 채운다 */
25
+ width?: number | string;
26
+ /** 포커스 상태 (제어/반영) */
27
+ focused?: boolean;
28
+ /** 호버 상태 (제어/반영) */
29
+ hovered?: boolean;
30
+ /** 내부 input 요소 className */
31
+ inputClass?: string;
32
+ /** 내부 input 요소 style */
33
+ inputStyle?: CSSProperties;
34
+ className?: string;
35
+ style?: CSSProperties;
36
+ }
37
+ /**
38
+ * SSearchInput — 목록·결과를 검색어로 좁히는 한 줄 입력.
39
+ *
40
+ * 테두리 박스(border/radius/배경 + hover·focus 강조)는 `SField` 가 소유하고,
41
+ * 이 컴포넌트는 그 안의 돋보기 아이콘 + input 만 그린다.
42
+ * 좌측 아이콘은 **장식**이라 클릭 대상이 아니다 — 검색 실행은 Enter(`onSearch`) 다.
43
+ *
44
+ * 라벨·검증·에러 메시지가 필요한 폼 필드라면 `SInput` 을 쓴다.
45
+ */
46
+ export declare const SSearchInput: import("react").ForwardRefExoticComponent<SSearchInputProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1 @@
1
+ export { SSearchInput, type SSearchInputProps, type SSearchInputSize } from './SSearchInput';
package/dist/index.cjs CHANGED
@@ -14492,6 +14492,119 @@ function SRadioButton({
14492
14492
  }
14493
14493
  );
14494
14494
  }
14495
+ var FONT2 = {
14496
+ sm: {
14497
+ fontSize: 12,
14498
+ lineHeight: 20,
14499
+ paddingX: "var(--cmp-textinput-sm-paddingX)",
14500
+ gap: "var(--cmp-textinput-sm-gap)"
14501
+ },
14502
+ md: {
14503
+ fontSize: 14,
14504
+ lineHeight: 24,
14505
+ paddingX: "var(--cmp-textinput-md-paddingX)",
14506
+ gap: "var(--cmp-textinput-md-gap)"
14507
+ }
14508
+ };
14509
+ var SSearchInput = /* @__PURE__ */ react.forwardRef(function SSearchInput2({
14510
+ value,
14511
+ onValueChange,
14512
+ onChange,
14513
+ onSearch,
14514
+ size = "sm",
14515
+ placeholder = "\uACB0\uACFC \uB0B4 \uAC80\uC0C9",
14516
+ clearable = false,
14517
+ disabled = false,
14518
+ width,
14519
+ focused,
14520
+ hovered,
14521
+ inputClass,
14522
+ inputStyle,
14523
+ className,
14524
+ style,
14525
+ onKeyDown,
14526
+ ...rest
14527
+ }, ref) {
14528
+ const f = FONT2[size];
14529
+ const hasValue = value !== void 0 && value !== null && String(value) !== "";
14530
+ return /* @__PURE__ */ jsxRuntime.jsx(
14531
+ SField,
14532
+ {
14533
+ size,
14534
+ width,
14535
+ focused,
14536
+ hovered,
14537
+ disabled,
14538
+ className,
14539
+ style,
14540
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
14541
+ "div",
14542
+ {
14543
+ className: "flex h-full w-full items-center",
14544
+ style: {
14545
+ gap: f.gap,
14546
+ padding: `0 ${f.paddingX}`,
14547
+ fontSize: f.fontSize,
14548
+ lineHeight: `${f.lineHeight}px`
14549
+ },
14550
+ children: [
14551
+ /* @__PURE__ */ jsxRuntime.jsx(
14552
+ SIcon,
14553
+ {
14554
+ name: "search",
14555
+ size: 16,
14556
+ color: disabled ? "var(--sys-color-disabled-icon)" : "var(--cmp-textinput-icon-default)",
14557
+ className: "flex-shrink-0"
14558
+ }
14559
+ ),
14560
+ /* @__PURE__ */ jsxRuntime.jsx(
14561
+ "input",
14562
+ {
14563
+ ref,
14564
+ type: "search",
14565
+ value: value ?? "",
14566
+ disabled,
14567
+ placeholder,
14568
+ onChange: (e) => {
14569
+ onChange?.(e);
14570
+ onValueChange?.(e.target.value);
14571
+ },
14572
+ onKeyDown: (e) => {
14573
+ onKeyDown?.(e);
14574
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
14575
+ onSearch?.(e.currentTarget.value);
14576
+ }
14577
+ },
14578
+ className: cn(
14579
+ // type="search" 의 브라우저 기본 취소 버튼은 clearable 과 겹치므로 지운다
14580
+ "h-full min-w-0 flex-1 border-0 bg-transparent p-0 outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden",
14581
+ AUTOFILL_RESET_CLASS,
14582
+ inputClass
14583
+ ),
14584
+ style: {
14585
+ // fontSize/lineHeight 는 컨테이너에서 상속 (원본 font-size: inherit)
14586
+ color: disabled ? "var(--cmp-textinput-text-disabled)" : "var(--sys-color-field-text-default)",
14587
+ ...inputStyle
14588
+ },
14589
+ ...rest
14590
+ }
14591
+ ),
14592
+ clearable && hasValue && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
14593
+ SGhostButton,
14594
+ {
14595
+ icon: "close",
14596
+ size: "xxs",
14597
+ ariaLabel: "\uC9C0\uC6B0\uAE30",
14598
+ className: "flex-shrink-0",
14599
+ onClick: () => onValueChange?.("")
14600
+ }
14601
+ )
14602
+ ]
14603
+ }
14604
+ )
14605
+ }
14606
+ );
14607
+ });
14495
14608
  var CHIPFILTER_LISTBOX_WIDTH = 240;
14496
14609
  var CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH = 246;
14497
14610
  var CHIPFILTER = {
@@ -16375,7 +16488,7 @@ var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
16375
16488
  }
16376
16489
  );
16377
16490
  });
16378
- var FONT2 = {
16491
+ var FONT3 = {
16379
16492
  sm: {
16380
16493
  fontSize: 12,
16381
16494
  paddingX: "var(--cmp-textinput-sm-paddingX)",
@@ -16423,7 +16536,7 @@ var SBarcodeInput = /* @__PURE__ */ react.forwardRef(
16423
16536
  className,
16424
16537
  style
16425
16538
  }, ref) {
16426
- const f = FONT2[size];
16539
+ const f = FONT3[size];
16427
16540
  const [ruleError, setRuleError] = react.useState("");
16428
16541
  const hasValue = value !== void 0 && value !== null && String(value) !== "";
16429
16542
  useFormField({
@@ -17521,6 +17634,7 @@ exports.SRadioButton = SRadioButton;
17521
17634
  exports.SRadioGroup = SRadioGroup;
17522
17635
  exports.SSPLITTER_UNITS = SSPLITTER_UNITS;
17523
17636
  exports.SScrollArea = SScrollArea;
17637
+ exports.SSearchInput = SSearchInput;
17524
17638
  exports.SSectionHeaderCard = SSectionHeaderCard2;
17525
17639
  exports.SSelect = SSelect2;
17526
17640
  exports.SSplitter = SSplitter2;