sellmate-design-system-react 8.1.0 → 9.0.0-beta.2

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 (50) hide show
  1. package/AGENTS.md +176 -111
  2. package/README.md +38 -8
  3. package/dist/components/SChipFilter/README.md +69 -0
  4. package/dist/components/SChipFilter/SChipFilter.d.ts +160 -0
  5. package/dist/components/SChipFilter/index.d.ts +1 -0
  6. package/dist/components/SCircleProgress/README.md +3 -0
  7. package/dist/components/SCircleProgress/SCircleProgress.d.ts +7 -1
  8. package/dist/components/SDatePicker/README.md +2 -0
  9. package/dist/components/SDateRangePicker/README.md +21 -0
  10. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +16 -0
  11. package/dist/components/SDateRangePicker/index.d.ts +1 -1
  12. package/dist/components/SGhostButton/README.md +4 -0
  13. package/dist/components/SGuide/README.md +1 -1
  14. package/dist/components/SGuide/SGuide.d.ts +1 -1
  15. package/dist/components/SIcon/README.md +4 -0
  16. package/dist/components/SIcon/icons.gen.d.ts +1 -0
  17. package/dist/components/SImage/README.md +44 -0
  18. package/dist/components/SImage/SImage.d.ts +41 -0
  19. package/dist/components/SImage/index.d.ts +1 -0
  20. package/dist/components/SList/README.md +1 -0
  21. package/dist/components/SList/SList.d.ts +2 -0
  22. package/dist/components/SListItem/README.md +2 -2
  23. package/dist/components/SListItem/SListItem.d.ts +2 -2
  24. package/dist/components/SPage/README.md +21 -0
  25. package/dist/components/SPage/SPage.d.ts +10 -0
  26. package/dist/components/SPage/SPageHeader.d.ts +32 -0
  27. package/dist/components/SPage/index.d.ts +1 -0
  28. package/dist/components/SPortal/README.md +1 -1
  29. package/dist/components/SPortal/SPortal.d.ts +7 -1
  30. package/dist/components/SRadio/README.md +2 -0
  31. package/dist/components/SRadioButton/README.md +13 -0
  32. package/dist/components/SSelect/README.md +2 -2
  33. package/dist/components/SSelect/SSelect.d.ts +2 -2
  34. package/dist/components/STag/README.md +2 -0
  35. package/dist/components/STextLink/README.md +2 -0
  36. package/dist/components/SToggle/README.md +1 -0
  37. package/dist/components/SToggle/SToggle.d.ts +4 -1
  38. package/dist/components/STooltip/README.md +2 -0
  39. package/dist/index.cjs +1830 -319
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +1826 -319
  43. package/dist/index.js.map +1 -1
  44. package/dist/lib/floating-width.d.ts +22 -0
  45. package/dist/llms-full.txt +343 -118
  46. package/dist/llms.txt +180 -114
  47. package/dist/styles.css +218 -16
  48. package/dist/theme.css +12 -5
  49. package/eslint/scale.gen.mjs +1 -1
  50. package/package.json +7 -4
