sellmate-design-system-react 9.0.0-beta.14 → 9.0.0-beta.16

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.
Files changed (54) hide show
  1. package/AGENTS.md +244 -45
  2. package/README.md +45 -12
  3. package/dist/components/SBarcodeInput/README.md +1 -1
  4. package/dist/components/SBarcodeInput/SBarcodeInput.d.ts +7 -1
  5. package/dist/components/SChipInput/README.md +1 -1
  6. package/dist/components/SChipInput/SChipInput.d.ts +7 -1
  7. package/dist/components/SDatePicker/README.md +1 -1
  8. package/dist/components/SDatePicker/SDatePicker.d.ts +7 -1
  9. package/dist/components/SDateRangePicker/README.md +1 -1
  10. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +7 -1
  11. package/dist/components/SField/README.md +3 -3
  12. package/dist/components/SField/SField.d.ts +12 -6
  13. package/dist/components/SFilePicker/README.md +1 -1
  14. package/dist/components/SFilePicker/SFilePicker.d.ts +7 -1
  15. package/dist/components/SGhostButton/README.md +2 -0
  16. package/dist/components/SInput/README.md +1 -1
  17. package/dist/components/SInput/SInput.d.ts +7 -1
  18. package/dist/components/SKeyValueTable/README.md +4 -1
  19. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +6 -2
  20. package/dist/components/SNumberInput/README.md +1 -1
  21. package/dist/components/SNumberInput/SNumberInput.d.ts +7 -1
  22. package/dist/components/SPage/README.md +1 -1
  23. package/dist/components/SPage/SPage.d.ts +7 -1
  24. package/dist/components/SSearchInput/README.md +1 -1
  25. package/dist/components/SSearchInput/SSearchInput.d.ts +8 -2
  26. package/dist/components/SSelect/README.md +1 -1
  27. package/dist/components/SSelect/SSelect.d.ts +8 -1
  28. package/dist/components/SSwitch/README.md +13 -0
  29. package/dist/components/STable/README.md +28 -67
  30. package/dist/components/STable/STable.d.ts +70 -6
  31. package/dist/components/STable/index.d.ts +1 -1
  32. package/dist/components/STextarea/README.md +1 -1
  33. package/dist/components/STextarea/STextarea.d.ts +7 -1
  34. package/dist/components/STimePicker/README.md +1 -1
  35. package/dist/components/STimePicker/STimePicker.d.ts +7 -1
  36. package/dist/components/STimePicker/timepicker.config.d.ts +4 -2
  37. package/dist/components/STimeRangePicker/README.md +1 -1
  38. package/dist/components/STimeRangePicker/STimeRangePicker.d.ts +7 -1
  39. package/dist/components/STooltip/README.md +2 -0
  40. package/dist/index.cjs +143 -97
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +141 -98
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/field-width.d.ts +31 -0
  46. package/dist/llms-full.txt +301 -131
  47. package/dist/llms.txt +244 -45
  48. package/dist/styles.css +10 -6
  49. package/eslint/index.mjs +5 -0
  50. package/eslint/lib/table-column.mjs +17 -0
  51. package/eslint/rules/field-width-grade.mjs +27 -10
  52. package/eslint/rules/table-column-width.mjs +109 -0
  53. package/eslint/scale.gen.mjs +3 -0
  54. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export type SBarcodeInputSize = SFieldSize;
