sellmate-design-system-react 9.0.0-beta.56 → 9.0.0-beta.59

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
@@ -45,7 +45,7 @@
45
45
  | **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
46
46
  | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SLauncherListBox`(런처 버튼을 눌러 뜨는 서비스 목록) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
47
47
  | **내비게이션** | `STabs` `SPagination` `SStepper` |
48
- | **차트** | `SBarChart`(막대 그래프 — 항목끼리 크기를 견준다. `stacked` 로 항목 안의 구성까지) |
48
+ | **차트** | `SBarChart`(막대 그래프 — 항목끼리 크기를 견준다. `stacked` 로 항목 안의 구성까지) `SLineChart`(꺾은선 그래프 — 순서가 있는 항목의 추이를 본다. `area` 로 크기까지, `stacked` 로 구성까지) |
49
49
  | **표시·상태** | `STag` `SBadge` `SIcon` `SLogo`(브랜드 로고를 아이콘처럼 — size 는 높이다) `SImage` `SCallout` `SGuide` |
50
50
  | **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
51
51
  | **오버레이** | `STooltip` `SPopover` `SPopup` `SDrawer` `SPortal` |
@@ -167,7 +167,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
167
167
  | 층 | 무엇인가 | 컴포넌트 |
168
168
  | --- | --- | --- |
169
169
  | **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
170
- | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `SBarChart` `STabs` `SStepper` `SPagination` `SDivider` |
170
+ | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `SBarChart` `SLineChart` `STabs` `SStepper` `SPagination` `SDivider` |
171
171
  | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SChatAttachedFile` `SChatFile` `SImage` `SLinearProgress` `SCircleProgress` |
172
172
  | **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `SLogo` `STextLink` `SChip` |
173
173
  | **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SLauncherListBox`(런처 버튼에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
@@ -566,6 +566,8 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
566
566
  | 사용자가 순서를 드래그로 바꾸게 한다 | `SDraggableList` + `SDraggableItem` | §3-7-6 |
567
567
  | 날짜별 일정을 한 달치 격자로 펼쳐 보여준다 | `SCalendarBoard` | §3-7-4 |
568
568
  | 항목끼리 크기를 눈으로 견주게 한다 (기간별 추이·채널별 비중) | `SBarChart` | 정확한 값을 읽어야 하면 `STable` — 그래프는 "어느 쪽이 큰가" 를 답하지 "얼마인가" 를 답하지 않는다 |
569
+ | 순서가 있는 항목의 **추이**를 보여준다 (일·월별 매출 흐름, 기간별 방문 수) | `SLineChart` | 항목 순서에 뜻이 없으면(채널별·상품별) `SBarChart` — 선은 "이어진다" 는 뜻을 덤으로 얹어, 순서 없는 항목을 이으면 없는 흐름을 만든다. 항목이 대여섯 개 안쪽이고 크기를 견주는 것이 목적이면 막대가 낫다 |
570
+ | 여러 항목의 추이를 **합계와 함께** 보여준다 (채널별 매출이 쌓여 전체가 되는 흐름) | `SLineChart stacked area` | 합계가 뜻을 갖는 값일 때만 쌓는다. 음수가 섞이거나 더해서 뜻이 없는 값(비율·평균)은 쌓지 말고 겹쳐 그린다 |
569
571
  | 항목의 크기와 **그 안의 구성**을 함께 보여준다 (주문 상태별 내역, 유입 경로별 몫) | `SBarChart stacked` | 계열끼리 견주는 것이 목적이면 `stacked` 없이 나란히 세운다. 더해서 뜻이 없는 값(비율·평균)은 쌓지 않는다 — 합계 라벨이 거짓말이 된다 |
570
572
  | 표 위에 건수 요약과 액션을 얹는다 | `STableBar` | 표의 이름도 여기 `title` 슬롯에 넣는다 — 제목만으로 블록을 따로 세우지 않는다. §4-2 |
571
573
  | 상태·분류를 라벨로 찍는다 | `STag` | §3-1 |
@@ -1092,11 +1094,57 @@ const columns: STableColumn[] = [
1092
1094
  <STable selectable isRowSelectable={row => row.failedCount > 0} … />
1093
1095
  ```
1094
1096
 
1097
+ - **잠긴 행은 회색으로 가라앉는다.** 배경과 글자색이 `--cmp-table-body-disabled-*` 로 바뀌고 hover 도 꺼진다. `selectable` · `dragSelectable` 어느 쪽이든 같다. **그 회색을 직접 그리지 않는다** — `tdClass` 로 따로 칠하면 토큰이 바뀔 때 그 열만 어긋난 채 남는다.
1098
+ - **셀이 자기 색을 정한 내용까지는 닿지 않는다.** `column.render` 안의 `STag` · `SIcon` 처럼 색을 직접 받는 것은 그대로 선명하게 남는다. 잠긴 행에서 그것도 가라앉혀야 하면 `render` 에서 행 상태를 보고 정한다.
1095
1099
  - **체크박스를 직접 잠그려 하지 않는다.** 색·커서·hover 가 한 벌로 움직이므로 밖에서 속성만 바꾸면 "잠기지 않았는데 멀쩡해 보이는" 상태가 된다. 이 prop 하나로 셋이 함께 잡힌다.
1096
1100
  - **전체 선택과 Shift 구간 선택의 셈에서도 빠진다.** 잠긴 행이 셈에 남으면 "전부 선택됨"에 닿지 못해 헤더 체크박스가 해제 방향으로 못 가고 한 방향이 된다. DS 가 이걸 처리하므로 직접 보정하지 않는다.
1097
1101
  - **아예 대상이 아닌 행이라면 `rows` 에서 거르는 편이 낫다.** 잠긴 행이 잔뜩 섞이면 무엇을 고를 수 있는지가 오히려 안 읽힌다. 고를 수 있는 행이 한 줄도 없으면 헤더까지 잠기는데, 그 상태라면 `selectable` 을 켤 자리가 아니다.
1098
1102
  - 잠금은 그리는 시점의 판정이라, **이미 `selected` 에 든 행이 나중에 잠겨도 DS 가 빼지 않는다** — 제어 상태를 말없이 바꾸지 않기 때문이다. 헤더의 전체 해제로는 걷힌다.
1099
1103
 