@@ -0,0 +1,160 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ import { type SDateRangeValue } from '../SDateRangePicker';
3
+ export type SChipFilterType = 'single' | 'multi' | 'keyword' | 'date' | 'custom';
4
+ export type SChipFilterOptionValue = string | number;
5
+ export type SChipFilterMatchMode = 'contains' | 'exact' | 'excludes';
6
+ export interface SChipFilterOption {
7
+ value: SChipFilterOptionValue;
8
+ label: string;
9
+ disabled?: boolean;
10
+ }
11
+ /** date 필드의 프리셋 라디오 항목 (오늘/지난 7일/사용자 지정 등) */
12
+ export interface SChipFilterDatePreset {
13
+ /** 프리셋 식별자 */
14
+ value: string;
15
+ /** 라벨 */
16
+ label: string;
17
+ /** true면 "사용자 지정" — 선택 시 날짜/기간 피커가 추가로 노출된다. resolve는 무시된다. */
18
+ custom?: boolean;
19
+ /** custom이 아닐 때 실제 값을 계산한다. 단일 날짜(string) 또는 기간([start,end]) 모두 가능 */
20
+ resolve?: () => string | SDateRangeValue;
21
+ }
22
+ /** keyword 필드에서 matchModes 활성 시 사용하는 값 형태 — 입력해 추가한 키워드 목록 */
23
+ export interface SChipFilterKeywordValue {
24
+ keywords: string[];
25
+ mode: SChipFilterMatchMode;
26
+ }
27
+ /** custom 필드가 자유롭게 담는 값. 형태를 강제하지 않는다 — render에서 직접 정의한 그대로 읽고 쓴다 */
28
+ export type SChipFilterCustomValue = Record<string, unknown>;
29
+ export type SChipFilterValue = SChipFilterOptionValue | SChipFilterOptionValue[] | SDateRangeValue | SChipFilterKeywordValue | SChipFilterCustomValue | null | undefined;
30
+ export type SChipFilterValueMap = Record<string, SChipFilterValue>;
31
+ export interface SChipFilterField {
32
+ /** 필터 식별자 */
33
+ key: string;
34
+ /** 칩에 표시할 레이블 */
35
+ label: string;
36
+ /** 필터 타입 */
37
+ type: SChipFilterType;
38
+ /** single/multi/keyword 후보 목록 */
39
+ options?: SChipFilterOption[];
40
+ /** 필수 필터 표시. true면 값이 비어 있을 때 기본값(defaultValue 또는 타입별 내장 기본값)이
41
+ * 자동으로 채워지고, clearable은 현재 값이 기본값과 같을 땐 숨겨지며 클릭 시 기본값으로 되돌아간다 */
42
+ required?: boolean;
43
+ /** 초기값 및 clearable 클릭 시 되돌아갈 값. required 여부와 무관하게 적용된다 — 값이 비어 있으면
44
+ * 마운트(또는 "필터 추가"로 활성화) 시 이 값이 자동으로 채워진다. required인데 지정하지 않으면
45
+ * 타입별 내장 기본값(single: 첫 번째 옵션, date: 오늘 날짜)을 대신 쓴다 */
46
+ defaultValue?: SChipFilterValue;
47
+ /** 경고 테두리 표시 */
48
+ warning?: boolean;
49
+ /** 비활성 상태 */
50
+ disabled?: boolean;
51
+ /** 처음부터 칩으로 노출할지 여부. false면 "필터 추가" 목록에만 나타나고, 거기서 고르기 전까지는
52
+ * 필터 바에 보이지 않는다. activeKeys를 직접 제어하는 경우엔 초기 activeKeys 구성에만 참고되고
53
+ * 이후엔 activeKeys가 우선한다 */
54
+ defaultActive?: boolean;
55
+ /** keyword 입력 placeholder */
56
+ placeholder?: string;
57
+ /** keyword 필드에 검색조건(포함/일치) 토글 표시 여부 */
58
+ matchModes?: boolean;
59
+ /** matchModes 활성 시 "미포함"까지 포함해 3개(포함/일치/미포함)로 노출할지. 기본 false — 기존처럼 2개(포함/일치)만 */
60
+ excludeMode?: boolean;
61
+ /** date 선택 가능 범위 */
62
+ selectable?: [string, string];
63
+ /** date 프리셋의 "사용자 지정" 기간 선택 시 최대 선택 일수 */
64
+ maxRange?: number;
65
+ /** date 필드의 프리셋 라디오 목록. 지정하지 않으면 기존처럼 단일 캘린더 트리거로 동작한다 */
66
+ presets?: SChipFilterDatePreset[];
67
+ /** presets 사용 시 UI 형태 — true: 필터 바에 세그먼트 라디오로 인라인 노출(팝오버 없음),
68
+ * false(기본): 칩 클릭 시 팝오버 안에 세로 라디오 목록(+사용자 지정 선택 시 기간 피커) */
69
+ radioButton?: boolean;
70
+ /** type이 'custom'일 때 필터 바에 이 필드 자리에 놓일 노드를 직접 그린다. 칩+팝오버를 거치지 않고
71
+ * 반환한 노드가 그대로 바에 노출된다 — SSelect를 그대로 놓거나 SInput을 바로 노출하는 식으로
72
+ * 렌더 방식 자체를 완전히 자유롭게 구성한다. */
73
+ render?: (ctx: {
74
+ value: SChipFilterValue;
75
+ disabled?: boolean;
76
+ warning?: boolean;
77
+ onValueChange: (value: SChipFilterValue) => void;
78
+ }) => ReactNode;
79
+ }
80
+ export interface SChipFilterChangeDetail {
81
+ key: string;
82
+ value: SChipFilterValue;
83
+ values: SChipFilterValueMap;
84
+ }
85
+ /** 필터 그룹 검증 규칙 */
86
+ export type SChipFilterGroupRule = {
87
+ type: 'requireKey';
88
+ key: string;
89
+ } | {
90
+ type: 'requireAll';
91
+ } | {
92
+ type: 'requireAny';
93
+ dataGroupName?: string;
94
+ };
95
+ /** 필터를 그룹으로 묶어서 넘길 때의 단위. 그룹이 시작될 때마다 앞에 구분선이 붙는다. */
96
+ export interface SChipFilterGroup {
97
+ fields: SChipFilterField[];
98
+ /** 지정하면 이 규칙으로 그룹을 검증한다. values가 바뀔 때마다 즉시 재평가되는 실시간 검증이라 —
99
+ * 그룹이 rule을 만족하지 못하면 검색 시도 여부와 무관하게 그 즉시 그룹 중앙에 경고 툴팁이 뜬다.
100
+ * 지정 안 하면 검증하지 않는다. */
101
+ rule?: SChipFilterGroupRule;
102
+ /** rule을 만족하지 않을 때 그룹 중앙에 띄울 툴팁 메시지. 지정 안 하면 rule 종류에 따른 기본 문구를 쓴다 */
103
+ tooltipMessage?: string;
104
+ }
105
+ export interface SChipFilterHandle {
106
+ /** 특정 필터 편집 팝오버를 엽니다. */
107
+ open: (key: string) => void;
108
+ /** 모든 필터 값을 초기화합니다. */
109
+ reset: () => void;
110
+ /** fields를 그룹(SChipFilterGroup[])으로 넘겼을 때, 현재 값 기준으로 각 그룹의 rule을 만족하는지
111
+ * 반환합니다. 경고 툴팁은 이 호출과 무관하게 rule 위반 상태인 동안 항상 실시간으로 떠 있으므로,
112
+ * 이 메서드는 그 상태를 그대로 읽어오는 용도다(예: 검색 버튼을 눌러도 되는지 사전 확인) */
113
+ validate: () => boolean;
114
+ }
115
+ export interface SChipFilterProps {
116
+ /** 필터 정의 목록. 그룹으로 묶으려면 SChipFilterGroup[]을 넘긴다 — 그룹이 시작될 때마다 앞에
117
+ * 구분선이 자동으로 붙는다(showLabel·인접 그룹·인라인 date 필터와 중첩되지 않도록 처리됨). */
118
+ fields?: SChipFilterField[] | SChipFilterGroup[];
119
+ /** 필터 값 맵 */
120
+ value?: SChipFilterValueMap;
121
+ /** 전체 값 변경 — 편집 중인 값이 바뀔 때마다(선택할 때마다) 호출된다. 실제 검색 실행은 onSearch를 쓴다 */
122
+ onValueChange?: (value: SChipFilterValueMap) => void;
123
+ /** 개별 필터 값 변경 */
124
+ onFilterChange?: (detail: SChipFilterChangeDetail) => void;
125
+ /** 실제 검색을 실행할 시점 — 편집 팝오버의 "검색" 버튼을 누르거나 팝오버가 닫힐 때(바깥 클릭·Esc·다른
126
+ * 칩으로 전환 포함) 그 시점의 전체 값 맵과 함께 호출된다. 팝오버가 없는 필드(인라인 date 프리셋·custom,
127
+ * clearable ×, 검색 초기화)는 값이 바뀌는 즉시 호출된다. dirty 체크가 기본 적용되어 있어 — 마지막으로
128
+ * 실제 검색이 실행된 값 맵과 비교해 하나라도 달라진 게 없으면(예: 팝오버를 열었다 아무것도 안 바꾸고
129
+ * 닫는 경우) 호출되지 않는다. fields를 그룹으로 넘겼다면 rule을 만족하지 못한 그룹이 있는 동안엔
130
+ * onSearch가 호출되지 않는다 — 해당 그룹의 경고 툴팁은 이 시점과 무관하게 값이 비어 있는 동안 항상
131
+ * 실시간으로 떠 있다(별도로 validate()를 호출할 필요 없음) */
132
+ onSearch?: (value: SChipFilterValueMap) => void;
133
+ /** 노출 필터 key 목록. 지정하면 제어 컴포넌트로 동작 — "필터 추가"로 고른 필드를 이 배열에 직접
134
+ * 넣어줘야 칩이 나타난다(onActiveKeysChange에서 받은 keys로 갱신). 지정하지 않으면 필드별
135
+ * defaultActive 값을 기준으로 컴포넌트가 내부 상태로 관리하는 비제어 방식으로 동작한다 */
136
+ activeKeys?: string[];
137
+ /** 노출 필터 key 변경(칩 추가·제거) — 비제어 방식에서도 참고용으로 호출된다.
138
+ * activeKeys를 직접 제어할 때는 이 값을 그대로 activeKeys에 반영해야 한다 */
139
+ onActiveKeysChange?: (keys: string[]) => void;
140
+ /** "검색 초기화" 클릭 — 모든 필드가 기본값(또는 null)으로 리셋된 뒤 호출된다 */
141
+ onReset?: () => void;
142
+ /** "필터 추가" 목록에서 항목을 골랐을 때 — activeKeys를 직접 제어 중이면 이 콜백에서
143
+ * (또는 onActiveKeysChange에서) key를 activeKeys에 추가해줘야 칩이 실제로 나타난다.
144
+ * activeKeys를 넘기지 않았다면(비제어) 별도 처리 없이도 컴포넌트가 알아서 칩을 노출한다 */
145
+ onAddFilter?: (key: string) => void;
146
+ /** 좌측 태그 텍스트 */
147
+ label?: string;
148
+ /** 좌측 태그(label)·구분선 표시 여부 */
149
+ showLabel?: boolean;
150
+ /** 필터 추가 버튼 표시 여부 */
151
+ showAddButton?: boolean;
152
+ /** 검색 초기화 링크 표시 여부 */
153
+ showReset?: boolean;
154
+ /** 바 비활성 상태 */
155
+ disabled?: boolean;
156
+ className?: string;
157
+ style?: CSSProperties;
158
+ }
159
+ /** SChipFilter — 검색 필터를 칩 바 형태로 구성하는 컴포넌트. */
160
+ export declare const SChipFilter: import("react").ForwardRefExoticComponent<SChipFilterProps & import("react").RefAttributes<SChipFilterHandle>>;
@@ -0,0 +1 @@
1
+ export { SChipFilter, type SChipFilterChangeDetail, type SChipFilterDatePreset, type SChipFilterField, type SChipFilterGroup, type SChipFilterGroupRule, type SChipFilterHandle, type SChipFilterKeywordValue, type SChipFilterMatchMode, type SChipFilterOption, type SChipFilterOptionValue, type SChipFilterProps, type SChipFilterType, type SChipFilterValue, type SChipFilterValueMap, } from './SChipFilter';
@@ -11,6 +11,7 @@
11
11
  | `value?` | `number` | `0` | 진행률 (0–100) |