8
9
  export interface SBarcodeInputProps {
9
10
  value?: string | number | null;
@@ -39,7 +40,12 @@ export interface SBarcodeInputProps {
39
40
  hint?: string;
40
41
  error?: boolean;
41
42
  errorMessage?: string;
42
- width?: number | string;
43
+ /**
44
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
45
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리며 `maxLength`(= 스키마 상한)로 고른다.
46
+ * 상한이 `xl` 을 넘거나 상한이 없으면 `"100%"`. (`sellmate/field-width-grade` 가 검사한다)
47
+ */
48
+ width?: SFieldWidth;
43
49
  className?: string;
44
50
  style?: CSSProperties;
45
51
  }
@@ -30,7 +30,7 @@
30
30
  | `labelWidth?` | `number \| string` | — | |
31
31
  | `hint?` | `string` | — | |
32
32
  | `errorMessage?` | `string` | — | |
33
- | `width?` | `number \| string` | — | |
33
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
34
34
  | `status?` | `SFieldStatus` | — | |
35
35
  | `icon?` | `SIconName` | — | |
36
36
  | `iconColor?` | `SColor` | — | |
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export type SChipInputSize = SFieldSize;
8
9
  export type SChipInputMetaPlacement = 'end' | 'inline';
9
10
  /** ref로 노출되는 UX 메서드 (sd-chip-input @Method sdFocus 대응) */
@@ -50,7 +51,12 @@ export interface SChipInputProps {
50
51
  labelWidth?: number | string;
51
52
  hint?: string;
52
53
  errorMessage?: string;
53
- width?: number | string;
54
+ /**
55
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
56
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
57
+ * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
58
+ */
59
+ width?: SFieldWidth;
54
60
  status?: SFieldStatus;
55
61
  icon?: SIconName;
56
62
  iconColor?: SColor;
@@ -33,7 +33,7 @@
33
33
  | `placeholder?` | `string` | — | |
34
34
  | `selectable?` | `[string, string]` | — | 선택 가능 범위 [시작, 종료] |
35
35
  | `disabled?` | `boolean` | `false` | |
36
- | `width?` | `number \| string` | — | |
36
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
37
37
  | `name?` | `string` | — | |
38
38
  | `rules?` | `Rule[]` | — | |
39
39
  | `status?` | `SFieldStatus` | — | |
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export type SDatePickerSize = SFieldSize;
8
9
  export type SDatePickerMode = 'date' | 'month' | 'year';
9
10
  export interface SDatePickerProps {
@@ -23,7 +24,12 @@ export interface SDatePickerProps {
23
24
  /** 선택 가능 범위 [시작, 종료] */
24
25
  selectable?: [string, string];
25
26
  disabled?: boolean;
26
- width?: number | string;
27
+ /**
28
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
29
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
30
+ * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
31
+ */
32
+ width?: SFieldWidth;
27
33
  name?: string;
28
34
  rules?: Rule[];
29
35
  status?: SFieldStatus;
@@ -15,7 +15,7 @@
15
15
  | `maxRange?` | `number` | — | 최대 선택 일수 |
16
16
  | `useTimePicker?` | `boolean` | `false` | 시간(시:분) 선택 푸터 사용 여부. true이면 value는 "YYYY-MM-DD HH:mm" 형식이 되고, 캘린더 하단에 시작·종료 시간 입력이 표시된다. |
17
17
  | `disabled?` | `boolean` | `false` | |
18
- | `width?` | `number \| string` | — | |
18
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
19
19
  | `name?` | `string` | — | |
20
20
  | `rules?` | `Rule[]` | — | |
21
21
  | `status?` | `SFieldStatus` | — | |
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export type SDateRangeValue = [string, string] | null;
8
9
  export type SDateRangePickerSize = SFieldSize;
9
10
  export interface SDateRangePickerProps {
@@ -26,7 +27,12 @@ export interface SDateRangePickerProps {
26
27
  */
27
28
  useTimePicker?: boolean;
28
29
  disabled?: boolean;
29
- width?: number | string;
30
+ /**
31
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
32
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
33
+ * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
34
+ */
35
+ width?: SFieldWidth;
30
36
  name?: string;
31
37
  rules?: Rule[];
32
38
  status?: SFieldStatus;
@@ -28,9 +28,9 @@
28
28
  | `hint?` | `string` | `''` | 하단 힌트 |
29
29
  | `disabled?` | `boolean` | `false` | 비활성 |
30
30
  | `readOnly?` | `boolean` | `false` | 읽기 전용 (회색 배경) |
31
- | `width?` | `number \| string` | — | 컨트롤 너비 (숫자=px). 지정하면 필드가 부모 폭을 다 먹지 않고 (레이블 + width) 만큼만 차지한다. 다른 요소와 나란히 놓으려면 부모를 flex 로 두면 된다. |
32
- | `minWidth?` | `number \| string` | — | 컨트롤 최소 너비 (숫자=px). 하한선만 지정하며 필드는 계속 부모 폭을 채운다 |
33
- | `maxWidth?` | `number \| string` | — | 컨트롤 최대 너비 (숫자=px). 상한선만 지정하며 필드는 그 아래에서 부모 폭을 채운다. `width` 와 달리 hug 로 전환하지 않는다 — 부모가 좁으면 같이 좁아지고, 넓어도 여기서 멈춘다. 값 길이가 정해진 컨트롤(날짜·시간)이 `width="100%"` 를 받아 행 전체로 늘어나는 것을 막는 데 쓴다. **라벨은 상한에 들어가지 않는다** — 라벨은 컨트롤의 형제라 이 상한 밖이다. `addonLabel` 은 테두리 박스 안이라 상한을 나눠 먹으므로, `labelWidth`(= addon 폭)만큼 상한을 자동으로 늘린다. addon 이 있는데 `labelWidth` 가 없으면 폭을 알 수 없어 상한을 걸지 않는다 — 컨트롤이 잘리는 것보다 넓은 편이 낫다. |
31
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비. 지정하면 필드가 부모 폭을 다 먹지 않고 (레이블 + width) 만큼만 차지한다. 다른 요소와 나란히 놓으려면 부모를 flex 로 두면 된다. **폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`)으로 준다** — `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 등급은 `maxLength`(= 스키마 상한)로 고르고, 상한이 `xl` 을 넘거나 상한이 없으면 `"100%"` 로 둔다. (`sellmate/field-width-grade` 가 검사한다) 숫자는 px, 그 밖의 문자열은 CSS 길이 그대로다. |
32
+ | `minWidth?` | `SFieldWidth` | — | 컨트롤 최소 너비 (등급 · 숫자=px · CSS 길이). 하한선만 지정하며 필드는 계속 부모 폭을 채운다 |
33
+ | `maxWidth?` | `SFieldWidth` | — | 컨트롤 최대 너비 (등급 · 숫자=px · CSS 길이). 상한선만 지정하며 필드는 그 아래에서 부모 폭을 채운다. `width` 와 달리 hug 로 전환하지 않는다 — 부모가 좁으면 같이 좁아지고, 넓어도 여기서 멈춘다. 값 길이가 정해진 컨트롤(날짜·시간)이 `width="100%"` 를 받아 행 전체로 늘어나는 것을 막는 데 쓴다. **라벨은 상한에 들어가지 않는다** — 라벨은 컨트롤의 형제라 이 상한 밖이다. `addonLabel` 은 테두리 박스 안이라 상한을 나눠 먹으므로, `labelWidth`(= addon 폭)만큼 상한을 자동으로 늘린다. addon 이 있는데 `labelWidth` 가 없으면 폭을 알 수 없어 상한을 걸지 않는다 — 컨트롤이 잘리는 것보다 넓은 편이 낫다. |
34
34
  | `multiline?` | `boolean` | `false` | 멀티라인(textarea) — 컨트롤 높이를 고정하지 않고 min-height만 적용 |
35
35
  | `borderless?` | `boolean` | `false` | 테두리 박스 제거 (inline 컨트롤용) — border/배경/hover·focus 강조만 사라지고 label·hint·errorMessage 등 나머지 필드 구성은 그대로 동작한다. |
36
36
  | `children?` | `ReactNode` | — | 실제 컨트롤 (input/select 등) — 테두리 없이 렌더, 테두리는 SField가 제공 |
@@ -3,6 +3,7 @@ import { type SIconName } from '../SIcon';
3
3
  import { type STooltipProps } from '../STooltip';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
+ import { type SFieldWidth } from '../../lib/field-width';
6
7
  export type SFieldSize = 'sm' | 'md';
7
8
  export type SFieldAddonAlign = 'start' | 'center' | 'end';
8
9
  export type SFieldStatus = 'default' | 'pass' | 'error';
@@ -57,14 +58,19 @@ export interface SFieldProps {
57
58
  /** 읽기 전용 (회색 배경) */
58
59
  readOnly?: boolean;
59
60
  /**
60
- * 컨트롤 너비 (숫자=px). 지정하면 필드가 부모 폭을 다 먹지 않고 (레이블 + width) 만큼만 차지한다.
61
+ * 컨트롤 너비. 지정하면 필드가 부모 폭을 다 먹지 않고 (레이블 + width) 만큼만 차지한다.
61
62
  * 다른 요소와 나란히 놓으려면 부모를 flex 로 두면 된다.
63
+ *
64
+ * **폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`)으로 준다** — `--cmp-field-width-*` 토큰으로 풀리므로
65
+ * 토큰이 바뀌면 화면이 따라간다. 등급은 `maxLength`(= 스키마 상한)로 고르고, 상한이 `xl` 을
66
+ * 넘거나 상한이 없으면 `"100%"` 로 둔다. (`sellmate/field-width-grade` 가 검사한다)
67
+ * 숫자는 px, 그 밖의 문자열은 CSS 길이 그대로다.
62
68
  */
63
- width?: number | string;
64
- /** 컨트롤 최소 너비 (숫자=px). 하한선만 지정하며 필드는 계속 부모 폭을 채운다 */
65
- minWidth?: number | string;
69
+ width?: SFieldWidth;
70
+ /** 컨트롤 최소 너비 (등급 · 숫자=px · CSS 길이). 하한선만 지정하며 필드는 계속 부모 폭을 채운다 */
71
+ minWidth?: SFieldWidth;
66
72
  /**
67
- * 컨트롤 최대 너비 (숫자=px). 상한선만 지정하며 필드는 그 아래에서 부모 폭을 채운다.
73
+ * 컨트롤 최대 너비 (등급 · 숫자=px · CSS 길이). 상한선만 지정하며 필드는 그 아래에서 부모 폭을 채운다.
68
74
  *
69
75
  * `width` 와 달리 hug 로 전환하지 않는다 — 부모가 좁으면 같이 좁아지고, 넓어도 여기서 멈춘다.
70
76
  * 값 길이가 정해진 컨트롤(날짜·시간)이 `width="100%"` 를 받아 행 전체로 늘어나는 것을 막는 데 쓴다.
@@ -74,7 +80,7 @@ export interface SFieldProps {
74
80
  * 상한을 자동으로 늘린다. addon 이 있는데 `labelWidth` 가 없으면 폭을 알 수 없어 상한을
75
81
  * 걸지 않는다 — 컨트롤이 잘리는 것보다 넓은 편이 낫다.
76
82
  */
77
- maxWidth?: number | string;
83
+ maxWidth?: SFieldWidth;
78
84
  /** 멀티라인(textarea) — 컨트롤 높이를 고정하지 않고 min-height만 적용 */
79
85
  multiline?: boolean;
80
86
  /**
@@ -35,7 +35,7 @@
35
35
  | `hint?` | `string` | — | |
36
36
  | `error?` | `boolean` | — | |
37
37
  | `errorMessage?` | `string` | — | |
38
- | `width?` | `number \| string` | — | |
38
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
39
39
  | `className?` | `string` | — | |
40
40
  | `style?` | `CSSProperties` | — | |
41
41
 
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export type SFilePickerValue = File[] | File | null;
8
9
  export type SFilePickerRejectReason = 'max-file-size' | 'max-total-size' | 'max-files';
9
10
  export interface SFilePickerProps {
@@ -45,7 +46,12 @@ export interface SFilePickerProps {
45
46
  hint?: string;
46
47
  error?: boolean;
47
48
  errorMessage?: string;
48
- width?: number | string;
49
+ /**
50
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
51
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
52
+ * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
53
+ */
54
+ width?: SFieldWidth;
49
55
  className?: string;
50
56
  style?: CSSProperties;
51
57
  }
@@ -59,6 +59,7 @@ export type SGhostButtonIntent = 'default' | 'danger' | 'action' | 'subAction' |
59
59
  - [SPage](../SPage)
60
60
  - [SPopover](../SPopover)
61
61
  - [SSearchInput](../SSearchInput)
62
+ - [STable](../STable)
62
63
  - [STimePicker](../STimePicker)
63
64
  - [STimeRangePicker](../STimeRangePicker)
64
65
  - [SToast](../SToast)
@@ -90,6 +91,7 @@ graph TD;
90
91
  SPage --> SGhostButton
91
92
  SPopover --> SGhostButton
92
93
  SSearchInput --> SGhostButton
94
+ STable --> SGhostButton
93
95
  STimePicker --> SGhostButton
94
96
  STimeRangePicker --> SGhostButton
95
97
  SToast --> SGhostButton
@@ -31,7 +31,7 @@
31
31
  | `errorMessage?` | `string` | — | |
32
32
  | `addonLabel?` | `string` | — | |
33
33
  | `addonAlign?` | `SFieldAddonAlign` | — | 어드온 정렬 |
34
- | `width?` | `number \| string` | — | |
34
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리며 `maxLength`(= 스키마 상한)로 고른다. 상한이 `xl` 을 넘거나 상한이 없으면 `"100%"`. (`sellmate/field-width-grade` 가 검사한다) |
35
35
  | `disabled?` | `boolean` | `false` | |
36
36
  | `readOnly?` | `boolean` | `false` | |
37
37
  | `className?` | `string` | — | |
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export interface SInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange' | 'type' | 'prefix' | 'suffix'> {
8
9
  /** 값 (제어) */
9
10
  value?: string | number;
@@ -49,7 +50,12 @@ export interface SInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
49
50
  addonLabel?: string;
50
51
  /** 어드온 정렬 */
51
52
  addonAlign?: SFieldAddonAlign;
52
- width?: number | string;
53
+ /**
54
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
55
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리며 `maxLength`(= 스키마 상한)로 고른다.
56
+ * 상한이 `xl` 을 넘거나 상한이 없으면 `"100%"`. (`sellmate/field-width-grade` 가 검사한다)
57
+ */
58
+ width?: SFieldWidth;
53
59
  disabled?: boolean;
54
60
  readOnly?: boolean;
55
61
  className?: string;
@@ -88,6 +88,7 @@ export type SKeyValueFieldByType =
88
88
  | { type: 'select'; options?: FieldOptions<SSelectProps> }
89
89
  | { type: 'radio'; options?: FieldOptions<SRadioGroupProps> }
90
90
  | { type: 'checkbox'; options?: FieldOptions<SCheckboxProps> }
91
+ | { type: 'switch'; options?: FieldOptions<SSwitchProps> }
91
92
  | { type: 'date-picker'; options?: FieldOptions<SDatePickerProps> }
92
93
  | { type: 'date-range-picker'; options?: FieldOptions<SDateRangePickerProps> }
93
94
  | { type: 'file-picker'; options?: FieldOptions<SFilePickerProps> };
@@ -100,7 +101,7 @@ export type SKeyValueFieldByType =
100
101
  * field type별 `onChange`로 올라오는 값 타입.
101
102
  * - `string`: input · textarea · select · radio · date-picker
102
103
  * - `number`: number-input
103
- * - `boolean`: checkbox
104
+ * - `boolean`: checkbox · switch
104
105
  * - `string[]`: date-range-picker
105
106
  */
106
107
  export type SKeyValueFieldValue = string | number | boolean | string[];
@@ -120,6 +121,7 @@ export type SKeyValueFieldValue = string | number | boolean | string[];
120
121
  - [SNumberInput](../SNumberInput)
121
122
  - [SRadio](../SRadio)
122
123
  - [SSelect](../SSelect)
124
+ - [SSwitch](../SSwitch)
123
125
  - [STextarea](../STextarea)
124
126
  - [STooltip](../STooltip)
125
127
 
@@ -137,6 +139,7 @@ graph TD;
137
139
  SKeyValueTable --> SNumberInput
138
140
  SKeyValueTable --> SRadio
139
141
  SKeyValueTable --> SSelect
142
+ SKeyValueTable --> SSwitch
140
143
  SKeyValueTable --> STextarea
141
144
  SKeyValueTable --> STooltip
142
145
  style SKeyValueTable fill:#f9f,stroke:#333,stroke-width:4px
@@ -5,10 +5,11 @@ import { type SNumberInputProps } from '../SNumberInput';
5
5
  import { type SSelectProps } from '../SSelect';
6
6
  import { type SRadioGroupProps } from '../SRadio';
7
7
  import { type SCheckboxProps } from '../SCheckbox';
8
+ import { type SSwitchProps } from '../SSwitch';
8
9
  import { type SDatePickerProps } from '../SDatePicker';
9
10
  import { type SDateRangePickerProps } from '../SDateRangePicker';
10
11
  import { type SFilePickerProps } from '../SFilePicker';
11
- export type SKeyValueFieldType = 'text' | 'input' | 'textarea' | 'number-input' | 'select' | 'radio' | 'checkbox' | 'date-picker' | 'date-range-picker' | 'file-picker';
12
+ export type SKeyValueFieldType = 'text' | 'input' | 'textarea' | 'number-input' | 'select' | 'radio' | 'checkbox' | 'switch' | 'date-picker' | 'date-range-picker' | 'file-picker';
12
13
  /** field type과 무관한 공통 속성 (레이아웃·레이블·스팬 등) */
13
14
  export interface SKeyValueFieldBase {
14
15
  name: string;
@@ -59,6 +60,9 @@ export type SKeyValueFieldByType = {
59
60
  } | {
60
61
  type: 'checkbox';
61
62
  options?: FieldOptions<SCheckboxProps>;
63
+ } | {
64
+ type: 'switch';
65
+ options?: FieldOptions<SSwitchProps>;
62
66
  } | {
63
67
  type: 'date-picker';
64
68
  options?: FieldOptions<SDatePickerProps>;
@@ -74,7 +78,7 @@ export type SKeyValueField = SKeyValueFieldBase & SKeyValueFieldByType;
74
78
  * field type별 `onChange`로 올라오는 값 타입.
75
79
  * - `string`: input · textarea · select · radio · date-picker
76
80
  * - `number`: number-input
77
- * - `boolean`: checkbox
81
+ * - `boolean`: checkbox · switch
78
82
  * - `string[]`: date-range-picker
79
83
  */
80
84
  export type SKeyValueFieldValue = string | number | boolean | string[];
@@ -40,7 +40,7 @@
40
40
  | `hint?` | `string` | — | |
41
41
  | `error?` | `boolean` | — | |
42
42
  | `errorMessage?` | `string` | — | |
43
- | `width?` | `number \| string` | — | |
43
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리며 `max`(= 스키마 상한)로 고른다. 상한이 `xl` 을 넘거나 상한이 없으면 `"100%"`. (`sellmate/field-width-grade` 가 검사한다) |
44
44
  | `className?` | `string` | — | |
45
45
  | `style?` | `CSSProperties` | — | |
46
46
 
@@ -4,6 +4,7 @@ import { type SIconName } from '../SIcon';
4
4
  import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
+ import { type SFieldWidth } from '../../lib/field-width';
7
8
  export type SNumberInputSize = SFieldSize;
8
9
  export interface SNumberInputProps {
9
10
  /** 입력 값 */
@@ -59,7 +60,12 @@ export interface SNumberInputProps {
59
60
  hint?: string;
60
61
  error?: boolean;
61
62
  errorMessage?: string;
62
- width?: number | string;
63
+ /**
64
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
65
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리며 `max`(= 스키마 상한)로 고른다.
66
+ * 상한이 `xl` 을 넘거나 상한이 없으면 `"100%"`. (`sellmate/field-width-grade` 가 검사한다)
67
+ */
68
+ width?: SFieldWidth;
63
69
  className?: string;
64
70
  style?: CSSProperties;
65
71
  }
@@ -27,7 +27,7 @@
27
27
  | Prop | Type | Default | Description |
28
28
  |------|------|---------|-------------|
29
29
  | `background?` | `SPageBackground` | `'frame'` | 페이지 배경 표면. frame=흰 콘텐츠 면, neutral=옅은 회색 면, screen=앱 바탕. 스크롤바 처리도 여기 묶여 있다 — 셋 다 구분선+트랙 배경이고, 트랙 색만 neutral 에서 흰색이 된다. |
30
- | `scrollEndSpacing?` | `boolean` | `true` | 스크롤 끝 여백. 마지막 항목이 창 하단에 붙어 "여기서 끝"이 안 읽히는 것을 막는다. 기본으로 켜져 있고, 페이지네이션처럼 끝을 알려주는 것이 이미 있으면 끈다(목록 페이지). |
30
+ | `scrollEndSpacing?` | `boolean` | `false` | 스크롤 끝 여백. 마지막 항목이 창 하단에 붙어 "여기서 끝"이 안 읽히는 것을 막는다. **기본은 꺼져 있다.** 페이지 스크롤 자체가 예외이기 때문이다 — 대부분의 화면은 본문이 창을 꽉 채우고 스크롤은 각 영역 안에서 일어난다. 여백을 조건 없이 붙이면 내용이 화면에 거의 딱 맞는 페이지까지 그 여백 때문에 스크롤되게 만든다. 페이지가 실제로 스크롤되는 화면(`contentHeight="auto"` + 내용이 창보다 김)에서만 켠다. 페이지네이션처럼 끝을 알려주는 것이 이미 있으면 켜지 않는다. |
31
31
  | `overlayScrollbar?` | `boolean` | `false` | true면 네이티브 스크롤바를 숨기고 SPage 위에 오버레이 스크롤바를 얹는다. 스크롤바가 레이아웃 폭을 차지하지 않아 내부 콘텐츠 폭이 줄어들지 않는다. |
32
32
  | `contentHeight?` | `SPageContentHeight` | `'auto'` | 본문 높이 모드. 기본은 `auto` — 콘텐츠가 흐르고 넘치면 페이지가 스크롤한다. `fill` 은 본문이 남은 높이를 정확히 채우고 페이지는 스크롤하지 않는다. 표가 자기 안에서 스크롤하고 페이지네이션이 하단에 고정되는 목록 화면용이다. 본문 루트에 `h-full` 이 먹으므로 `<div className="flex h-full min-h-0 flex-col">` + `<STable className="min-h-0 flex-1" />` 구성이 성립한다. `fill` 에서는 페이지가 스크롤하지 않으므로 `scrollEndSpacing` 이 무시된다. |
33
33
  | `header?` | `SPageHeaderProps` | — | 페이지 타이틀 영역(pageHeader 포팅). 주면 스크롤·페이지 패딩 밖으로 빼내 상단에 고정 배치한다. `children` 은 항상 본문이다 — header 유무와 무관하게 같은 자리에 같은 뜻으로 들어간다. |
@@ -9,7 +9,13 @@ export interface SPageProps extends HTMLAttributes<HTMLElement> {
9
9
  background?: SPageBackground;
10
10
  /**
11
11
  * 스크롤 끝 여백. 마지막 항목이 창 하단에 붙어 "여기서 끝"이 안 읽히는 것을 막는다.
12
- * 기본으로 켜져 있고, 페이지네이션처럼 끝을 알려주는 것이 이미 있으면 끈다(목록 페이지).
12
+ *
13
+ * **기본은 꺼져 있다.** 페이지 스크롤 자체가 예외이기 때문이다 — 대부분의 화면은 본문이
14
+ * 창을 꽉 채우고 스크롤은 각 영역 안에서 일어난다. 여백을 조건 없이 붙이면 내용이 화면에
15
+ * 거의 딱 맞는 페이지까지 그 여백 때문에 스크롤되게 만든다.
16
+ *
17
+ * 페이지가 실제로 스크롤되는 화면(`contentHeight="auto"` + 내용이 창보다 김)에서만 켠다.
18
+ * 페이지네이션처럼 끝을 알려주는 것이 이미 있으면 켜지 않는다.
13
19
  */
14
20
  scrollEndSpacing?: boolean;
15
21
  /**
@@ -13,7 +13,7 @@
13
13
  | `placeholder?` | `string` | `'결과 내 검색'` | 플레이스홀더 |
14
14
  | `clearable?` | `boolean` | `false` | 지우기 버튼 — 값이 있을 때만 나타난다 |
15
15
  | `disabled?` | `boolean` | `false` | 비활성 |
16
- | `width?` | `number \| string` | — | 컨트롤 너비 (숫자=px). 미지정 시 부모 폭을 채운다 |
16
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. 미지정 시 부모 폭을 채운다. |
17
17
  | `focused?` | `boolean` | — | 포커스 상태 (제어/반영) |
18
18
  | `hovered?` | `boolean` | — | 호버 상태 (제어/반영) |
19
19
  | `inputClass?` | `string` | — | 내부 input 요소 className |
@@ -1,5 +1,6 @@
1
1
  import { type CSSProperties, type InputHTMLAttributes } from 'react';
2
2
  import { type SFieldSize } from '../SField';
3
+ import { type SFieldWidth } from '../../lib/field-width';
3
4
  export type SSearchInputSize = SFieldSize;
4
5
  export interface SSearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange' | 'type' | 'prefix' | 'suffix'> {
5
6
  /** 값 (제어) */
@@ -21,8 +22,13 @@ export interface SSearchInputProps extends Omit<InputHTMLAttributes<HTMLInputEle
21
22
  clearable?: boolean;
22
23
  /** 비활성 */
23
24
  disabled?: boolean;
24
- /** 컨트롤 너비 (숫자=px). 미지정 시 부모 폭을 채운다 */
25
- width?: number | string;
25
+ /**
26
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
27
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
28
+ * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
29
+ * 미지정 시 부모 폭을 채운다.
30
+ */
31
+ width?: SFieldWidth;
26
32
  /** 포커스 상태 (제어/반영) */
27
33
  focused?: boolean;
28
34
  /** 호버 상태 (제어/반영) */
@@ -33,7 +33,7 @@
33
33
  | `addonAlign?` | `SFieldAddonAlign` | — | 어드온 정렬 |
34
34
  | `hint?` | `string` | — | |
35
35
  | `errorMessage?` | `string` | — | |
36
- | `width?` | `number \| string` | — | |
36
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. 셀렉트는 `maxLength` 개념이 없어 목록 최장값을 담는 등급으로 고른다. |
37
37
  | `className?` | `string` | — | |
38
38
  | `style?` | `CSSProperties` | — | |
39
39
 
@@ -5,6 +5,7 @@ import { type SIconName } from '../SIcon';
5
5
  import { type SColor } from '../../lib/color';
6
6
  import { type STooltipProps } from '../STooltip';
7
7
  import { type SSelectOption, type SSelectType } from './select.config';
8
+ import { type SFieldWidth } from '../../lib/field-width';
8
9
  export type { SSelectOption, SSelectType } from './select.config';
9
10
  export interface SSelectHandle {
10
11
  /** 트리거 버튼에 포커스 (sdFocus) */
@@ -62,7 +63,13 @@ export interface SSelectProps {
62
63
  addonAlign?: SFieldAddonAlign;
63
64
  hint?: string;
64
65
  errorMessage?: string;
65
- width?: number | string;
66
+ /**
67
+ * 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
68
+ * 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다.
69
+ * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
70
+ * 셀렉트는 `maxLength` 개념이 없어 목록 최장값을 담는 등급으로 고른다.
71
+ */
72
+ width?: SFieldWidth;
66
73
  className?: string;
67
74
  style?: CSSProperties;
68
75
  }
@@ -22,3 +22,16 @@
22
22
  |-------|------|-------------|
23
23
  | `onValueChange` | `(value: boolean) => void` | 변경 (sdUpdate) |
24
24
 
25
+ ## Dependencies
26
+
27
+ ### Used by
28
+
29
+ - [SKeyValueTable](../SKeyValueTable)
30
+
31
+ ### Graph
32
+
33
+ ```mermaid
34
+ graph TD;
35
+ SKeyValueTable --> SSwitch
36
+ style SSwitch fill:#f9f,stroke:#333,stroke-width:4px
37
+ ```
@@ -13,6 +13,7 @@
13
13
  | `rowKey?` | `string` | `'id'` | 행 식별 필드 |
14
14
  | `selectable?` | `boolean` | `false` | 행 선택 체크박스 |
15
15
  | `selected?` | `SRow[]` | `[]` | |
16
+ | `sort?` | `STableSort \| null` | `null` | 정렬 상태 (controlled). `null`·미지정이면 정렬 없음. 컴포넌트는 정렬 상태를 갖지 않는다 — 서버 정렬이면 이 값이 곧 조회 조건이고, 뒤로가기·새로고침·링크 공유로 복원돼야 하므로 진실은 URL·store 쪽에 있어야 한다. 행을 실제로 정렬하는 것도 소비 앱 몫이다 (`STable` 은 받은 순서대로 그린다). |
16
17
  | `resizable?` | `boolean` | `false` | 컬럼 너비 조절 |
17
18
  | `width?` | `string` | — | |
18
19
  | `height?` | `string` | — | |
@@ -42,6 +43,7 @@
42
43
  | Event | Type | Description |
43
44
  |-------|------|-------------|
44
45
  | `onSelectedChange` | `(rows: SRow[]) => void` | |
46
+ | `onSortChange` | `(sort: STableSort \| null) => void` | 정렬 헤더 클릭 (`asc → desc → 해제` 3단). 해제되면 `null` 이 온다. 다중 정렬은 1차 안에서 지원하지 않는다. |
45
47
  | `onPageChange` | `(page: number) => void` | |
46
48
  | `onRowsPerPageChange` | `(perPage: number) => void` | |
47
49
  | `onVirtualUpdate` | `(range: { from: number; to: number }) => void` | |
@@ -65,55 +67,14 @@
65
67
  ### STableColumn
66
68
 
67
69
  ```ts
68
- export interface STableColumn {
69
- name: string;
70
- label: string;
71
- /** 값 접근: 필드명 또는 접근 함수 */
72
- field: string | ((row: SRow) => any);
73
- align?: 'left' | 'center' | 'right';
74
- width?: string;
75
- /** 고정폭 없이 자동 너비 (sticky/resize 계산에서 제외) */
76
- autoWidth?: boolean;
77
- /** 리사이즈 최소/최대 너비 (px) */
78
- minWidth?: number;
79
- maxWidth?: number;
80
- /** 표시 포맷 */
81
- format?: (value: any, row: SRow) => string;
82
- visible?: boolean;
83
- icon?: SIconName;
84
- /** 헤더 아이콘 색상. 팔레트 키(`grey_65`, `red_95` …) 또는 임의 CSS 색상 */
85
- iconColor?: SColor;
86
- /**
87
- * 셀에 무엇이 들어가는지. 좌우 여백이 여기서 갈린다 — 기본은 `text`(넓게).
88
- *
89
- * 자기 폭을 가진 컨트롤(`SInput`·`SNumberInput`·`SSelect` 등)이 들어가는 열은 `control` 로 준다.
90
- * 텍스트와 같은 넓은 여백을 두면 컨트롤이 쓸 폭이 그만큼 줄어 같은 열 폭에서도 좁아 보인다.
91
- *
92
- * `SKeyValueTable` 은 `field.type` 으로 이 판정을 스스로 하지만, `STable` 의 셀은 소비 앱이 넘긴
93
- * 임의의 `render` 결과라 컴포넌트가 알 수 없다. 그래서 열 단위로 받는다.
94
- */
95
- contentType?: 'text' | 'control';
96
- /** 셀 커스텀 렌더 (React 확장) — `<td>` 안쪽 내용만 교체한다 */
97
- render?: (row: SRow, value: any) => ReactNode;
98
- /**
99
- * 셀(`<td>`) 자체를 커스텀 렌더 (React 확장). 반환한 엘리먼트가 그대로 `<td>`가 되므로
100
- * `colSpan` / `rowSpan`을 직접 지정할 수 있다. `render`보다 우선한다.
101
- *
102
- * - `ctx.className` / `ctx.style`을 그대로 펼쳐야 구분선·정렬·sticky 등 기본 스타일이 유지된다.
103
- * - `null`(또는 `false`)을 반환하면 그 자리에는 `<td>`를 만들지 않는다.
104
- * 다른 셀의 `colSpan` / `rowSpan`에 덮이는 자리를 이렇게 비운다.
105
- * - 병합은 sticky 컬럼(인덱스 기준 offset 계산)과 함께 쓰지 않는 것을 전제로 한다.
106
- * `rowSpan`은 행을 잘라 쓰는 가상 스크롤 / 내부 페이지네이션과 경계에서 어긋날 수 있다.
107
- */
108
- renderCell?: (ctx: STableCellContext) => ReactNode;
109
- /** 헤더 셀 커스텀 렌더 (React 확장) */
110
- renderHeader?: (column: STableColumn, index: number) => ReactNode;
111
- /** 본문 셀 커스텀 클래스. 함수를 주면 행마다 다른 클래스를 반환할 수 있다 */
112
- tdClass?: string | ((row: SRow) => string | undefined);
113
- /** 헤더 셀 커스텀 클래스/스타일 (sd-table thClass/thStyle) */
114
- thClass?: string;
115
- thStyle?: CSSProperties;
116
- }
70
+ /**
71
+ * `autoWidth` 스페이서 열은 값을 그리지 않으므로 `field` 를 생략할 수 있다.
72
+ * 그 밖의 열은 값 접근자가 있어야 한다.
73
+ */
74
+ export type STableColumn = STableColumnBase &
75
+ (
76
+ { autoWidth: true; field?: STableColumnField } | { autoWidth?: false; field: STableColumnField }
77
+ );
117
78
  ```
118
79
 
119
80
  ### SRow
@@ -122,6 +83,17 @@ export interface STableColumn {
122
83
  export type SRow = Record<string, any>;
123
84
  ```
124
85
 
86
+ ### STableSort
87
+
88
+ ```ts
89
+ /** 정렬 상태 — 어느 열을 어느 방향으로 정렬했는가 */
90
+ export interface STableSort {
91
+ /** 정렬 기준 컬럼의 `name` */
92
+ name: string;
93
+ dir: 'asc' | 'desc';
94
+ }
95
+ ```
96
+
125
97
  ### STableStickyColumn
126
98
 
127
99
  ```ts
@@ -141,26 +113,11 @@ export interface STablePagination {
141
113
  }
142
114
  ```
143
115
 
144
- ### STableCellContext
116
+ ### STableColumnField
145
117
 
146
118
  ```ts
147
- /** `STableColumn.renderCell`에 전달되는 컨텍스트 */
148
- export interface STableCellContext {
149
- row: SRow;
150
- /** `field` + `format`이 적용된 표시 값 */
151
- value: any;
152
- column: STableColumn;
153
- /** 보이는 컬럼 기준 인덱스 */
154
- colIndex: number;
155
- /** 현재 렌더 중인 행 목록 기준 인덱스 (페이지/가상 스크롤 윈도우 기준) */
156
- rowIndex: number;
157
- /** 행 선택 여부 */
158
- isSelected: boolean;
159
- /** STable이 이 셀에 적용하려던 클래스 (구분선·말줄임·sticky 그림자 포함) */
160
- className: string;
161
- /** STable이 이 셀에 적용하려던 스타일 (패딩·정렬·폭·sticky offset 포함) */
162
- style: CSSProperties;
163
- }
119
+ /** 값 접근: 필드명 또는 접근 함수 */
120
+ export type STableColumnField = string | ((row: SRow) => any);
164
121
  ```
165
122
 
166
123
  ## Dependencies
@@ -169,9 +126,11 @@ export interface STableCellContext {
169
126
 
170
127
  - [SCheckbox](../SCheckbox)
171
128
  - [SCircleProgress](../SCircleProgress)
129
+ - [SGhostButton](../SGhostButton)
172
130
  - [SIcon](../SIcon)
173
131
  - [SPagination](../SPagination)
174
132
  - [SSelect](../SSelect)
133
+ - [STooltip](../STooltip)
175
134
 
176
135
  ### Graph
177
136
 
@@ -179,8 +138,10 @@ export interface STableCellContext {
179
138
  graph TD;
180
139
  STable --> SCheckbox
181
140
  STable --> SCircleProgress
141
+ STable --> SGhostButton
182
142
  STable --> SIcon
183
143
  STable --> SPagination
184
144
  STable --> SSelect
145
+ STable --> STooltip
185
146
  style STable fill:#f9f,stroke:#333,stroke-width:4px
186
147
  ```