1104
+ #### 체크박스 대신 드래그로 고르게 하려면 `dragSelectable`
1105
+
1106
+ 고르는 **수단만** 다른 같은 선택이다. `selected` · `onSelectedChange` · `isRowSelectable` 을 그대로 쓰고, 체크박스 열 대신 고른 구간이 배경색과 바깥 테두리로 표시된다.
1107
+
1108
+ ```tsx
1109
+ <STable dragSelectable selected={selected} onSelectedChange={setSelected} … />
1110
+ ```
1111
+
1112
+ | | `selectable` | `dragSelectable` |
1113
+ | --- | --- | --- |
1114
+ | 고르는 법 | 체크박스 클릭 · Shift 구간 | 행을 눌러 끌기 |
1115
+ | 흩어진 행 모으기 | 하나씩 체크 | `Ctrl`(macOS `Cmd`)을 짚고 끌기 |
1116
+ | 선택 열 | 생긴다 (48px, 왼쪽 고정) | 없다 |
1117
+ | 헤더 전체 선택 | 있다 | 없다 |
1118
+ | 셀 글자 복사 | 된다 | **안 된다** |
1119
+
1120
+ - **둘 다 켜면 `selectable` 이 이긴다.** 체크박스가 있는 표에서 드래그까지 걸리면 글자를 긁으려던 손이 선택을 통째로 갈아치운다. 어느 쪽이 맞는지 정해서 하나만 켠다.
1121
+ - **값을 복사해 가는 표에는 켜지 않는다.** 끌기가 곧 선택이라 글자 선택과 같은 손짓을 두고 다투고, 그래서 셀 안의 텍스트를 긁을 수 없다. 주문번호·송장번호처럼 복사해 쓰는 열이 있으면 `selectable` 이다.
1122
+ - **새로 끌면 이전 선택은 풀린다.** 여러 구간을 모으려면 `Ctrl`/`Cmd` 를 짚고 끈다. 이 규칙을 화면에 안내할 자리가 없으면, 흩어진 행을 자주 고르는 표에는 맞지 않는다.
1123
+ - **한 번에 이어진 구간을 고르는 표에 쓴다.** 목록에서 연속한 기간·회차를 통째로 집어 처리하는 화면이 제자리다.
1124
+ - 셀 안의 버튼·입력·링크는 그대로 눌린다 — 거기서 시작한 손짓은 드래그로 세지 않는다. 그래도 컨트롤이 빽빽한 표라면 끌 여백이 없어 잘 맞지 않는다.
1125
+ - 잠긴 행(`isRowSelectable`)은 구간 안에 있어도 그냥 지나간다 — 체크박스 쪽과 같은 규칙이다.
1126
+
1127
+ ##### 고른 행에 바로 할 일을 붙이려면 `contextMenuItems`
1128
+
1129
+ 행을 **오른쪽 클릭**하면 커서 자리에 메뉴가 뜬다. 고른 항목의 `value` 와 **그 메뉴가 다루는 행들**이 함께 온다.
1130
+
1131
+ ```tsx
1132
+ <STable
1133
+ dragSelectable
1134
+ contextMenuItems={[
1135
+ { value: 'export', label: '내보내기', icon: 'download' },
1136
+ { value: 'delete', label: '삭제', icon: 'remove' },
1137
+ ]}
1138
+ onContextMenuItemClick={(value, rows) => run(value, rows)}
1139
+ … />
1140
+ ```
1141
+
1142
+ - **`dragSelectable` 전용이다.** 체크박스 모드에 주면 무시된다. 체크박스 표에서 일괄 작업을 붙이는 자리는 표 위의 `STableBar` 다 (§4 페이지 레시피).
1143
+ - **행 목록을 인자로 받는다. `selected` 를 따로 읽지 않는다.** 오른쪽 클릭이 선택을 바꾸는 경우가 있어서, 그때 앱이 든 `selected` 는 아직 이전 값일 수 있다.
1144
+ - **고르지 않은 행에서 누르면 그 행만 골라진 뒤 열린다.** 메뉴가 다룰 대상과 화면에 칠해진 것이 어긋나지 않게 하기 위함이다. 잠긴 행 위에서는 열리지 않고 브라우저 기본 메뉴가 그대로 나온다.
1145
+ - **오른쪽 클릭에만 있는 기능을 두지 않는다.** 뜨는 것을 모르면 닿을 수 없고, 키보드로도 열 수 없다. 여기 넣는 것은 표 위 버튼이나 행 안 메뉴에도 있는 **지름길**이어야 한다.
1146
+ - 항목을 주지 않으면 오른쪽 클릭을 가로채지 않는다 — 브라우저 기본 메뉴가 그대로 뜬다.
1147
+
1100
1148
  #### 헤더 전체 선택이 집는 범위
1101
1149
 
1102
1150
  **모드가 정한다. 앱이 보정하지 않는다.**