12
12
  | `type?` | `SCircleProgressType` | `'primary'` | 색상 테마 |
13
13
  | `indeterminate?` | `boolean` | `false` | 불확정(스피너) 모드 — value 무시 |
14
+ | `size?` | `number \| string` | `DEFAULT_SIZE` | 링의 지름. 숫자는 px 로 해석한다. 부모 크기에 비례시키려면 컨테이너 쿼리 단위(`'45cqh'`)를 쓴다 — 퍼센트는 부모가 inline-flex 라 기준 폭이 정해지지 않아 해석되지 않는다. |
14
15
  | `label?` | `string` | — | 하단 레이블 |
15
16
  | `innerValue?` | `boolean` | `false` | true면 퍼센트를 원 아래가 아닌 원 가운데에 표시 |
16
17
  | `className?` | `string` | — | |
@@ -20,6 +21,7 @@
20
21
 
21
22
  ### Used by
22
23
 
24
+ - [SImage](../SImage)
23
25
  - [SLoadingContainer](../SLoadingContainer)
24
26
  - [SLoadingModal](../SLoadingModal)
25
27
  - [STable](../STable)
@@ -28,6 +30,7 @@
28
30
 
29
31
  ```mermaid
30
32
  graph TD;
33
+ SImage --> SCircleProgress
31
34
  SLoadingContainer --> SCircleProgress
32
35
  SLoadingModal --> SCircleProgress
33
36
  STable --> SCircleProgress
@@ -1,5 +1,5 @@
1
1
  import { type CSSProperties } from 'react';
2
- export type SCircleProgressType = 'primary' | 'inverse' | 'error' | 'complete';
2
+ export type SCircleProgressType = 'primary' | 'inverse' | 'error' | 'complete' | 'neutral';
3
3
  export interface SCircleProgressProps {
4
4
  /** 진행률 (0–100) */
5
5
  value?: number;
@@ -7,6 +7,12 @@ export interface SCircleProgressProps {
7
7
  type?: SCircleProgressType;
8
8
  /** 불확정(스피너) 모드 — value 무시 */
9
9
  indeterminate?: boolean;
10
+ /**
11
+ * 링의 지름. 숫자는 px 로 해석한다.
12
+ * 부모 크기에 비례시키려면 컨테이너 쿼리 단위(`'45cqh'`)를 쓴다 — 퍼센트는 부모가
13
+ * inline-flex 라 기준 폭이 정해지지 않아 해석되지 않는다.
14
+ */
15
+ size?: number | string;
10
16
  /** 하단 레이블 */
11
17
  label?: string;
12
18
  /** true면 퍼센트를 원 아래가 아닌 원 가운데에 표시 */
@@ -42,6 +42,7 @@
42
42
 
43
43
  ### Used by
44
44
 
45
+ - [SChipFilter](../SChipFilter)
45
46
  - [SKeyValueTable](../SKeyValueTable)
46
47
 
47
48
  ### Depends on
@@ -57,6 +58,7 @@ graph TD;
57
58
  SDatePicker --> SCalendar
58
59
  SDatePicker --> SField
59
60
  SDatePicker --> SIcon
61
+ SChipFilter --> SDatePicker
60
62
  SKeyValueTable --> SDatePicker
61
63
  style SDatePicker fill:#f9f,stroke:#333,stroke-width:4px
62
64
  ```
@@ -40,10 +40,30 @@
40
40
  | `onValueChange` | `(range: SDateRangeValue) => void` | 선택 변경 (sdUpdate) — [start, end] |
41
41
  | `onViewChange` | `(view: { year: number; month: number }) => void` | 보이는 연·월 변경 (sdViewChange) |
42
42
 
43
+ ### SRangeCalendar
44
+
45
+ #### Props
46
+
47
+ | Prop | Type | Default | Description |
48
+ |------|------|---------|-------------|
49
+ | `value` | `SDateRangeValue` | — | |
50
+ | `selectable?` | `[string, string]` | — | |
51
+ | `maxRange?` | `number` | — | |
52
+ | `useTimePicker?` | `boolean` | `false` | |
53
+
54
+ #### Events
55
+
56
+ | Event | Type | Description |
57
+ |-------|------|-------------|
58
+ | `onSelect` | `(r: [string, string]) => void` | |
59
+ | `onPendingStartChange` | `(start: string \| null) => void` | 시작일만 선택된(종료일 대기) 상태를 상위로 전달 — 트리거에 `start ~` 프리뷰 표시용 |
60
+ | `onViewChange` | `(view: { year: number; month: number }) => void` | |
61
+
43
62
  ## Dependencies
44
63
 
45
64
  ### Used by
46
65
 
66
+ - [SChipFilter](../SChipFilter)
47
67
  - [SKeyValueTable](../SKeyValueTable)
48
68
 
49
69
  ### Depends on
@@ -61,6 +81,7 @@ graph TD;
61
81
  SDateRangePicker --> SField
62
82
  SDateRangePicker --> SGhostButton
63
83
  SDateRangePicker --> SIcon
84
+ SChipFilter --> SDateRangePicker
64
85
  SKeyValueTable --> SDateRangePicker
65
86
  style SDateRangePicker fill:#f9f,stroke:#333,stroke-width:4px