@@ -1635,7 +1683,9 @@ useEffect(() => {
1635
1683
 
1636
1684
  `SDateRangePicker` 가 한 등급씩 위인 것은 값이 `YYYY-MM-DD ~ YYYY-MM-DD` 로 두 배가 넘기 때문이다. 같은 이유로 `STimeRangePicker` 의 오전/오후 모드도 sm 에서 한 등급 위를 쓴다 — 그 모드의 최소 폭이 md 등급을 이미 넘어, 그대로 두면 하한이 상한을 넘어 상한이 무력해진다.
1637
1685
 
1638
- `SDatePicker` 이 상한을 `maxWidth` 로 덮을 수 있다 — 등급을 주면 그 등급이 상한이 되고, `width="100%" maxWidth="100%"` 면 부모 폭을 그대로 채운다. **폭이 이미 좁게 정해진 자리(팝오버·좁은 카드)에서만 쓴다.** 폼·표 행에서는 쓰지 않는다 — 거기서 상한을 풀면 4~10글자짜리 값이 행 전체를 차지한다.
1686
+ 컴포넌트 모두 이 상한을 `maxWidth` 로 덮을 수 있다 — 등급을 주면 그 등급이 상한이 되고, `width="100%" maxWidth="100%"` 면 부모 폭을 그대로 채운다. **폭이 이미 좁게 정해진 자리(팝오버·좁은 카드)에서 쓴다.**
1687
+
1688
+ **`SKeyValueTable` 안에서는 필드마다 `maxWidth` 를 주지 않는다.** 표 안에서 이 상한이 거슬리는 이유는 대개 "같은 행의 다른 필드와 오른쪽 끝이 어긋난다" 이고, 그건 표 전체의 문제다. 표의 `fieldWidth="fill"` 로 한 번에 정한다 (§4-3). 그 표에서 한 필드만 예외로 둘 때만 `options.maxWidth` 를 쓴다.
1639
1689
 
1640
1690
  ##### 값을 지울 수 있게 하려면 `clearable`
1641
1691
 
@@ -2169,10 +2219,12 @@ export default function ProductListPage() {
2169
2219
  >
2170
2220
  {/* h-full min-h-0 → STable 의 min-h-0 flex-1 로 세로 축이 이어진다 */}
2171
2221
  <div className="flex h-full min-h-0 flex-col gap-sd-12">
2172
- {/* 필터 — search 를 켜면 우측에 검색 패널이 붙는다 */}
2222
+ {/* 필터 — search 를 켜면 우측에 검색 패널이 붙는다.
2223
+ 조건 칸이 줄지어 서는 자리라 fieldWidth="fill" 로 오른쪽 끝을 맞춘다 (§4-3) */}
2173
2224
  <SKeyValueTable
2174
2225
  fields={filterFields}
2175
2226
  values={filters}
2227
+ fieldWidth="fill"
2176
2228
  search
2177
2229
  onChange={({ values }) => setFilters(values)}
2178
2230
  onSearch={fetchList}
@@ -2253,6 +2305,22 @@ const [initialDense] = useState(() => loadPref('list.dense', true));
2253
2305
  - **폼 페이지도 `contentHeight="fill"` 이 기본이다**(§2-2). 폼이 길어 페이지가 실제로 스크롤되는 화면에서만 `auto` + `scrollEndSpacing` 을 켠다.
2254
2306
  - **필드 폭은 등급으로 준다** — `width="md"` 처럼 `'xs' | 'sm' | 'md' | 'lg' | 'xl'` 중 하나다. px 를 직접 적지 않는다. 등급은 `maxLength`(= 스키마 상한)로 고르고, 상한이 `xl` 을 넘거나 상한이 없으면 `width="100%"` 로 행 전체를 쓴다 (§6 `field-width-grade`).
2255
2307
 
2308
+ **날짜·시간 피커는 값 길이에 맞춘 폭 상한을 스스로 갖는다.** 표가 모든 컨트롤에 `width="100%"` 를 넘기지만 이 넷만 자기 상한에서 멈춘다 — `SDatePicker` · `SDateRangePicker` · `STimePicker` · `STimeRangePicker`. 그래서 한 행에 이런 필드가 섞이면 **왼쪽 끝만 맞고 오른쪽 끝이 어긋나** 보인다. 이것이 폼이 들쭉날쭉해 보이는 가장 흔한 원인이다.
2309
+
2310
+ **어느 쪽으로 갈지는 표 하나가 한 번에 정한다 — `SKeyValueTable` 의 `fieldWidth` 다.** 필드마다 폭을 손보지 않는다.
2311
+
2312
+ | 값 | 무엇이 달라지나 | 쓰는 자리 |
2313
+ | --- | --- | --- |
2314
+ | `"auto"`(기본) | 컨트롤이 자기 폭을 정한다. 폭이 값 길이를 알려준다 | 등록·수정 폼 — 입력 길이를 짐작하게 하는 편이 낫다 |
2315
+ | `"fill"` | 모든 컨트롤이 셀 끝까지 찬다. 오른쪽 끝이 맞는다 | 조회 필터 — 가지런함이 우선이다 (§4-2) |
2316
+
2317
+ ```tsx
2318
+ {/* 필터는 조건 칸이 줄지어 서는 자리다 — 오른쪽 끝을 맞춘다 */}
2319
+ <SKeyValueTable fields={filterFields} fieldWidth="fill" onSearch={search} search />
2320
+ ```
2321
+
2322
+ 한 필드만 예외로 두려면 그 필드의 `options.maxWidth` 를 준다 — 표의 정책보다 우선한다. 표 밖에 홀로 선 피커도 같은 prop 으로 상한을 푼다. 상한 값 자체는 컴포넌트가 폭 등급으로 갖고 있으므로 px 를 직접 적지 않는다.
2323
+
2256
2324
  **`SKeyValueTable` 의 전체 열 수는 가장 긴 행이 정한다.** 어떤 행이 그보다 짧으면 남는 자리에 셀이 없어 그 구간의 행 구분선이 끊긴다. 마지막 필드에 `tdColSpan` 을 주어 채운다.
2257
2325
 
2258
2326
  ```tsx
@@ -2264,6 +2332,25 @@ const [initialDense] = useState(() => loadPref('list.dense', true));
2264
2332
 
2265
2333
  **한 행에 필드를 추가하면 다른 행들의 `tdColSpan` 도 함께 봐야 한다.** 전체 열 수가 늘면 나머지 행들이 조용히 짧아진다 — 화면에서만 드러나는 컴포넌트 고유 동작이라 자동으로 채워 주지 않는다.
2266
2334
 
2335
+ **세로 병합(`thRowSpan` · `tdRowSpan`)은 아래 행에 쓰는 법이 둘로 갈린다.** 무엇을 병합했는지에 따라 아래 행에 적는 내용이 달라진다 — 섞으면 셀이 겹치거나 값 칸이 빈다.
2336
+
2337
+ ```tsx
2338
+ // 레이블만 병합 — 아래 행에는 label 없는 "값만 있는 필드" 를 둔다
2339
+ [
2340
+ [{ name: 'roadAddress', label: '주소', thRowSpan: 3, … }],
2341
+ [{ name: 'detailAddress', … }], // 레이블 칸은 위 행 th 가 덮는다
2342
+ [{ name: 'zipcode', … }],
2343
+ ]
2344
+
2345
+ // 레이블 + 값을 한 덩어리로 병합 — 아래 행에서는 그 필드를 아예 뺀다
2346
+ [
2347
+ [{ name: 'name', label: '이름', … }, { name: 'role', label: '직급', thRowSpan: 2, tdRowSpan: 2, … }],
2348
+ [{ name: 'email', label: '이메일', … }], // 오른쪽 [th|td] 는 위 행 직급이 채운다
2349
+ ]
2350
+ ```
2351
+
2352
+ **병합으로 찬 자리는 배열 순서가 아니라 실제 열 위치로 판정한다.** 그러니 아래 행의 필드를 병합된 열에 맞추려고 자리 채우기용 빈 필드를 끼우지 않는다 — 앞 필드에 `tdColSpan` 이나 `hideTh` 가 있어도 남은 필드가 알아서 빈 열부터 놓인다.
2353
+
2267
2354
  ```tsx
2268
2355
  import {
2269
2356
  SForm, SKeyValueTable, SButton, SCheckbox,
@@ -2588,6 +2675,7 @@ export default function TransferOrderPopupPage() {
2588
2675
  - [ ] 조회 조건으로 쓰는 셀렉트·날짜·시간 피커에 `clearable` 을 줬는가, 그 상태가 `null` 을 담을 수 있는가 (§3-7-4 — 필수 입력 필드에는 켜지 않는다)
2589
2676
  - [ ] 서버에서 페이지 단위로 받는 `SSelect` 에 `onReachEnd` 와 `hasMore`·`loading`·`serverSearch` 를 함께 줬는가, 늦게 온 응답을 버리는 cleanup 이 있는가 (§3-7-2 — 렌더 최적화는 DS 가 알아서 한다)
2590
2677
  - [ ] `SKeyValueTable` 의 짧은 행에 `tdColSpan` 을 주어 전체 열 수를 채웠는가 (§4-3 — 안 채우면 그 구간의 행 구분선이 끊긴다)
2678
+ - [ ] 날짜·시간 필드가 섞인 `SKeyValueTable` 에서 오른쪽 끝을 맞출지 정했는가 (§4-3 `fieldWidth` — 필터는 `"fill"`, 등록·수정 폼은 기본값. 필드마다 `maxWidth` 를 주지 않는다)
2591
2679
  - [ ] 하단 버튼이 양끝 분리(`justify-between`)이고, 되돌리기가 왼쪽 끝 · 실행이 오른쪽 끝인가
2592
2680
  - [ ] 페이지에 `color="primary"` 채움 버튼이 **1개뿐**인가 (`danger` 채움도 1개, `SDropdownButton` 포함)
2593
2681
  - [ ] 버튼 `size` 가 위치 규칙과 맞는가 (행 내부 `xs` / 화면 액션 `sm` / 모달 푸터 `md`)
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * SBarChart 의 치수·색 상수와 막대 배치 계산.
3
3
  */
4
+ import { AXIS_EDGE_PADDING } from '../../lib/chart/cartesian';
5
+ export { AXIS_EDGE_PADDING };
4
6
  /**
5
7
  * 막대 두께 하한(px) — 시안 `bar width min : 24`.
6
8
  * 이보다 얇아지지 않는다. 카테고리가 너무 많으면 막대가 겹치는데, 그건
@@ -15,14 +17,6 @@ export declare const BAR_GAP = 6;
15
17
  export declare const GROUP_PADDING_MIN = 8;
16
18
  /** 막대 묶음 좌우 여백의 상한(px) — 시안 `bar group paddingX max : 24`. */
17
19
  export declare const GROUP_PADDING_MAX = 24;
18
- /**
19
- * 항목 축 양끝에 두는 여백(px).
20
- *
21
- * 카테고리가 나눠 갖는 자리(band) 바깥에 따로 붙는다 — 첫 막대와 마지막 막대가
22
- * 차트 경계에 붙어 잘린 것처럼 보이지 않게 한다. 세로 막대는 좌우, 가로 막대는
23
- * 위아래에 생긴다.
24
- */
25
- export declare const AXIS_EDGE_PADDING = 24;
26
20
  /** 막대 끝과 값 라벨 사이 간격(px) — 시안 실측. */
27
21
  export declare const VALUE_LABEL_GAP = 6;
28
22
  /**
@@ -17,6 +17,7 @@
17
17
  | `disabled?` | `boolean` | `false` | |
18
18
  | `clearable?` | `boolean` | `false` | 선택값 지우기 버튼. 값이 있을 때만 나타나고, 누르면 `onValueChange` 로 `null` 이 온다. **필수 입력 필드에는 켜지 않는다** — 지우면 다시 고르기 전까지 폼이 통과하지 못한다. `SDatePicker` · `STimePicker` · `SSelect` 의 `clearable` 과 같은 규칙이다. |
19
19
  | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 등급은 `--cmp-field-width-*` 토큰으로 풀리므로 토큰이 바뀌면 화면이 따라간다. 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다. |
20
+ | `maxWidth?` | `SFieldWidth` | — | 컨트롤 최대 너비 — 폭 등급 · 숫자=px · CSS 길이. 지정하지 않으면 값 길이(`YYYY-MM-DD ~ YYYY-MM-DD`)에 맞춘 내장 상한(`size='md'` → `xl`, `'sm'` → `lg`)이 걸려, `width="100%"` 를 받아도 행 전체로 늘어나지 않는다. 팝오버처럼 폭이 이미 좁게 정해진 자리에서 그 폭을 그대로 채워야 하면 `width="100%"` 와 함께 `maxWidth="100%"` 를 준다. `0` 은 상한을 아예 걸지 않는다 — 부모보다 넓어지는 것까지 허용해야 할 때만 쓴다. |
20
21
  | `name?` | `string` | — | |
21
22
  | `rules?` | `Rule[]` | — | |
22
23
  | `status?` | `SFieldStatus` | — | |
@@ -40,6 +40,16 @@ export interface SDateRangePickerProps {
40
40
  * 값 길이에 상한이 없으면 `"100%"` 로 두어 행 전체를 쓴다.
41
41
  */
42
42
  width?: SFieldWidth;
43
+ /**
44
+ * 컨트롤 최대 너비 — 폭 등급 · 숫자=px · CSS 길이. 지정하지 않으면 값 길이(`YYYY-MM-DD ~ YYYY-MM-DD`)에
45
+ * 맞춘 내장 상한(`size='md'` → `xl`, `'sm'` → `lg`)이 걸려, `width="100%"` 를 받아도 행 전체로
46
+ * 늘어나지 않는다.
47
+ *
48
+ * 팝오버처럼 폭이 이미 좁게 정해진 자리에서 그 폭을 그대로 채워야 하면 `width="100%"` 와 함께
49
+ * `maxWidth="100%"` 를 준다. `0` 은 상한을 아예 걸지 않는다 — 부모보다 넓어지는 것까지 허용해야
50
+ * 할 때만 쓴다.
51
+ */
52
+ maxWidth?: SFieldWidth;
43
53
  name?: string;
44
54
  rules?: Rule[];
45
55
  status?: SFieldStatus;
@@ -74,4 +84,4 @@ export interface SRangeCalendarProps {
74
84
  * 캘린더 그리드만 필요한 커스텀 조합(예: 다른 트리거에 팝오버로 얹는 경우)에 재사용한다. */
75
85
  export declare function RangeCalendar({ value, selectable, maxRange, showTimePicker, onSelect, onPendingStartChange, onViewChange, }: SRangeCalendarProps): import("react").JSX.Element;
76
86
  /** SDateRangePicker — sd-date-range-picker 포팅. SField + SPopover(범위 캘린더). */
77
- export declare function SDateRangePicker({ value, onValueChange, onViewChange, size, placeholder, selectable, maxRange, showTimePicker, disabled, clearable, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDateRangePickerProps): import("react").JSX.Element;
87
+ export declare function SDateRangePicker({ value, onValueChange, onViewChange, size, placeholder, selectable, maxRange, showTimePicker, disabled, clearable, width, maxWidth, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDateRangePickerProps): import("react").JSX.Element;
@@ -14,6 +14,7 @@
14
14
  | `search?` | `boolean` | `false` | 우측 검색 패널 |
15
15
  | `radius?` | `'default' \| 'useTop' \| 'full'` | `'default'` | border-radius 제어 |
16
16
  | `bordered?` | `boolean` | `true` | 바깥 테두리. 기본은 `true`. `SSectionHeaderCard` 의 `padding="none"` 안에 넣어 카드 가장자리까지 채울 때 `false` 로 끈다 — 카드가 이미 바깥 테두리를 그리므로, 켜 두면 1px 두 개가 나란히 놓여 그 변만 2px 로 보인다. |
17
+ | `fieldWidth?` | `'auto' \| 'fill'` | `'auto'` | 필드 폭 정책 — **표 하나가 한 번에 정한다**. 필드마다 따로 정하지 않는다. 표는 모든 컨트롤에 `width="100%"` 를 넘기지만, 값 길이를 아는 컨트롤(날짜·기간 피커)은 자기 상한에서 멈춘다. 그래서 한 행에 그런 필드가 섞이면 **왼쪽 끝만 맞고 오른쪽 끝이 어긋나** 보인다. - `'auto'`(기본) — 컨트롤이 자기 폭을 정한다. 상한이 그대로 살아 있어 폭이 "이 칸에 얼마나 긴 값이 들어가는가" 를 알려준다. 등록·수정 폼처럼 입력 길이를 짐작하게 하는 편이 나은 표에 쓴다. - `'fill'` — 모든 컨트롤이 셀 끝까지 찬다. 셀 폭은 열 수와 `tdColSpan` 이 정하므로 오른쪽 끝이 저절로 맞는다. 조회 필터처럼 가지런함이 우선인 표에 쓴다. 대신 폭이 값 길이를 알려주지 않는다 — 날짜 칸이 메모 칸만큼 넓어진다. 개별 필드만 예외로 두려면 그 필드의 `options.maxWidth` 를 준다 — 이 정책보다 우선한다. |
17
18
  | `className?` | `string` | — | |
18
19
  | `style?` | `CSSProperties` | — | |
19
20
 
@@ -122,6 +122,22 @@ export interface SKeyValueTableProps {
122
122
  * 카드가 이미 바깥 테두리를 그리므로, 켜 두면 1px 두 개가 나란히 놓여 그 변만 2px 로 보인다.
123
123
  */
124
124
  bordered?: boolean;
125
+ /**
126
+ * 필드 폭 정책 — **표 하나가 한 번에 정한다**. 필드마다 따로 정하지 않는다.
127
+ *
128
+ * 표는 모든 컨트롤에 `width="100%"` 를 넘기지만, 값 길이를 아는 컨트롤(날짜·기간 피커)은
129
+ * 자기 상한에서 멈춘다. 그래서 한 행에 그런 필드가 섞이면 **왼쪽 끝만 맞고 오른쪽 끝이
130
+ * 어긋나** 보인다.
131
+ *
132
+ * - `'auto'`(기본) — 컨트롤이 자기 폭을 정한다. 상한이 그대로 살아 있어 폭이 "이 칸에 얼마나 긴 값이 들어가는가" 를
133
+ * 알려준다. 등록·수정 폼처럼 입력 길이를 짐작하게 하는 편이 나은 표에 쓴다.
134
+ * - `'fill'` — 모든 컨트롤이 셀 끝까지 찬다. 셀 폭은 열 수와 `tdColSpan` 이 정하므로 오른쪽
135
+ * 끝이 저절로 맞는다. 조회 필터처럼 가지런함이 우선인 표에 쓴다. 대신 폭이 값 길이를
136
+ * 알려주지 않는다 — 날짜 칸이 메모 칸만큼 넓어진다.
137
+ *
138
+ * 개별 필드만 예외로 두려면 그 필드의 `options.maxWidth` 를 준다 — 이 정책보다 우선한다.
139
+ */
140
+ fieldWidth?: 'auto' | 'fill';
125
141
  /** 값 변경 (sdChange) */
126
142
  onChange?: (detail: SKeyValueChangeDetail) => void;
127
143
  /** 검색 클릭 (sdSearch) */
@@ -130,5 +146,5 @@ export interface SKeyValueTableProps {
130
146
  style?: CSSProperties;
131
147
  }
132
148
  /** SKeyValueTable — sd-key-value-table 포팅. th/td 폼 레이아웃 + 필드 렌더러. */
133
- export declare function SKeyValueTable({ fields, values, dense, search, radius, bordered, onChange, onSearch, className, style, }: SKeyValueTableProps): import("react").JSX.Element | null;
149
+ export declare function SKeyValueTable({ fields, values, dense, search, radius, bordered, fieldWidth, onChange, onSearch, className, style, }: SKeyValueTableProps): import("react").JSX.Element | null;
134
150
  export {};
@@ -0,0 +1,106 @@
1
+ # SLineChart
2
+
3
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
4
+
5
+ ### SLineChart
6
+
7
+ #### Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `categories` | `string[]` | — | 가로축에 놓일 항목 이름 |
12
+ | `series` | `SLineChartSeries[]` | — | 계열 목록. 각 `data` 는 `categories` 와 같은 순서다 |
13
+ | `curve?` | `SLineChartCurve` | `'linear'` | 점을 잇는 방식. `smooth` 는 값을 넘어가 부풀지 않는 곡선이다 |
14
+ | `area?` | `boolean` | `false` | 선 아래를 계열 색으로 채운다. 크기의 변화를 함께 보여줄 때 쓴다 |
15
+ | `stacked?` | `boolean` | `false` | 계열을 겹쳐 그리는 대신 **아래 계열 위에 쌓는다**. 전체의 크기와 그 안의 구성을 함께 볼 때 쓴다 — 맨 위 선이 곧 합계다. 툴팁은 쌓기 전 각 계열의 값을 보여준다 |
16
+ | `palette?` | `SLineChartPalette` | `'default'` | 색 팔레트. `custom` 일 때만 `colors` 를 본다 |
17
+ | `colors?` | `SLineChartColor[]` | — | `palette='custom'` 에서 쓸 색 이름을 계열 순서대로. 계열보다 짧으면 앞에서부터 순환한다 |
18
+ | `max?` | `number` | — | 값 축의 최댓값. 주지 않으면 데이터에서 읽기 좋은 값으로 정한다 |
19
+ | `tickCount?` | `number` | `8` | 값 축 눈금 개수 |
20
+ | `showPoint?` | `boolean` | `true` | 데이터 지점마다 점을 찍는다. 꺼도 짚은 항목의 점은 뜬다 |
21
+ | `showValueLabel?` | `boolean` | `false` | 점마다 값을 적는다. 기본은 끔 — 선 그래프는 항목이 많은 데이터에 쓰는 일이 잦아 숫자가 서로 붙는다. 항목이 대여섯 개 안쪽일 때만 켠다 |
22
+ | `showLegend?` | `boolean` | `true` | 범례를 보인다 |
23
+ | `legendPosition?` | `SLineChartLegendPosition` | `'top'` | 범례 위치 |
24
+ | `showTooltip?` | `boolean` | `true` | 항목에 마우스를 올리면 값 상자를 띄운다 |
25
+ | `formatValue?` | `(value: number) => string` | `defaultFormat` | 값 표시 형식 |
26
+ | `height?` | `number \| string` | — | 차트 높이. **아래 축 텍스트까지 포함한 값**이다 — 그림은 그만큼 줄어든다 |
27
+ | `className?` | `string` | — | |
28
+ | `style?` | `CSSProperties` | — | |
29
+
30
+ #### Events
31
+
32
+ | Event | Type | Description |
33
+ |-------|------|-------------|
34
+ | `onPointClick` | `(point: SLineChartPoint) => void` | 점을 눌렀을 때 |
35
+
36
+ ## Types
37
+
38
+ ### SLineChartSeries
39
+
40
+ ```ts
41
+ /** 한 계열 — 선 하나, 범례 한 줄에 대응한다. */
42
+ export interface SLineChartSeries {
43
+ /** 범례·툴팁에 표시할 이름 */
44
+ name: string;
45
+ /** `categories` 와 같은 순서의 값. 빠진 자리(`null`·짧은 배열)에서는 선이 끊긴다 */
46
+ data: (number | null)[];
47
+ }
48
+ ```
49
+
50
+ ### SLineChartCurve
51
+
52
+ ```ts
53
+ /** 점과 점을 잇는 방식. */
54
+ export type SLineChartCurve = 'linear' | 'smooth';
55
+ ```
56
+
57
+ ### SLineChartPalette
58
+
59
+ ```ts
60
+ /**
61
+ * 색 팔레트.
62
+ * - `default` 파랑 한 색. 계열이 하나거나 색으로 나눌 것이 없을 때
63
+ * - `multi` 서로 무관한 항목(11색)을 순서대로
64
+ * - `gradation` 한 지표의 단계(6색)를 진한 쪽부터 순서대로
65
+ * - `custom` `colors` 로 고른 색을 순서대로
66
+ */
67
+ export type SLineChartPalette = 'default' | 'multi' | 'gradation' | 'custom';
68
+ ```
69
+
70
+ ### SLineChartColor
71
+
72
+ ```ts
73
+ /**
74
+ * `palette='custom'` 에서 고를 수 있는 색 이름.
75
+ * positive=늘어남 · negative=줄어듦 · neutral=변화 없음 을 뜻하므로 뜻에 맞게 고른다.
76
+ */
77
+ export type SLineChartColor =
78
+ | 'positivePrimary'
79
+ | 'positiveSecondary'
80
+ | 'negativePrimary'
81
+ | 'negativeSecondary'
82
+ | 'neutralPrimary'
83
+ | 'neutralSecondary';
84
+ ```
85
+
86
+ ### SLineChartLegendPosition
87
+
88
+ ```ts
89
+ /** 범례 위치. */
90
+ export type SLineChartLegendPosition = 'top' | 'bottom';
91
+ ```
92
+
93
+ ### SLineChartPoint
94
+
95
+ ```ts
96
+ /** 툴팁·클릭이 가리키는 지점. */
97
+ export interface SLineChartPoint {
98
+ /** 계열 인덱스 */
99
+ seriesIndex: number;
100
+ /** 카테고리 인덱스 */
101
+ categoryIndex: number;
102
+ /** 그 지점의 값. 누적이어도 **그 계열이 제 몫으로 가진 값**이다 */
103
+ value: number;
104
+ }
105
+ ```
106
+
@@ -0,0 +1,88 @@
1
+ import { type CSSProperties } from 'react';
2
+ /** 점과 점을 잇는 방식. */
3
+ export type SLineChartCurve = 'linear' | 'smooth';
4
+ /** 범례 위치. */
5
+ export type SLineChartLegendPosition = 'top' | 'bottom';
6
+ /**
7
+ * 색 팔레트.
8
+ * - `default` 파랑 한 색. 계열이 하나거나 색으로 나눌 것이 없을 때
9
+ * - `multi` 서로 무관한 항목(11색)을 순서대로
10
+ * - `gradation` 한 지표의 단계(6색)를 진한 쪽부터 순서대로
11
+ * - `custom` `colors` 로 고른 색을 순서대로
12
+ */
13
+ export type SLineChartPalette = 'default' | 'multi' | 'gradation' | 'custom';
14
+ /**
15
+ * `palette='custom'` 에서 고를 수 있는 색 이름.
16
+ * positive=늘어남 · negative=줄어듦 · neutral=변화 없음 을 뜻하므로 뜻에 맞게 고른다.
17
+ */
18
+ export type SLineChartColor = 'positivePrimary' | 'positiveSecondary' | 'negativePrimary' | 'negativeSecondary' | 'neutralPrimary' | 'neutralSecondary';
19
+ /** 툴팁·클릭이 가리키는 지점. */
20
+ export interface SLineChartPoint {
21
+ /** 계열 인덱스 */
22
+ seriesIndex: number;
23
+ /** 카테고리 인덱스 */
24
+ categoryIndex: number;
25
+ /** 그 지점의 값. 누적이어도 **그 계열이 제 몫으로 가진 값**이다 */
26
+ value: number;
27
+ }
28
+ /** 한 계열 — 선 하나, 범례 한 줄에 대응한다. */
29
+ export interface SLineChartSeries {
30
+ /** 범례·툴팁에 표시할 이름 */
31
+ name: string;
32
+ /** `categories` 와 같은 순서의 값. 빠진 자리(`null`·짧은 배열)에서는 선이 끊긴다 */
33
+ data: (number | null)[];
34
+ }
35
+ export interface SLineChartProps {
36
+ /** 가로축에 놓일 항목 이름 */
37
+ categories: string[];
38
+ /** 계열 목록. 각 `data` 는 `categories` 와 같은 순서다 */
39
+ series: SLineChartSeries[];
40
+ /** 점을 잇는 방식. `smooth` 는 값을 넘어가 부풀지 않는 곡선이다 */
41
+ curve?: SLineChartCurve;
42
+ /** 선 아래를 계열 색으로 채운다. 크기의 변화를 함께 보여줄 때 쓴다 */
43
+ area?: boolean;
44
+ /**
45
+ * 계열을 겹쳐 그리는 대신 **아래 계열 위에 쌓는다**. 전체의 크기와 그 안의 구성을
46
+ * 함께 볼 때 쓴다 — 맨 위 선이 곧 합계다. 툴팁은 쌓기 전 각 계열의 값을 보여준다
47
+ */
48
+ stacked?: boolean;
49
+ /** 색 팔레트. `custom` 일 때만 `colors` 를 본다 */
50
+ palette?: SLineChartPalette;
51
+ /** `palette='custom'` 에서 쓸 색 이름을 계열 순서대로. 계열보다 짧으면 앞에서부터 순환한다 */
52
+ colors?: SLineChartColor[];
53
+ /** 값 축의 최댓값. 주지 않으면 데이터에서 읽기 좋은 값으로 정한다 */
54
+ max?: number;
55
+ /** 값 축 눈금 개수 */
56
+ tickCount?: number;
57
+ /** 데이터 지점마다 점을 찍는다. 꺼도 짚은 항목의 점은 뜬다 */
58
+ showPoint?: boolean;
59
+ /**
60
+ * 점마다 값을 적는다. 기본은 끔 — 선 그래프는 항목이 많은 데이터에 쓰는 일이 잦아
61
+ * 숫자가 서로 붙는다. 항목이 대여섯 개 안쪽일 때만 켠다
62
+ */
63
+ showValueLabel?: boolean;
64
+ /** 범례를 보인다 */
65
+ showLegend?: boolean;
66
+ /** 범례 위치 */
67
+ legendPosition?: SLineChartLegendPosition;
68
+ /** 항목에 마우스를 올리면 값 상자를 띄운다 */
69
+ showTooltip?: boolean;
70
+ /** 값 표시 형식 */
71
+ formatValue?: (value: number) => string;
72
+ /** 차트 높이. **아래 축 텍스트까지 포함한 값**이다 — 그림은 그만큼 줄어든다 */
73
+ height?: number | string;
74
+ /** 점을 눌렀을 때 */
75
+ onPointClick?: (point: SLineChartPoint) => void;
76
+ className?: string;
77
+ style?: CSSProperties;
78
+ }
79
+ /**
80
+ * SLineChart — 꺾은선 그래프.
81
+ *
82
+ * 항목의 순서에 뜻이 있을 때(기간별 추이) 쓴다. 순서가 없는 항목끼리 크기를 견주는
83
+ * 것이 목적이면 `SBarChart` 가 맞다 — 선은 "이어진다" 는 뜻을 덤으로 얹는다.
84
+ *
85
+ * 값 축은 막대 차트와 마찬가지로 **항상 0 을 포함한다.** 0 을 잘라 확대하면 같은 데이터가
86
+ * 훨씬 가파른 변화로 보인다.
87
+ */
88
+ export declare const SLineChart: import("react").ForwardRefExoticComponent<SLineChartProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1 @@
1
+ export { SLineChart, type SLineChartProps, type SLineChartSeries, type SLineChartPoint, type SLineChartCurve, type SLineChartPalette, type SLineChartColor, type SLineChartLegendPosition, } from './SLineChart';
@@ -0,0 +1,75 @@
1
+ /**
2
+ * SLineChart 의 치수·색 상수와 선·영역 경로 계산.
3
+ *
4
+ * **시안이 없는 컴포넌트다.** 그래서 두 가지 규칙으로 값을 정했다.
5
+ *
6
+ * 1. 두 차트가 공유하는 것(축 여백·눈금·격자·툴팁·범례·팔레트)은 새로 정하지 않고
7
+ * 바 차트와 `lib/chart` 에서 그대로 가져온다 — 같은 데이터를 막대에서 선으로
8
+ * 바꿨을 때 그림이 시작하는 자리와 눈금이 어긋나면 두 차트를 견줄 수 없다.
9
+ * 2. 선 고유의 것(선 두께·점 지름 따위)만 여기서 정한다. 디자인 토큰이 생기면
10
+ * 이 파일의 상수부터 토큰 참조로 바꾼다.
11
+ *
12
+ * **토큰 이름이 `bar` 로 시작하는 것을 그대로 쓰는 자리가 있다** — 값 라벨의 색·타이포와
13
+ * 짚은 항목의 바탕이 그렇다(`--cmp-chart-bar-dataLabel-*`, `--cmp-chart-barContainer-*`).
14
+ * 이름만 막대 것이고 뜻은 "차트의 값 라벨"·"짚은 항목의 바탕" 이라, 선 차트에서 값을
15
+ * 따로 정하면 같은 자리의 글자가 차트마다 달라진다. `chart.line.*` 토큰이 생기면 옮긴다.
16
+ */
17
+ /** 선 두께(px). */
18
+ export declare const LINE_WIDTH = 2;
19
+ /** 데이터 지점에 찍는 점의 반지름(px) — 시안 dot, 지름 8. 계열 색으로만 채운 민 원이다. */
20
+ export declare const POINT_RADIUS = 4;
21
+ /**
22
+ * 짚은 항목의 점 — 시안 dotHover, 지름 20.
23
+ *
24
+ * 색 점(반지름 5)이 **바탕 원**(반지름 9.5 + 1px 테두리) 위에 앉는다. 바탕 원은 점 둘레를
25
+ * 비워 선·영역 채움과 겹치지 않게 하고, 테두리가 그 경계를 계열 색으로 잡아 준다.
26
+ * 바탕색을 흰색이 아니라 `--cmp-chart-bg` 로 두는 이유는 "흰 원을 그린다" 가 아니라
27
+ * "차트 바탕만큼 비워 둔다" 이기 때문이다.
28
+ */
29
+ export declare const POINT_HOVER_RADIUS = 9.5;
30
+ export declare const POINT_HOVER_BORDER_WIDTH = 1;
31
+ export declare const POINT_HOVER_INNER_RADIUS = 5;
32
+ export declare const POINT_HOVER_BG = "var(--cmp-chart-bg)";
33
+ /**
34
+ * 짚은 점이 실제로 차지하는 반지름 — 테두리 **바깥**까지(=지름 20의 절반).
35
+ * 테두리는 반지름 위에 걸쳐 그려지므로 절반이 밖으로 나간다. 툴팁이 이만큼 비켜선다.
36
+ */
37
+ export declare const POINT_HOVER_EXTENT: number;
38
+ /**
39
+ * 항목 하나가 가져야 할 최소 폭(px).
40
+ *
41
+ * 막대 차트의 최소 band(여백 8 + 막대 24 + 여백 8)와 같은 값이다 — 같은 데이터에서
42
+ * 두 차트의 스크롤 시점이 같아야 한다. 이보다 좁아지면 점이 서로 붙고 항목 이름이
43
+ * 겹치므로, 좁히는 대신 가로로 스크롤한다.
44
+ */
45
+ export declare const BAND_MIN = 40;
46
+ /** 점과 값 라벨 사이 간격(px) — 막대 차트와 같다. */
47
+ export declare const VALUE_LABEL_GAP = 6;
48
+ /**
49
+ * 영역 채움의 불투명도.
50
+ *
51
+ * 선은 또렷하게 두고 영역만 물리는 값이다. 계열이 여럿이면 채움끼리 겹쳐 색이 탁해지는데,
52
+ * 그건 채움을 진하게 잡아서가 아니라 겹쳐 그리는 것 자체의 한계다 — 계열이 많으면
53
+ * `area` 를 끄거나 `stacked` 로 쌓는다.
54
+ */
55
+ export declare const AREA_OPACITY = "var(--opacity-040)";
56
+ export declare const LINE_DATA_LABEL_COLOR = "var(--cmp-chart-bar-dataLabel-color-default)";
57
+ /** 값 라벨의 타이포 — 짚은 항목만 굵고 크게 뜬다(막대 차트와 같은 규칙). */
58
+ export declare const LINE_DATA_LABEL_TYPO = "typo-body-sm-medium";
59
+ export declare const LINE_DATA_LABEL_TYPO_HOVER = "typo-body-md-bold";
60
+ /** 그림 좌표계의 한 점(px). */
61
+ export interface ChartPoint {
62
+ x: number;
63
+ y: number;
64
+ }
65
+ /**
66
+ * 이어진 점들을 지나는 선. 점이 하나뿐이면 그릴 선이 없다(점만 남는다).
67
+ */
68
+ export declare function linePath(points: ChartPoint[], smooth: boolean): string;
69
+ /**
70
+ * 위 선과 아래 선 사이를 채우는 영역.
71
+ *
72
+ * 아래 선을 **역순으로** 이어 닫는다. 곡선일 때 아래 선도 같은 방식으로 휘어야 하므로
73
+ * 직선으로 질러가지 않고 다시 보간한다 — 안 그러면 채움이 곡선 아래로 삐져나온다.
74
+ */
75
+ export declare function areaPath(top: ChartPoint[], bottom: ChartPoint[], smooth: boolean): string;
@@ -13,6 +13,8 @@
13
13
  | `rows?` | `SRow[]` | `[]` | |
14
14
  | `rowKey?` | `string` | `'id'` | 행 식별 필드 |
15
15
  | `selectable?` | `boolean` | `false` | 행 선택 체크박스 |
16
+ | `dragSelectable?` | `boolean` | `false` | 행을 드래그해서 고른다. 기본 `false`. 고르는 수단만 다를 뿐 `selectable` 과 같은 선택이다 — `selected` · `onSelectedChange` · `isRowSelectable` 을 그대로 쓴다. 체크박스 열은 생기지 않고, 대신 고른 구간이 배경색과 바깥 테두리로 표시된다. **`selectable` 과 함께 켜면 `selectable` 이 이긴다** — 체크박스가 있는 표에서 드래그까지 걸리면 글자를 긁으려던 손이 선택을 갈아치운다. 둘 다 필요해 보이면 체크박스 쪽만 남긴다. 누른 자리가 구간의 기준점이고, 끌어간 자리까지가 구간이다. **새로 끌면 이전 선택은 풀린다** — 기존 선택에 더하려면 `Ctrl`(macOS 는 `Cmd`)을 짚고 끈다. 잠긴 행(`isRowSelectable`)은 구간 안에 있어도 그냥 지나간다. **켜 두면 셀 안의 글자를 긁어 복사할 수 없다** — 끌기가 곧 선택이라 글자 선택과 같은 손짓을 두고 다툰다. 값을 복사해 가는 표에는 켜지 않는다. 셀 안의 버튼·입력은 그대로 눌린다. |
17
+ | `contextMenuItems?` | `STableContextMenuItem[]` | — | 드래그 선택에서 **행을 오른쪽 클릭했을 때** 커서 자리에 뜨는 메뉴의 항목. 주지 않거나 비면 메뉴가 뜨지 않고 브라우저 기본 메뉴가 나온다. **`dragSelectable` 에서만 동작한다** — 체크박스 모드에서는 무시된다. 고르지 않은 행에서 누르면 **그 행만 고른 뒤** 열린다. 메뉴가 다룰 대상과 화면에 칠해진 것이 어긋나지 않게 하기 위함이다. 잠긴 행(`isRowSelectable`) 위에서는 열리지 않는다 — 고를 수 없는 행을 대상으로 삼을 수 없기 때문이다. |
16
18
  | `isRowSelectable?` | `(row: SRow) => boolean` | — | 이 행을 고를 수 있는가. 주지 않으면 모든 행을 고를 수 있다. 고를 수 없는 행은 체크박스가 잠기고, **전체 선택·Shift 구간 선택의 셈에서도 빠진다** — 잠긴 행이 셈에 남으면 "전부 선택됨"에 닿지 못해 헤더 체크박스가 해제 방향으로 가지 못한다. 목록에서 지워 버리는 것과 다르다. 실패 0건인 차수처럼 **자리는 보여야 하지만 대상이 될 수는 없는 행**에 쓴다. 아예 대상이 아니라면 `rows` 에서 거르는 편이 낫다. 잠금은 그리기 시점의 판정일 뿐이라, 이미 `selected` 에 든 행이 나중에 잠겨도 DS 가 빼지 않는다(제어 상태를 말없이 바꾸지 않는다). 다만 헤더의 전체 해제로는 걷어낼 수 있다. |
17
19
  | `selected?` | `SRow[]` | `[]` | |
18
20
  | `sort?` | `STableSort \| null` | `null` | 정렬 상태 (controlled). `null`·미지정이면 정렬 없음. 컴포넌트는 정렬 상태를 갖지 않는다 — 서버 정렬이면 이 값이 곧 조회 조건이고, 뒤로가기·새로고침·링크 공유로 복원돼야 하므로 진실은 URL·store 쪽에 있어야 한다. 행을 실제로 정렬하는 것도 소비 앱 몫이다 (`STable` 은 받은 순서대로 그린다). |
@@ -43,6 +45,7 @@
43
45
 
44
46
  | Event | Type | Description |
45
47
  |-------|------|-------------|
48
+ | `onContextMenuItemClick` | `(value: string \| number, rows: SRow[]) => void` | 메뉴 항목을 골랐을 때. 고른 항목의 `value` 와 **그 메뉴가 다루는 행들**이 함께 온다. 행 목록은 메뉴가 열린 시점에 확정되므로 `selected` 를 따로 읽지 않아도 된다 — 오른쪽 클릭이 선택을 바꾸는 경우(고르지 않은 행)에도 바뀐 뒤의 행이 온다. |
46
49
  | `onSelectedChange` | `(rows: SRow[]) => void` | |
47
50
  | `onSortChange` | `(sort: STableSort \| null) => void` | 정렬 헤더 클릭 (`asc → desc → 해제` 3단). 해제되면 `null` 이 온다. 다중 정렬은 1차 안에서 지원하지 않는다. |
48
51
  | `onDenseChange` | `(dense: boolean) => void` | 밀도 변경 (하단 바의 밀도 토글을 눌렀을 때). 표시는 테이블이 알아서 바꾸므로 받지 않아도 되고, 사용자가 고른 밀도를 다음 방문까지 기억해 두려는(로컬 저장 등) 페이지만 받으면 된다. |
@@ -104,6 +107,19 @@ export interface STableHeaderGroup {
104
107
  export type SRow = Record<string, any>;
105
108
  ```
106
109
 
110
+ ### STableContextMenuItem
111
+
112
+ ```ts
113
+ /** 오른쪽 클릭 메뉴의 항목 한 줄 */
114
+ export interface STableContextMenuItem {
115
+ /** 클릭 시 `onContextMenuItemClick` 으로 돌아오는 값 */
116
+ value: string | number;
117
+ label: string;
118
+ icon?: SIconName;
119
+ disabled?: boolean;
120
+ }
121
+ ```
122
+
107
123
  ### STableSort
108
124
 
109
125
  ```ts