66
87
  ```
@@ -44,5 +44,21 @@ export interface SDateRangePickerProps {
44
44
  className?: string;
45
45
  style?: CSSProperties;
46
46
  }
47
+ export interface SRangeCalendarProps {
48
+ value: SDateRangeValue;
49
+ selectable?: [string, string];
50
+ maxRange?: number;
51
+ useTimePicker?: boolean;
52
+ onSelect: (r: [string, string]) => void;
53
+ /** 시작일만 선택된(종료일 대기) 상태를 상위로 전달 — 트리거에 `start ~` 프리뷰 표시용 */
54
+ onPendingStartChange?: (start: string | null) => void;
55
+ onViewChange?: (view: {
56
+ year: number;
57
+ month: number;
58
+ }) => void;
59
+ }
60
+ /** RangeCalendar — SDateRangePicker의 캘린더 부분만 떼어낸 조각. 자체 트리거/팝오버 없이
61
+ * 캘린더 그리드만 필요한 커스텀 조합(예: 다른 트리거에 팝오버로 얹는 경우)에 재사용한다. */
62
+ export declare function RangeCalendar({ value, selectable, maxRange, useTimePicker, onSelect, onPendingStartChange, onViewChange, }: SRangeCalendarProps): import("react").JSX.Element;
47
63
  /** SDateRangePicker — sd-date-range-picker 포팅. SField + SPopover(범위 캘린더). */
48
64
  export declare function SDateRangePicker({ value, onValueChange, onViewChange, size, placeholder, selectable, maxRange, useTimePicker, disabled, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDateRangePickerProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- export { SDateRangePicker, type SDateRangePickerProps, type SDateRangePickerSize, type SDateRangeValue, } from './SDateRangePicker';
1
+ export { RangeCalendar, SDateRangePicker, type SDateRangePickerProps, type SDateRangePickerSize, type SDateRangeValue, type SRangeCalendarProps, } from './SDateRangePicker';
@@ -34,6 +34,7 @@
34
34
  - [SBarcodeInput](../SBarcodeInput)
35
35
  - [SCalendar](../SCalendar)
36
36
  - [SChip](../SChip)
37
+ - [SChipFilter](../SChipFilter)
37
38
  - [SDateRangePicker](../SDateRangePicker)
38
39
  - [SFilePicker](../SFilePicker)
39
40
  - [SGnb](../SGnb)
@@ -41,6 +42,7 @@
41
42
  - [SInput](../SInput)
42
43
  - [SModalContainer](../SModalContainer)
43
44
  - [SOverlayHeader](../SOverlayHeader)
45
+ - [SPage](../SPage)
44
46
  - [SPopover](../SPopover)
45
47
  - [SSelect](../SSelect)
46
48
  - [STimePicker](../STimePicker)
@@ -63,6 +65,7 @@ graph TD;
63
65
  SBarcodeInput --> SGhostButton
64
66
  SCalendar --> SGhostButton
65
67
  SChip --> SGhostButton
68
+ SChipFilter --> SGhostButton
66
69
  SDateRangePicker --> SGhostButton
67
70
  SFilePicker --> SGhostButton
68
71
  SGnb --> SGhostButton
@@ -70,6 +73,7 @@ graph TD;
70
73
  SInput --> SGhostButton
71
74
  SModalContainer --> SGhostButton
72
75
  SOverlayHeader --> SGhostButton
76
+ SPage --> SGhostButton
73
77
  SPopover --> SGhostButton
74
78
  SSelect --> SGhostButton
75
79
  STimePicker --> SGhostButton
@@ -13,7 +13,7 @@
13
13
  | `message?` | `SGuideMessage` | `''` | 팝업 메시지 (배열이면 리스트, 중첩 배열이면 depth 표현) |
14
14
  | `url?` | `string` | `''` | notion 타입 클릭 시 이동 URL |
15
15
  | `popupTitle?` | `string` | `''` | 팝업 제목 (없으면 type 기본값) |
16
- | `popupWidth?` | `number` | — | 팝업 너비 (px, 기본 426) |
16
+ | `popupWidth?` | `number` | — | 팝업 너비 (px, 기본 426). 창보다 넓게 줘도 창 폭까지만 벌어진다. |
17
17
  | `className?` | `string` | — | |
18
18
  | `style?` | `CSSProperties` | — | |
19
19
 
@@ -13,7 +13,7 @@ export interface SGuideProps {
13
13
  url?: string;
14
14
  /** 팝업 제목 (없으면 type 기본값) */
15
15
  popupTitle?: string;
16
- /** 팝업 너비 (px, 기본 426) */
16
+ /** 팝업 너비 (px, 기본 426). 창보다 넓게 줘도 창 폭까지만 벌어진다. */
17
17
  popupWidth?: number;
18
18
  className?: string;
19
19
  style?: CSSProperties;
@@ -23,6 +23,7 @@
23
23
  - [SButton](../SButton)
24
24
  - [SCallout](../SCallout)
25
25
  - [SCheckbox](../SCheckbox)
26
+ - [SChipFilter](../SChipFilter)
26
27
  - [SChipInput](../SChipInput)
27
28
  - [SConfirmModal](../SConfirmModal)
28
29
  - [SDatePicker](../SDatePicker)
@@ -35,6 +36,7 @@
35
36
  - [SGhostButton](../SGhostButton)
36
37
  - [SGnb](../SGnb)
37
38
  - [SGuide](../SGuide)
39
+ - [SImage](../SImage)
38
40
  - [SKeyValueTable](../SKeyValueTable)
39
41
  - [SListItem](../SListItem)
40
42
  - [SLoadingModal](../SLoadingModal)
@@ -59,6 +61,7 @@ graph TD;
59
61
  SButton --> SIcon
60
62
  SCallout --> SIcon
61
63
  SCheckbox --> SIcon
64
+ SChipFilter --> SIcon
62
65
  SChipInput --> SIcon
63
66
  SConfirmModal --> SIcon
64
67
  SDatePicker --> SIcon
@@ -71,6 +74,7 @@ graph TD;
71
74
  SGhostButton --> SIcon
72
75
  SGnb --> SIcon
73
76
  SGuide --> SIcon
77
+ SImage --> SIcon
74
78
  SKeyValueTable --> SIcon
75
79
  SListItem --> SIcon
76
80
  SLoadingModal --> SIcon
@@ -83,6 +83,7 @@ export declare const ICONS: {
83
83
  readonly settingOutline: (p: IconRenderProps) => import("react").JSX.Element;
84
84
  readonly shield: (p: IconRenderProps) => import("react").JSX.Element;
85
85
  readonly sidebar: (p: IconRenderProps) => import("react").JSX.Element;
86
+ readonly star: (p: IconRenderProps) => import("react").JSX.Element;
86
87
  readonly store: (p: IconRenderProps) => import("react").JSX.Element;
87
88
  readonly synchronize: (p: IconRenderProps) => import("react").JSX.Element;
88
89
  readonly thumbsDown: (p: IconRenderProps) => import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ # SImage
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SImage
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `src?` | `string` | — | 이미지 경로. 없으면 빈 이미지 아이콘이 나온다 |
12
+ | `alt?` | `string` | `''` | 대체 텍스트 |
13
+ | `ratio?` | `number` | `1` | 가로세로 비율. 소비자가 높이를 직접 주면 그쪽이 이긴다 |
14
+ | `fit?` | `SImageFit` | `'cover'` | 이미지가 상자를 채우는 방식 (object-fit) |
15
+ | `position?` | `string` | `'50% 50%'` | 이미지 정렬 위치 (object-position) |
16
+ | `loading?` | `'lazy' \| 'eager'` | `'lazy'` | 브라우저 로딩 방식 |
17
+ | `loadingShowDelay?` | `number` | `0` | 스피너를 띄우기까지 기다리는 시간(ms). 이 시간 안에 로드되면 스피너를 아예 그리지 않는다 — 캐시된 이미지에서 스피너가 한 프레임 번쩍이는 것을 막는다. |
18
+ | `noSpinner?` | `boolean` | `false` | 로딩 중에도 스피너를 그리지 않는다 |
19
+ | `loadingSlot?` | `ReactNode` | — | 로딩 중 표시를 직접 지정한다 (기본: 스피너) |
20
+ | `errorSlot?` | `ReactNode` | — | 실패·빈 상태 표시를 직접 지정한다 (기본: 빈 이미지 아이콘) |
21
+ | `imgProps?` | `ImgHTMLAttributes<HTMLImageElement>` | — | 안쪽 `<img>` 로 넘길 네이티브 속성 (srcSet · sizes · crossOrigin · draggable 등) |
22
+
23
+ #### Events
24
+
25
+ | Event | Type | Description |
26
+ |-------|------|-------------|
27
+ | `onLoad` | `(src: string) => void` | 로드 완료. 로드된 src 를 넘긴다 |
28
+ | `onError` | `(event: SyntheticEvent<HTMLImageElement>) => void` | 로드 실패 |
29
+
30
+ ## Dependencies
31
+
32
+ ### Depends on
33
+
34
+ - [SCircleProgress](../SCircleProgress)
35
+ - [SIcon](../SIcon)
36
+
37
+ ### Graph
38
+
39
+ ```mermaid
40
+ graph TD;
41
+ SImage --> SCircleProgress
42
+ SImage --> SIcon
43
+ style SImage fill:#f9f,stroke:#333,stroke-width:4px
44
+ ```
@@ -0,0 +1,41 @@
1
+ import { type HTMLAttributes, type ImgHTMLAttributes, type ReactNode, type SyntheticEvent } from 'react';
2
+ export declare const IMAGE_FITS: readonly ["cover", "contain", "fill", "none", "scale-down"];
3
+ export type SImageFit = (typeof IMAGE_FITS)[number];
4
+ export interface SImageProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad' | 'onError'> {
5
+ /** 이미지 경로. 없으면 빈 이미지 아이콘이 나온다 */
6
+ src?: string;
7
+ /** 대체 텍스트 */
8
+ alt?: string;
9
+ /** 가로세로 비율. 소비자가 높이를 직접 주면 그쪽이 이긴다 */
10
+ ratio?: number;
11
+ /** 이미지가 상자를 채우는 방식 (object-fit) */
12
+ fit?: SImageFit;
13
+ /** 이미지 정렬 위치 (object-position) */
14
+ position?: string;
15
+ /** 브라우저 로딩 방식 */
16
+ loading?: 'lazy' | 'eager';
17
+ /**
18
+ * 스피너를 띄우기까지 기다리는 시간(ms). 이 시간 안에 로드되면 스피너를 아예 그리지 않는다 —
19
+ * 캐시된 이미지에서 스피너가 한 프레임 번쩍이는 것을 막는다.
20
+ */
21
+ loadingShowDelay?: number;
22
+ /** 로딩 중에도 스피너를 그리지 않는다 */
23
+ noSpinner?: boolean;
24
+ /** 로딩 중 표시를 직접 지정한다 (기본: 스피너) */
25
+ loadingSlot?: ReactNode;
26
+ /** 실패·빈 상태 표시를 직접 지정한다 (기본: 빈 이미지 아이콘) */
27
+ errorSlot?: ReactNode;
28
+ /** 안쪽 `<img>` 로 넘길 네이티브 속성 (srcSet · sizes · crossOrigin · draggable 등) */
29
+ imgProps?: ImgHTMLAttributes<HTMLImageElement>;
30
+ /** 로드 완료. 로드된 src 를 넘긴다 */
31
+ onLoad?: (src: string) => void;
32
+ /** 로드 실패 */
33
+ onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
34
+ }
35
+ /**
36
+ * SImage — 이미지 로딩·실패 상태를 스스로 표현하는 이미지 상자.
37
+ *
38
+ * 로딩 중에는 스피너, `src` 가 없거나 로드에 실패하면 빈 이미지 아이콘을 컨테이너 높이에
39
+ * 비례한 크기로 가운데에 그린다.
40
+ */
41
+ export declare const SImage: import("react").ForwardRefExoticComponent<SImageProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1 @@
1
+ export { SImage, IMAGE_FITS, type SImageProps, type SImageFit } from './SImage';
@@ -11,6 +11,7 @@
11
11
  | `children?` | `ReactNode` | — | 리스트 컨테이너 내부에 렌더링할 내용 |
12
12
  | `useGap?` | `boolean` | `false` | 리스트 아이템 사이 gap 토큰 적용 여부 |
13
13
  | `usePadding?` | `boolean` | `false` | 리스트 컨테이너 padding 토큰 적용 여부 |
14
+ | `separator?` | `boolean` | `false` | 아이템 하단 구분선 표시 여부. 테두리를 가진 아이템(`bordered`)에는 쓰지 않는다 |
14
15
 
15
16
  ## Dependencies
16
17
 
@@ -6,5 +6,7 @@ export interface SListProps extends HTMLAttributes<HTMLDivElement> {
6
6
  useGap?: boolean;
7
7
  /** 리스트 컨테이너 padding 토큰 적용 여부 */
8
8
  usePadding?: boolean;
9
+ /** 아이템 하단 구분선 표시 여부. 테두리를 가진 아이템(`bordered`)에는 쓰지 않는다 */
10
+ separator?: boolean;
9
11
  }
10
12
  export declare const SList: import("react").ForwardRefExoticComponent<SListProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -15,8 +15,8 @@
15
15
  | `leading?` | `SListItemSlot` | — | 타이틀 앞에 표시할 아이콘/콘텐츠 |
16
16
  | `trailing?` | `SListItemSlot` | — | 타이틀 뒤에 표시할 태그/콘텐츠 |
17
17
  | `bordered?` | `boolean` | `false` | 외곽 테두리 사용 여부 |
18
- | `selected?` | `boolean` | `false` | 선택 상태 여부 |
19
- | `clickable?` | `boolean` | `true` | 클릭 가능 상태 여부 |
18
+ | `selected?` | `boolean` | `false` | 선택 상태 여부. `clickable` 이 false 면 무시된다 |
19
+ | `clickable?` | `boolean` | `false` | 클릭 가능 상태 여부. hover·selected·chevron 등 인터랙션 표현이 여기에 딸린다 |
20
20
  | `interaction?` | `SListItemInteraction` | — | hover/selected 상태에서 적용할 인터랙션 preset |
21
21
  | `accentStripe?` | `boolean` | `false` | 아이템 왼쪽 accent stripe 표시 여부 |
22
22
  | `dense?` | `boolean` | `false` | 조밀한 높이 사용 여부 |
@@ -23,9 +23,9 @@ export interface SListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'ch
23
23
  trailing?: SListItemSlot;
24
24
  /** 외곽 테두리 사용 여부 */
25
25
  bordered?: boolean;
26
- /** 선택 상태 여부 */
26
+ /** 선택 상태 여부. `clickable` 이 false 면 무시된다 */
27
27
  selected?: boolean;
28
- /** 클릭 가능 상태 여부 */
28
+ /** 클릭 가능 상태 여부. hover·selected·chevron 등 인터랙션 표현이 여기에 딸린다 */
29
29
  clickable?: boolean;
30
30
  /** hover/selected 상태에서 적용할 인터랙션 preset */
31
31
  interaction?: SListItemInteraction;
@@ -2,6 +2,24 @@
2
2
 
3
3
  > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
4
 
5
+ ### SPageHeader
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `title` | `string` | — | 타이틀 (필수) |
12
+ | `subText?` | `string` | — | 서브 텍스트. fix=true 면 타이틀 오른쪽에, fix=false(기본) 면 타이틀 아래에 놓인다. |
13
+ | `fix?` | `boolean` | `false` | true: 높이 56 고정 + 흰 배경 + 하단 구분선, 서브 텍스트가 타이틀과 같은 줄. false(기본): 최소 높이 66(상하 패딩 20/16 안에서 타이틀 한 줄 높이로 결정) + 투명 배경, 서브 텍스트가 타이틀 아래로 내려간다. |
14
+ | `slot?` | `ReactNode` | — | 오른쪽 끝 커스텀 슬롯. 가이드 링크·액션 버튼 등 페이지마다 다른 것이 온다. |
15
+ | `scrolled?` | `boolean` | `false` | `<SPage>` 가 스크롤 여부를 보고 자동으로 넣어준다 — 직접 주지 않는다. fix=false 일 때만 의미가 있다: 스크롤이 시작되면 그림자(floating)가 들어가 스크롤되는 콘텐츠 위에 뜬 것처럼 보인다. 배경은 그대로 페이지 배경을 따라간다 — 배경을 바꾸는 건 scrolled 가 아니라 fix 다. |
16
+
17
+ #### Events
18
+
19
+ | Event | Type | Description |
20
+ |-------|------|-------------|
21
+ | `onBack` | `() => void` | 뒤로가기 클릭 핸들러. 지정하면 왼쪽에 뒤로가기 버튼이 나타난다. |
22
+
5
23
  ### SPage
6
24
 
7
25
  #### Props
@@ -10,17 +28,20 @@
10
28
  |------|------|---------|-------------|
11
29
  | `background?` | `SPageBackground` | `'frame'` | 페이지 배경 표면. frame=흰 콘텐츠 면, neutral=옅은 회색 면, screen=앱 바탕. 스크롤바 처리도 여기 묶여 있다 — 셋 다 구분선+트랙 배경이고, 트랙 색만 neutral 에서 흰색이 된다. |
12
30
  | `scrollEndSpacing?` | `boolean` | `true` | 스크롤 끝 여백. 마지막 항목이 창 하단에 붙어 "여기서 끝"이 안 읽히는 것을 막는다. 기본으로 켜져 있고, 페이지네이션처럼 끝을 알려주는 것이 이미 있으면 끈다(목록 페이지). |
31
+ | `header?` | `SPageHeaderProps` | — | 페이지 타이틀 영역(pageHeader 포팅). 주면 스크롤·페이지 패딩 밖으로 빼내 상단에 고정 배치한다. `children` 은 항상 본문이다 — header 유무와 무관하게 같은 자리에 같은 뜻으로 들어간다. |
13
32
 
14
33
  ## Dependencies
15
34
 
16
35
  ### Depends on
17
36
 
37
+ - [SGhostButton](../SGhostButton)
18
38
  - [SLayout](../SLayout)
19
39
 
20
40
  ### Graph
21
41
 
22
42
  ```mermaid
23
43
  graph TD;
44
+ SPage --> SGhostButton
24
45
  SPage --> SLayout
25
46
  style SPage fill:#f9f,stroke:#333,stroke-width:4px
26
47
  ```
@@ -1,5 +1,6 @@
1
1
  import { type HTMLAttributes } from 'react';
2
2
  import type { SPageBackground } from './page.config';
3
+ import { type SPageHeaderProps } from './SPageHeader';
3
4
  export interface SPageProps extends HTMLAttributes<HTMLElement> {
4
5
  /**
5
6
  * 페이지 배경 표면. frame=흰 콘텐츠 면, neutral=옅은 회색 면, screen=앱 바탕.
@@ -11,6 +12,11 @@ export interface SPageProps extends HTMLAttributes<HTMLElement> {
11
12
  * 기본으로 켜져 있고, 페이지네이션처럼 끝을 알려주는 것이 이미 있으면 끈다(목록 페이지).
12
13
  */
13
14
  scrollEndSpacing?: boolean;
15
+ /**
16
+ * 페이지 타이틀 영역(pageHeader 포팅). 주면 스크롤·페이지 패딩 밖으로 빼내 상단에 고정 배치한다.
17
+ * `children` 은 항상 본문이다 — header 유무와 무관하게 같은 자리에 같은 뜻으로 들어간다.
18
+ */
19
+ header?: SPageHeaderProps;
14
20
  }
15
21
  /**
16
22
  * SPage — SLayout 안에서 남은 공간을 채우는 스크롤 콘텐츠 영역.
@@ -21,5 +27,9 @@ export interface SPageProps extends HTMLAttributes<HTMLElement> {
21
27
  * - header="fix": flex 자식으로 남은 가로 공간을 채운다.
22
28
  * - header="full": SLayout 이 깐 grid 의 "page" 영역 = 상단바 아래·메뉴 오른쪽 자리를 차지한다.
23
29
  * 좌표가 아니라 이름으로 잡으므로 프레임의 열·행 구성이 바뀌어도 여기는 그대로다.
30
+ *
31
+ * **`header` prop 을 주면** 스크롤·페이지 패딩 밖으로 빼내 상단에 고정 배치한다(fix 베리에이션이
32
+ * 늘 보이는 높이 56 바가 되도록). 주지 않으면 이전과 동일하게 동작한다 — ref·className·style·
33
+ * 나머지 속성이 전부 스크롤 영역 자체(`<main>`)에 그대로 붙는다.
24
34
  */
25
35
  export declare const SPage: import("react").ForwardRefExoticComponent<SPageProps & import("react").RefAttributes<HTMLElement>>;
@@ -0,0 +1,32 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export interface SPageHeaderProps extends Omit<HTMLAttributes<HTMLElement>, 'slot' | 'title'> {
3
+ /** 타이틀 (필수) */
4
+ title: string;
5
+ /** 서브 텍스트. fix=true 면 타이틀 오른쪽에, fix=false(기본) 면 타이틀 아래에 놓인다. */
6
+ subText?: string;
7
+ /**
8
+ * true: 높이 56 고정 + 흰 배경 + 하단 구분선, 서브 텍스트가 타이틀과 같은 줄.
9
+ * false(기본): 최소 높이 66(상하 패딩 20/16 안에서 타이틀 한 줄 높이로 결정) + 투명 배경,
10
+ * 서브 텍스트가 타이틀 아래로 내려간다.
11
+ */
12
+ fix?: boolean;
13
+ /** 뒤로가기 클릭 핸들러. 지정하면 왼쪽에 뒤로가기 버튼이 나타난다. */
14
+ onBack?: () => void;
15
+ /** 오른쪽 끝 커스텀 슬롯. 가이드 링크·액션 버튼 등 페이지마다 다른 것이 온다. */
16
+ slot?: ReactNode;
17
+ /**
18
+ * `<SPage>` 가 스크롤 여부를 보고 자동으로 넣어준다 — 직접 주지 않는다.
19
+ * fix=false 일 때만 의미가 있다: 스크롤이 시작되면 그림자(floating)가 들어가 스크롤되는
20
+ * 콘텐츠 위에 뜬 것처럼 보인다. 배경은 그대로 페이지 배경을 따라간다 — 배경을 바꾸는 건
21
+ * scrolled 가 아니라 fix 다.
22
+ * @nodemo SPage 가 스크롤 이벤트로 넣어준다 — 사용자가 직접 시연할 값이 아니다.
23
+ */
24
+ scrolled?: boolean;
25
+ }
26
+ /**
27
+ * SPage 위에 얹는 페이지 타이틀 영역(pageHeader 포팅). `SPage` 가 `header` prop 값으로
28
+ * 내부적으로만 렌더한다 — 직접 임포트해 쓰지 않는다 (SPage.tsx 참고).
29
+ * fix 로 bar(높이 고정·흰 배경) / plain(투명·최소 높이) 두 베리에이션을 고른다.
30
+ * 뒤로가기 버튼은 onBack 유무로 나타나며, 없을 때는 타이틀 쪽에 4px 여백이 대신 들어간다.
31
+ */
32
+ export declare const SPageHeader: import("react").ForwardRefExoticComponent<SPageHeaderProps & import("react").RefAttributes<HTMLElement>>;
@@ -1,2 +1,3 @@
1
1
  export { SPage, type SPageProps } from './SPage';
2
2
  export { PAGE_BACKGROUNDS, type SPageBackground } from './page.config';
3
+ export { type SPageHeaderProps } from './SPageHeader';
@@ -18,7 +18,7 @@
18
18
  | `maxHeight?` | `number` | — | 콘텐츠 최대 높이 px. 넘치면 세로 스크롤. |
19
19
  | `autoClose?` | `boolean` | `false` | 콘텐츠 내부를 클릭하면 자동으로 닫는다. |
20
20
  | `persistent?` | `boolean` | `false` | 외부 클릭 · ESC 로 닫히지 않게 한다. |
21
- | `viewportPadding?` | `number` | `0` | 뷰포트 경계 여백 px (충돌 회피 패딩). |
21
+ | `viewportPadding?` | `number` | `0` | 뷰포트 경계 여백 px (충돌 회피 패딩). 콘텐츠 폭은 항상 `창 폭 - viewportPadding*2` 를 넘지 않게 잘린다. |
22
22
  | `children?` | `ReactNode` | — | 본문 내용 (콘텐츠 자유). |
23
23
  | `className?` | `string` | — | |
24
24
  | `style?` | `CSSProperties` | — | |