sellmate-design-system-react 9.0.0-beta.89 → 9.0.0-beta.90

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.
@@ -48,7 +48,7 @@
48
48
  | 분류 | 컴포넌트 |
49
49
  | --- | --- |
50
50
  | **버튼·링크** | `SButton` `SGhostButton` `SMeatballButton`(⋯ 로 '더 보기' 메뉴를 여는 아이콘 버튼) `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
51
- | **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
51
+ | **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `SSlider` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
52
52
  | **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
53
53
  | **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `STreeTable`(행이 부모-자식 계층을 이루는 표 — 펼치고 접는다) `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` `STimeline`(날짜로 묶인 시간순 기록 — 작업 로그·메모 이력) |
54
54
  | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SLauncherListBox`(런처 버튼을 눌러 뜨는 서비스 목록) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
@@ -84,6 +84,7 @@ AI 에이전트는 코드를 생성하기 전에 이 목록을 반드시 지킨
84
84
  | `<input type="text/password/...">` | `SInput` |
85
85
  | `<input type="search">` | `SSearchInput` |
86
86
  | `<input type="number">` | `SNumberInput` |
87
+ | `<input type="range">` | `SSlider` |
87
88
  | `<input type="checkbox">` | `SCheckbox`, `SToggle`, `SSwitch` |
88
89
  | `<input type="radio">` | `SRadio`, `SRadioButton` |
89
90
  | `<input type="file">` | `SFilePicker` |
@@ -176,7 +177,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
176
177
  | --- | --- | --- |
177
178
  | **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
178
179
  | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `STreeTable` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `STimeline` `SCallout` `SContextAlert` `SBarChart` `SLineChart` `SPieChart` `SDonutChart` `STabs` `SStepper` `SPagination` `SDivider` |
179
- | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SMeatballButton` `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` `SSkeleton` |
180
+ | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SMeatballButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `SSlider` `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` `SSkeleton` |
180
181
  | **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `SLogo` `STextLink` `SChip` |
181
182
  | **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SLauncherListBox`(런처 버튼에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
182
183
 
@@ -537,6 +538,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
537
538
  | 제목·굵게·목록·색 같은 **서식이 남아야 하는** 글을 받는다 | `SEditor` | §3-7-1 |
538
539
  | 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
539
540
  | 숫자(수량·금액)를 받는다 | `SNumberInput` | §3-7-13 |
541
+ | 정해진 범위 안에서 수치 하나·구간을 끌어서 고르게 한다 | `SSlider` | §3-7-16 |
540
542
  | 바코드를 스캔해 받는다 | `SBarcodeInput` | |
541
543
  | 목록에서 하나 고르게 한다 | `SSelect` | §3-7-2 |
542
544
  | 선택지를 항상 펼쳐 두고 하나 고르게 한다 | `SRadioGroup` | §3-7-2 |
@@ -1909,6 +1911,16 @@ const [from, setFrom] = useState<string | null>(null);
1909
1911
 
1910
1912
  직접 쓰는 경우는 하나뿐이다 — **디자인 시스템에 없는 컨트롤**에 다른 필드와 똑같은 라벨·필수·에러 모양을 붙일 때.
1911
1913
 
1914
+ - **라벨은 `controlId` 로 컨트롤에 잇는다.** 컨트롤에 `id` 를 주고 같은 값을 `controlId` 로 넘기면 라벨이 그 컨트롤의 이름이 되고, 라벨을 누르면 컨트롤에 포커스가 간다. 컨트롤이 `input`·`textarea`·`select`·`button` 이 아니라 `role` 로 만든 요소면 `aria-labelledby={fieldLabelId(id)}` 를 직접 건다.
1915
+ - **라벨이 보이는 입력에 `aria-label` 을 다시 적지 않는다.** DS 입력 컴포넌트는 `label` 을 알아서 컨트롤에 잇는다. 화면의 라벨과 읽히는 이름을 두 번 적으면 어긋난다. `aria-label`(`SCheckbox`·`SChipInput` 은 `ariaLabel`)은 **라벨 글자 없이 서는 컨트롤**에만 준다 — 표의 행 선택 체크박스처럼.
1916
+
1917
+ ```tsx
1918
+ ✅ <SField label="색상" controlId={id}><input id={id} type="color" /></SField>
1919
+ ✅ <STextarea label="그룹명" value={name} onValueChange={setName} /> {/* 이름은 label 이 된다 */}
1920
+ ❌ <STextarea label="그룹명" aria-label="그룹명" … /> {/* 이름을 두 번 적는다 */}
1921
+ ❌ <SField label="색상"><input type="color" aria-label="색상" /></SField> {/* 라벨을 눌러도 포커스가 안 간다 */}
1922
+ ```
1923
+
1912
1924
  #### 3-7-6. 여러 건을 나열하는 넷 — STable vs SList vs SDraggableList vs STimeline
1913
1925
 
1914
1926
  | 판별 | 사용 |
@@ -1925,6 +1937,7 @@ const [from, setFrom] = useState<string | null>(null);
1925
1937
  - **`SList` 의 자식은 `SListItem` 을 권장한다.** 다른 자식도 그대로 렌더되지만, 펼치는 항목은 `SExpansionList` + `SExpansionItem` 이, 끌어서 순서를 바꾸는 항목은 `SDraggableList` + `SDraggableItem` 이 여닫힘·정렬 동작까지 함께 관리하므로 그쪽을 쓴다 (§3-7-7).
1926
1938
  - **항목 사이 구분선은 리스트가 알아서 그린다.** `SListItem`·`SExpansionItem`·`SDraggableItem` 셋 다 스스로 구분선을 그리지 않는다. `SList`·`SExpansionList`·`SDraggableList` 가 자식 **사이에** 구분선을 넣으므로 아이템에 `border-b` 를 직접 붙이지 않고, 켜는 prop 도 따로 없다. 마지막 항목 아래에는 선이 남지 않는다. 테두리형(`bordered`)은 테두리가 구분 역할을 하므로 리스트가 구분선을 빼고, `gap` 으로 띄운다 — `gap` 을 준 목록에도 구분선은 들어가지 않는다.
1927
1939
  - **`SListItem` 은 기본이 표시 전용이다.** 눌러서 이동·선택하게 하려면 `clickable` 을 준다 — hover·`selected`·`interaction="chevron"` 표현이 전부 여기에 딸려 있어서, `clickable` 없이 `selected` 만 주면 선택 표시가 나오지 않는다. `SExpansionList` 는 자식 아이템을 알아서 클릭 가능하게 만들어 이 함정을 막아 준다 — 선택 상태 자체는 앱이 든다 (§3-7-7).
1940
+ - **`clickable` 에 `onClick` 까지 주면 키보드로도 눌린다.** 항목이 버튼으로 Tab 순서에 들고 Enter·Space 가 `onClick` 을 부른다. `role`·`tabIndex`·키 처리를 직접 붙이거나, 그것을 붙인 `div` 로 항목을 감싸지 않는다.
1928
1941
 
1929
1942
  ```tsx
1930
1943
  ✅ <SList><SListItem title="일반 문의" />…</SList> {/* 줄로 구분되는 목록 — 구분선은 자동 */}
@@ -1933,6 +1946,7 @@ const [from, setFrom] = useState<string | null>(null);
1933
1946
  ❌ <SList><SListItem title="일반 문의" className="border-b" />…</SList> {/* 구분선을 직접 붙이지 않는다 */}
1934
1947
  ❌ <SList><><SListItem title="일반 문의" /><SListItem title="결제 문의" /></></SList> {/* Fragment 로 묶으면 그 안쪽은 구분되지 않는다 */}
1935
1948
  ❌ <SListItem title="일반 문의" selected /> {/* clickable 없으면 선택 표시가 안 나온다 */}
1949
+ ❌ <div role="button" tabIndex={0} onKeyDown={…}><SListItem title="일반 문의" clickable /></div> {/* 키보드 조작은 항목이 한다 */}
1936
1950
  ✅ <STimeline groups={[{ heading: '2026-10-07', entries: [{ key: id, time: '14:32', content: <Memo … /> }] }]} />
1937
1951
  ❌ <div className="grid grid-cols-[52px_8px_1fr]"><span className="rounded-full …" />…</div> {/* 타임라인 레일을 직접 그리지 않는다 */}
1938
1952
  ```
@@ -2222,6 +2236,30 @@ const [selectedId, setSelectedId] = useState<string>();
2222
2236
  ❌ <SSkeleton className="bg-grey-20" /> {/* 색은 토큰이 넣는다 — 어두운 면이면 mode="dark" */}
2223
2237
  ```
2224
2238
 
2239
+ #### 3-7-16. 숫자 둘 — SNumberInput vs SSlider
2240
+
2241
+ | 판별 | 사용 |
2242
+ | --- | --- |
2243
+ | **정확한 값**이 중요하다 (수량·금액·우선순위) | `SNumberInput` |
2244
+ | 범위가 정해져 있고 **대략의 위치**를 고르면 된다 (할인율·가중치·가격대 필터) | `SSlider` |
2245
+ | 범위의 **양 끝**을 함께 고른다 | `SSlider` 에 `value={[시작, 끝]}` |
2246
+
2247
+ - **단일·범위는 `value` 의 모양으로 정한다.** `number` 면 손잡이 하나, `[시작, 끝]` 이면 둘이다. 따로 켜는 prop 이 없다.
2248
+ - **서버 요청·목록 다시 불러오기는 `onValueCommit` 에서 한다.** `onValueChange` 는 끄는 동안 step 마다 불린다.
2249
+ - **값을 옆에 따로 적으면 `showValue="none"`, 아니면 기본(`drag`)이나 `always`.** 두 손잡이가 가까이 모일 수 있는 범위에는 `always` 를 쓰지 않는다 — 말풍선이 겹친다.
2250
+ - **`marks` 는 고를 수 있는 값이 몇 개 안 될 때만 `true` 로 준다.** step 이 촘촘하면 대표 위치만 배열로 준다.
2251
+ - **라벨을 준다.** `label` 이 손잡이의 접근성 이름이 된다.
2252
+ - 트랙·손잡이 색과 크기는 컴포넌트가 넣는다. 직접 주지 않는다.
2253
+
2254
+ ```tsx
2255
+ ✅ <SSlider label="할인율" value={rate} onValueChange={setRate} formatValue={v => `${v}%`} />
2256
+ ✅ <SSlider label="가격대" min={0} max={100000} step={1000} value={range}
2257
+ onValueChange={setRange} onValueCommit={refetch} />
2258
+
2259
+ ❌ <SSlider label="주문 수량" min={1} max={9999} … /> {/* 정확한 값을 받는 자리 — SNumberInput */}
2260
+ ❌ <SSlider marks min={0} max={100} … /> {/* step 1 마다 점 100개 — 대표 위치만 배열로 */}
2261
+ ```
2262
+
2225
2263
  ---
2226
2264
 
2227
2265
  ## 4. 페이지 레시피 — 표준 골격
@@ -4475,10 +4513,12 @@ export default function StoreSetupPage() {
4475
4513
  - **여백은 영역마다 다르다** (§2-2 상담 콘솔 예). 목록·템플릿은 같은 항목의 반복이라 목록 컴포넌트가 자기 여백을 갖고, 머리 줄도 목록 항목의 좌우 여백(`--cmp-listItem-paddingX`)에 맞춘다. 대화는 말풍선·파일·시스템 안내가 섞여 24 다 — 머리 줄·스크롤 안쪽 상자가 24 를 쓴다. 입력 영역은 `SDivider` 로 대화와 나누고, 좌우는 메시지와 끝을 맞춰 24, 위아래는 16 이다.
4476
4514
  - **대화는 최신이 바닥이다.** 쌓기 상자에 `min-h-full justify-end` 로 메시지가 적어도 바닥에 붙이고, 처음 열 때와 메시지가 늘 때 스크롤 영역(`SScrollArea` 의 ref)을 맨 아래로 내린다. 입력창은 스크롤 밖 바닥에 고정한다.
4477
4515
  - **메시지 한 건 = 한 사람이 이어 말한 묶음이다.** 같은 사람이 같은 시각에 이어 보낸 글·파일은 `SChatMessage` 하나로 묶어 `messages`·`attachments` 로 넘긴다 — 말풍선마다 컴포넌트를 따로 세우지 않는다. 파일은 `attachments` 로만 넘긴다 (`messages` 로 넘기면 칸이 두 겹이 된다).
4516
+ - **아바타 칸은 말하는 상대가 둘 이상일 때만 둔다.** 상담 콘솔처럼 고객·상담사가 오가면 켜 둔다(기본). 챗봇 답변처럼 받는 쪽이 하나뿐인 대화는 받은 메시지 전부에 `showAvatar={false}` 를 준다 — 대화 안에서 섞지 않는다. 칸을 CSS 로 숨기지 않는다.
4478
4517
  - **줄 사이 간격은 토큰이 정한다** — 메시지 묶음 사이는 `--cmp-chatMessage-group-message-gap`, 시스템 안내(`SChatSystemMessage`) 앞뒤는 그보다 넓은 `--cmp-chatMessage-group-system-gap`. 숫자로 적지 않는다.
4479
4518
  - **날짜가 바뀌는 자리에는 날짜 구분선을 끼운다** — 가로 `SDivider` 두 개 사이에 날짜(`typo-body-sm-default` + `text-fg-tertiary`). 일어난 사실이 아니라 흐름의 경계라 시스템 안내 알약(`SChatSystemMessage`)을 쓰지 않는다. **날짜는 `2026년 10월 6일 화요일`** 로 쓴다 — 말풍선 시각과 같은 대화 예외다(§1-4). `toLocaleDateString()` 은 로케일에 따라 결과가 바뀌므로 쓰지 않고 연·월·일·요일을 직접 이어 붙인다. 가로 구분선은 기본이 전체 폭이라 한 줄에 둘을 놓을 때는 `className="w-auto flex-1"` 로 남은 폭을 나눠 갖게 한다. 앞뒤 간격은 시스템 안내와 같은 토큰이다. 날짜가 다르면 같은 시각이어도 한 묶음으로 합치지 않는다.
4480
4519
  - 대화의 시각은 `오후 5:52` 로 쓴다 (§1-4 대화 예외). 읽음 표시는 보낸 메시지에만, 템플릿으로 보낸 글은 `variant="template"` 로 면을 나눈다.
4481
4520
  - **입력 중인 글은 페이지가 상담마다 든다.** 상담을 옮겨도 쓰던 글이 남고, 템플릿 패널이 그 글에 넣을 수 있다. 템플릿은 누르면 **입력창에 넣는다** — 바로 보내지 않고 상담사가 고쳐 보낸다. 보내기 전 첨부는 입력창 위에 `SChatAttachedFile` 로 쌓는다.
4521
+ - **첨부를 받는 대화면 `onAttach` 와 `onPasteFiles` 를 함께 넘긴다** — 붙여넣은 파일·캡처 이미지도 첨부 버튼으로 고른 것과 같은 자리에 쌓는다. 붙여넣기에서 파일을 골라내고 글로 들어가지 않게 막는 것은 입력창이 한다 — `onPaste` 로 직접 처리하지 않는다. 첨부가 쌓여 있으면 `hasAttachments` 를 켠다 — 글 없이 파일만 보낼 수 있고, 이때 `onSend` 에는 빈 문자열이 온다.
4482
4522
  - **상담 종료는 `SChatInput` 의 `footerAction` 이다** — 전송과 층이 다른 동작이라 입력창 아래 보조 슬롯(`STextLink`)에 두고, 실행 전에 `SModal.confirm` 으로 묻는다. 종료된 상담은 입력창을 `disabled` 로 잠그고 종료 링크를 뺀다.
4483
4523
  - 목록의 상태는 `STag` 다 — 색 점(`SBadge`)만으로 찍지 않는다 (§3-1). 템플릿 분류는 `SExpansionList` 루트 하나에 분류(`SExpansionItem`) 안 템플릿(`SListItem`)을 바로 넣는다 (§3-7-7).
4484
4524
 
@@ -4844,6 +4884,10 @@ function Conversation({
4844
4884
  onValueChange={onDraftChange}
4845
4885
  onSend={send}
4846
4886
  onAttach={() => setAttached(current => [...current, `첨부_${current.length + 1}.png`])}
4887
+ // 붙여넣은 파일·캡처 이미지도 첨부 버튼으로 고른 것과 같은 자리에 쌓는다
4888
+ onPasteFiles={files => setAttached(current => [...current, ...files.map(f => f.name)])}
4889
+ // 첨부가 쌓여 있으면 글 없이도 보낼 수 있다
4890
+ hasAttachments={attached.length > 0}
4847
4891
  // 상담 종료는 전송과 층이 다른 동작 — 입력창 아래 보조 액션 슬롯이다
4848
4892
  footerAction={!closed && <STextLink label="상담 종료" onClick={confirmClose} />}
4849
4893
  />
@@ -6382,6 +6426,7 @@ export type SCalloutMessage = string | SCalloutMessage[];
6382
6426
  | `value?` | `string` | `''` | 입력값 (제어) |
6383
6427
  | `placeholder?` | `string` | `DEFAULT_PLACEHOLDER` | 안내 문구. 세 제품이 같은 말로 서도록 기본값을 쥐고 있다 |
6384
6428
  | `disabled?` | `boolean` | `false` | 비활성 |
6429
+ | `hasAttachments?` | `boolean` | `false` | 보낼 첨부가 쌓여 있는지. 켜면 글이 비어도 전송 버튼이 열린다 — 파일·이미지만 보낼 수 있다. 첨부 목록은 앱이 쥐므로 입력창은 그 유무만 받는다. 첨부를 받는 대화면 쌓인 첨부 수로 켠다. |
6385
6430
  | `busy?` | `boolean` | `false` | 응답을 기다리는 중인지. 켜면 전송 버튼 자리에 정지 버튼이 서고 Enter 전송이 잠긴다 — 답이 오는 동안 또 보내지 못하게 막는 자리다. |
6386
6431
  | `footerAction?` | `ReactNode` | — | 입력창 아래 보조 액션 슬롯. 없으면 그 줄이 통째로 빠진다. 상담 종료처럼 **대화 자체를 끝내는, 전송과 층이 다른 동작**이 오는 자리다. 무엇을 놓을지는 앱이 정하고, 입력창은 가운데에 세우는 것까지만 한다 — `STextLink` 를 넣는 것이 보통이다. |
6387
6432
  | `maxRows?` | `number` | `DEFAULT_MAX_ROWS` | 입력창이 자랄 수 있는 최대 줄 수. 여기 닿으면 더 자라지 않고 그 안에서 스크롤한다. |
@@ -6394,8 +6439,9 @@ export type SCalloutMessage = string | SCalloutMessage[];
6394
6439
  | Event | Type | Description |
6395
6440
  |-------|------|-------------|
6396
6441
  | `onValueChange` | `(value: string) => void` | 값 변경 |
6397
- | `onSend` | `(value: string) => void` | 전송. 전송 버튼을 누르거나 Enter 를 쳤을 때 현재 값을 넘긴다. **값이 비면 전송 버튼이 잠긴다** — 빈 메시지는 보낼 수 없다. |
6442
+ | `onSend` | `(value: string) => void` | 전송. 전송 버튼을 누르거나 Enter 를 쳤을 때 현재 값을 넘긴다. **값이 비면 전송 버튼이 잠긴다** — 빈 메시지는 보낼 수 없다. 단 `hasAttachments` 가 켜져 있으면 글 없이도 보낼 수 있고, 이때 빈 문자열을 넘긴다. |
6398
6443
  | `onAttach` | `() => void` | 파일 첨부. **주면 입력창 왼쪽에 첨부 버튼이 선다** |
6444
+ | `onPasteFiles` | `(files: File[]) => void` | 붙여넣은 파일. 클립보드에 파일(캡처한 이미지 포함)이 있으면 불린다. **파일이 있으면 글자 붙여넣기를 막는다** — 그대로 두면 파일 이름이 글로 함께 들어간다. 파일이 없는 붙여넣기는 평소대로 글로 들어간다. 받은 파일을 어디에 쌓을지는 앱이 정한다 — 첨부 버튼(`onAttach`)으로 고른 파일과 같은 자리에 쌓는 것이 보통이다. |
6399
6445
  | `onStop` | `() => void` | 정지. `busy` 일 때 정지 버튼을 누르면 불린다 |
6400
6446
 
6401
6447
  ## Dependencies
@@ -6425,6 +6471,7 @@ export type SCalloutMessage = string | SCalloutMessage[];
6425
6471
  | `readStatus?` | `SChatMessageReadStatus` | — | 상대가 읽었는지. 시각 왼쪽에 `읽음`·`안 읽음` 으로 붙고, 주지 않으면 표시하지 않는다. **내가 보낸 메시지에만 붙인다** — 받은 메시지에 읽음 표시를 다는 것은 뜻이 없다. |
6426
6472
  | `avatarSrc?` | `string` | — | 아바타 이미지 경로. 없으면 사람 아이콘 자리표시가 선다. **받은 메시지에만 쓰인다** — 보낸 메시지에는 아바타가 서지 않는다(`direction` 참고). |
6427
6473
  | `avatarAlt?` | `string` | — | 아바타 대체 텍스트 |
6474
+ | `showAvatar?` | `boolean` | `true` | 받은 메시지에 아바타 칸을 세울지. 끄면 **칸 자체가 빠져** 말풍선이 행 왼쪽 끝에서 시작한다. 챗봇 답변처럼 **말하는 상대가 하나뿐인 대화**에서 끈다 — 얼굴로 가를 사람이 없으니 칸이 폭만 먹는다. 여러 사람이 말하는 대화에서는 켜 둔다. **한 대화 안에서는 모든 받은 메시지에 같은 값을 준다** — 섞으면 말풍선 시작선이 묶음마다 44px 씩 어긋난다. 끈 대화에서 `continued` 는 이름만 숨긴다(비워 둘 아바타 자리가 없다). 보낸 메시지에는 원래 아바타가 없으므로 영향이 없다. |
6428
6475
  | `continued?` | `boolean` | `false` | 같은 사람이 이어서 말한 묶음인지. 켜면 **아바타와 `sender` 를 그리지 않는다** — 말풍선이 위 묶음과 같은 선에서 시작해 한 사람의 말로 이어 읽힌다. 받은 메시지에서는 아바타 자리를 비워 둔 채 남긴다 — 안 남기면 말풍선이 44px 왼쪽으로 밀려 위 묶음과 어긋난다. 보낸 메시지는 아바타가 없으므로 이름만 숨긴다. `time`·`readStatus` 는 그대로 나온다. 시간이 벌어져 묶음을 나눴어도 얼굴은 한 번만 보이면 되고 시각은 묶음마다 필요하기 때문이다. **얼마나 벌어져야 묶음을 나눌지는 앱이 정한다** — 30분이든 하루든 대화의 성격에 달렸으므로 컴포넌트는 기준을 갖지 않고 스위치만 연다. |
6429
6476
  | `variant?` | `SChatMessageVariant` | `'default'` | 말풍선 종류. `template` 은 미리 등록된 서식으로, `admin` 은 관리자·시스템이 보낸 메시지임을 각각 다른 면으로 드러낸다. 면 색은 토큰이 정하므로 여기서 색을 정하지 않는다. **보낸 메시지(`direction="outgoing"`)에만 쓰인다** — 받은 메시지에는 넘겨도 그리지 않는다. 셋 다 사업자 쪽이 보내는 것이라 받은 쪽에는 뜻이 없다. 앱이 메시지를 한 번만 매핑해 방향과 무관하게 넘길 수 있도록 막지 않고 무시한다. |
6430
6477
  | `actions?` | `ReactNode` | — | 시각 아래에 붙는 동작 슬롯. 없으면 그 줄이 통째로 빠진다. 「상담원 연결」처럼 **이 메시지에 이어서 할 수 있는 동작**이 오는 자리다. 무엇을 놓을지는 앱이 정하고, 메시지는 줄로 세우고 방향에 맞춰 정렬하는 것까지만 한다. 시각과 같은 선에서 시작한다 — 말풍선 끝에서 한 칸 안이다. |
@@ -6518,6 +6565,7 @@ export const CHAT_MESSAGE_VARIANTS = ['default', 'template', 'admin'] as const;
6518
6565
  | `value?` | `SCheckboxValue` | `false` | 선택 상태. boolean(단일) / 배열(그룹) / null(indeterminate) |
6519
6566
  | `val?` | `unknown` | — | 배열(그룹) 모드에서 이 체크박스의 값 |
6520
6567
  | `label?` | `string` | `''` | 라벨 |
6568
+ | `ariaLabel?` | `string` | — | 스크린리더용 접근성 레이블. **`label` 이 없을 때만 준다** — 표의 행 선택처럼 라벨 글자 없이 서는 체크박스는 이것이 없으면 "체크박스" 로만 읽힌다. `label` 이 있으면 그 글자가 이미 이름이 된다. 둘 다 주면 이 값이 화면의 라벨을 덮어써 보이는 글자와 읽히는 이름이 갈린다. |
6521
6569
  | `disabled?` | `boolean` | `false` | 비활성 |
6522
6570
  | `inverse?` | `boolean` | `false` | 어두운 배경용 흰색 스타일 |
6523
6571
  | `className?` | `string` | — | |
@@ -7059,7 +7107,7 @@ export interface SChipFilterDatePreset {
7059
7107
  | `disabledChips?` | `boolean[] \| ((value: string) => boolean)` | `[]` | 칩별 비활성 (배열 또는 판별 함수) |
7060
7108
  | `size?` | `SChipInputSize` | `'sm'` | |
7061
7109
  | `disabled?` | `boolean` | `false` | |
7062
- | `placeholder?` | `string` | `'태그 입력 (Enter로 등록 / 콤마로 구분 / 띄어쓰기 불가)'` | |
7110
+ | `placeholder?` | `string` | — | 칩이 없을 때 보이는 안내. 주지 않으면 확정 방법을 안내하는 문구가 선다 — `separators` 에 콤마가 있으면 `태그 입력 (Enter·콤마로 등록)`, 없으면 `태그 입력 (Enter로 등록)`. 직접 줄 때도 **실제 확정 방법과 맞춘다** — 콤마를 안내하면서 `separators` 를 주지 않으면 콤마가 글자로 들어가 칩 하나에 값 여러 개가 붙는다. |
7063
7111
  | `name?` | `string` | — | |
7064
7112
  | `rules?` | `Rule[]` | — | |
7065
7113
  | `error?` | `boolean` | — | |
@@ -7072,6 +7120,9 @@ export interface SChipFilterDatePreset {
7072
7120
  | `recommendedItems?` | `string[]` | `[]` | 추천 항목 (입력값 없을 때) |
7073
7121
  | `loadingRecommendedItems?` | `boolean` | `false` | |
7074
7122
  | `dropdownMinWidth?` | `number \| string` | `200` | 드롭다운 최소 너비 (숫자=px) |
7123
+ | `commitOnBlur?` | `boolean` | `false` | 입력 칸을 벗어날 때(blur) 쳐 둔 글자를 칩으로 확정할지. 끄면(기본) 친 글자는 칸에 남은 채로 확정되지 않아, 저장하면 빠진다. 이메일 받는 사람처럼 **친 글자가 곧 넣으려는 값인 칸**에서 켠다. 자동완성 후보에서 고르게 하는 칸이라면 끈다 — 덜 친 검색어가 칩으로 굳는다. |
7124
+ | `separators?` | `string[]` | — | 칩을 나누는 구분자 (예: `[',', '\n']`). 입력에 구분자가 들어오면 그 앞까지를 칩으로 확정한다. **붙여 넣기에도 적용된다** — 여러 줄·콤마로 이어진 목록을 붙여 넣으면 조각마다 칩이 되고, 마지막 조각까지 모두 확정한다. 앞뒤 공백은 지우고 빈 조각은 버린다. `maxCount` 를 넘는 조각은 버린다. 주지 않으면 Enter 로만 확정한다. |
7125
+ | `ariaLabel?` | `string` | — | 스크린리더용 접근성 레이블. **`label` 이 없을 때만 준다** — 라벨 글자 없이 서는 칸은 이것이 없으면 이름 없이 읽힌다. `label` 이 있으면 그 글자가 이미 이름이 된다. |
7075
7126
  | `label?` | `string` | — | |
7076
7127
  | `labelPosition?` | `SFieldLabelPosition` | — | 레이블 위치 — `top` 이면 라벨이 컨트롤 위에 선다 (SField 로 그대로 넘어간다). |
7077
7128
  | `subLabel?` | `string` | — | 보조 레이블 — 레이블 오른쪽 같은 줄에 붙는다 (SField 로 그대로 넘어간다). |
@@ -7935,6 +7986,8 @@ export interface SDropdownButtonItem {
7935
7986
  | `bubbleMenu` | `SEditorToolbarItem[]` | — | 선택 영역 위에 뜨는 서식 판 — 편집할 수 있으면 언제나 뜬다 (SEditor 주석 참고) |
7936
7987
  | `editorClassName?` | `string` | — | |
7937
7988
  | `editorStyle?` | `CSSProperties` | — | |
7989
+ | `controlId` | `string` | — | 편집 영역(contenteditable)의 id — SField 레이블이 이 id 로 본문을 가리킨다 |
7990
+ | `labelled` | `boolean` | — | SField 레이블이 그려졌는지. 그려졌을 때만 본문을 레이블 id 로 잇는다 |
7938
7991
 
7939
7992
  #### Events
7940
7993
 
@@ -8218,6 +8271,7 @@ export interface SExpansionItemRenderState {
8218
8271
  |------|------|---------|-------------|
8219
8272
  | `name?` | `string` | — | 폼 연동용 name 속성 |
8220
8273
  | `label?` | `string` | `''` | 레이블 텍스트 |
8274
+ | `controlId?` | `string` | — | 레이블이 가리킬 컨트롤의 `id`. 주면 레이블 글자가 `<label for>` 로 그 컨트롤에 이어져, **스크린리더가 레이블을 컨트롤 이름으로 읽고 레이블을 누르면 컨트롤에 포커스가 간다.** DS 입력 컴포넌트는 알아서 넘긴다 — `SField` 로 직접 컨트롤을 감쌀 때만 준다. 컨트롤이 `input`·`textarea`·`select`·`button` 이 아니면(`role` 로 만든 요소) `<label for>` 가 닿지 않으므로, 컨트롤에 `aria-labelledby={fieldLabelId(controlId)}` 를 직접 건다. |
8221
8275
  | `subLabel?` | `string` | `''` | 보조 레이블. 레이블 오른쪽 같은 줄에 붙는다 (예: `(선택)`, `최대 50자`). 레이블보다 한 단계 약하게 보이도록 기본 타이포(body/sm/medium)·`fg.secondary` 색을 쓴다. 레이블 폭이 모자라면 레이블이 먼저 말줄임되고 보조 레이블은 그대로 남는다. `label` 이 없으면(`addonLabel` 만 있을 때 포함) 그려지지 않는다. |
8222
8276
  | `icon?` | `SIconName` | — | 레이블 영역 아이콘명 (크기는 size 토큰 16px 고정) |
8223
8277
  | `iconColor?` | `SColor` | — | 레이블 영역 아이콘 색상. 미지정 시 currentColor 상속 |
@@ -8300,6 +8354,7 @@ export type SFieldStatus = 'default' | 'pass' | 'error';
8300
8354
  - [SNumberInput](../SNumberInput)
8301
8355
  - [SSearchInput](../SSearchInput)
8302
8356
  - [SSelect](../SSelect)
8357
+ - [SSlider](../SSlider)
8303
8358
  - [STextarea](../STextarea)
8304
8359
  - [STimePicker](../STimePicker)
8305
8360
  - [STimeRangePicker](../STimeRangePicker)
@@ -9649,7 +9704,7 @@ export type SListPadding = 'default' | 'none';
9649
9704
  | `trailing?` | `SListItemSlot` | — | 타이틀 뒤에 표시할 태그/콘텐츠 |
9650
9705
  | `bordered?` | `boolean` | `false` | 외곽 테두리 사용 여부 |
9651
9706
  | `selected?` | `boolean` | `false` | 선택 상태 여부. `clickable` 이 false 면 무시된다 |
9652
- | `clickable?` | `boolean` | `false` | 클릭 가능 상태 여부. hover·selected·chevron 등 인터랙션 표현이 여기에 딸린다. `SExpansionList` 안에서는 기본으로 켜진다 — 표시 전용이면 `false` 를 준다. |
9707
+ | `clickable?` | `boolean` | `false` | 클릭 가능 상태 여부. hover·selected·chevron 등 인터랙션 표현이 여기에 딸린다. `SExpansionList` 안에서는 기본으로 켜진다 — 표시 전용이면 `false` 를 준다. **`onClick` 까지 주면 키보드로도 누를 수 있다** — 항목이 `role="button"` 으로 Tab 순서에 들고, Enter·Space 가 `onClick` 을 부른다. 앱이 `role`·`tabIndex` 를 직접 주면 그 값이 우선한다. `onClick` 이 없으면 눌러도 일어날 일이 없으므로 Tab 순서에 넣지 않는다. |
9653
9708
  | `interaction?` | `SListItemInteraction` | — | hover/selected 상태에서 적용할 인터랙션 preset |
9654
9709
  | `accentStripe?` | `boolean` | `false` | 아이템 왼쪽 accent stripe 표시 여부 |
9655
9710
  | `dense?` | `boolean` | `false` | 조밀한 높이 사용 여부 |
@@ -11424,6 +11479,89 @@ export const SKELETON_ANIMATIONS = ['wave', 'pulse', 'fade', 'none'] as const;
11424
11479
 
11425
11480
  ---
11426
11481
 
11482
+ # SSlider
11483
+
11484
+ > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
11485
+
11486
+ ### SSlider
11487
+
11488
+ #### Props
11489
+
11490
+ | Prop | Type | Default | Description |
11491
+ |------|------|---------|-------------|
11492
+ | `value?` | `V` | — | 값 (제어). **`number` 면 단일, `[시작, 끝]` 이면 범위 슬라이더**가 된다 — 손잡이 개수와 `onValueChange` 가 돌려주는 모양이 이 값을 따른다. 범위는 두 손잡이가 서로를 넘어가지 않는다. |
11493
+ | `min?` | `number` | `0` | 최솟값 |
11494
+ | `max?` | `number` | `100` | 최댓값 |
11495
+ | `step?` | `number` | `1` | 한 칸 크기. 값은 `min` 에서 이 간격으로만 움직인다 |
11496
+ | `marks?` | `boolean \| SSliderMark[]` | — | 눈금. `true` 면 `step` 마다 점을 찍고, 배열이면 그 위치에만 찍는다(라벨을 함께 줄 수 있다). 고를 수 있는 값이 몇 개 안 될 때 어디서 멈추는지 보여 주는 용도다 — `step` 이 촘촘하면 `true` 대신 배열로 대표 위치만 준다. |
11497
+ | `showValue?` | `SSliderShowValue` | `'drag'` | 값 말풍선 표시 시점. - `drag` — 드래그·키보드 조작 중, 손잡이에 마우스를 올렸을 때만 보인다 - `always` — 늘 보인다. 값을 숫자로 따로 보여 주지 않는 자리에서 쓴다 - `none` — 보이지 않는다. 옆에 값을 따로 적어 둘 때 쓴다 `none` 이 아니면 말풍선 자리만큼 위쪽 공간을 미리 잡아 두어, 나타날 때 레이아웃이 밀리지 않는다. |
11498
+ | `formatValue?` | `(value: number) => string` | `String` | 말풍선·스크린리더에 읽히는 값 표기 (예: `v => \`${v}%\``) |
11499
+ | `size?` | `SFieldSize` | `'sm'` | 크기 — 다른 필드와 나란히 놓일 때 라벨과 높이를 맞추는 등급이다. 슬라이더 모양 자체는 같다. |
11500
+ | `rules?` | `Rule[]` | — | 유효성 규칙 — 조작이 끝날 때(`onValueCommit` 시점)와 SForm 제출 시 검증 |
11501
+ | `name?` | `string` | — | 폼 연동용 name |
11502
+ | `status?` | `SFieldStatus` | — | 필드 상태 ('default' | 'pass' | 'error') |
11503
+ | `label?` | `string` | — | 레이블 텍스트. 손잡이의 접근성 이름으로도 쓰인다 |
11504
+ | `labelPosition?` | `SFieldLabelPosition` | — | 레이블 위치 — `top` 이면 라벨이 컨트롤 위에 선다 (SField 로 그대로 넘어간다). |
11505
+ | `subLabel?` | `string` | — | 보조 레이블 — 레이블 오른쪽 같은 줄에 붙는다 (SField 로 그대로 넘어간다). |
11506
+ | `labelWidth?` | `number \| string` | — | 레이블 너비 (숫자=px) |
11507
+ | `icon?` | `SIconName` | — | 레이블 영역 아이콘 |
11508
+ | `iconColor?` | `SColor` | — | 레이블 영역 아이콘 색상 |
11509
+ | `labelTooltip?` | `string` | — | 레이블 툴팁 텍스트 |
11510
+ | `labelTooltipProps?` | `Partial<STooltipProps>` | — | 레이블 툴팁 상세 옵션 |
11511
+ | `hint?` | `string` | — | 하단 힌트 |
11512
+ | `error?` | `boolean` | — | 에러 상태 |
11513
+ | `errorMessage?` | `string` | — | 에러 메시지 |
11514
+ | `width?` | `SFieldWidth` | — | 컨트롤 너비 — 폭 등급 · 숫자=px · CSS 길이. 기본은 부모 폭을 채운다. |
11515
+ | `disabled?` | `boolean` | `false` | 비활성 — 조작·포커스가 모두 막힌다 |
11516
+ | `readOnly?` | `boolean` | `false` | 읽기 전용 — 값은 그대로 보이고 키보드 포커스는 받지만 값이 바뀌지 않는다 |
11517
+ | `className?` | `string` | — | |
11518
+ | `style?` | `CSSProperties` | — | |
11519
+
11520
+ #### Events
11521
+
11522
+ | Event | Type | Description |
11523
+ |-------|------|-------------|
11524
+ | `onValueChange` | `(value: V) => void` | 값 변경 (sdUpdate) — 드래그 중 매 step 마다 호출된다 |
11525
+ | `onValueCommit` | `(value: V) => void` | 조작이 끝났을 때 한 번 호출된다 (포인터를 놓을 때, 키보드로 한 칸 움직일 때). 값을 서버에 보내거나 목록을 다시 불러오는 등 **매 step 마다 하면 안 되는 일**은 여기서 한다. |
11526
+
11527
+ ## Types
11528
+
11529
+ ### SSliderValue
11530
+
11531
+ ```ts
11532
+ /** 단일 값은 `number`, 범위는 `[시작, 끝]` */
11533
+ export type SSliderValue = number | [number, number];
11534
+ ```
11535
+
11536
+ ### SSliderMark
11537
+
11538
+ ```ts
11539
+ /** 눈금 하나 */
11540
+ export interface SSliderMark {
11541
+ /** 눈금 위치 값 (`min`~`max`) */
11542
+ value: number;
11543
+ /** 눈금 아래 라벨. 없으면 점만 찍힌다 */
11544
+ label?: string;
11545
+ }
11546
+ ```
11547
+
11548
+ ### SSliderShowValue
11549
+
11550
+ ```ts
11551
+ /** 값 말풍선을 언제 보일지 */
11552
+ export type SSliderShowValue = 'none' | 'drag' | 'always';
11553
+ ```
11554
+
11555
+ ## Dependencies
11556
+
11557
+ ### Depends on
11558
+
11559
+ - [SField](../SField)
11560
+
11561
+ ### Graph
11562
+
11563
+ ---
11564
+
11427
11565
  # SSplitter
11428
11566
 
11429
11567
  > 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
package/dist/llms.txt CHANGED
@@ -49,7 +49,7 @@
49
49
  | 분류 | 컴포넌트 |
50
50
  | --- | --- |
51
51
  | **버튼·링크** | `SButton` `SGhostButton` `SMeatballButton`(⋯ 로 '더 보기' 메뉴를 여는 아이콘 버튼) `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
52
- | **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
52
+ | **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `SSlider` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
53
53
  | **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
54
54
  | **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `STreeTable`(행이 부모-자식 계층을 이루는 표 — 펼치고 접는다) `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` `STimeline`(날짜로 묶인 시간순 기록 — 작업 로그·메모 이력) |
55
55
  | **레이아웃** | `SLayout` `SGnb` `SGnbSystem` `SSystemActionButton`(GNB system 패널에 한 줄씩 쌓는 액션 행 — 버튼 고르기는 §3-5) `SAccountListBox`(계정 행을 눌러 뜨는 계정 패널) `SLauncherListBox`(런처 버튼을 눌러 뜨는 서비스 목록) `SPage` `SPageHeader`(페이지 제목 영역 — `SLayout` 안에서 `SPage` 앞에 둔다) `SSectionHeaderCard` `SCard` `SLoginCard`(통합 계정 로그인 화면의 카드) `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
@@ -85,6 +85,7 @@ AI 에이전트는 코드를 생성하기 전에 이 목록을 반드시 지킨
85
85
  | `<input type="text/password/...">` | `SInput` |
86
86
  | `<input type="search">` | `SSearchInput` |
87
87
  | `<input type="number">` | `SNumberInput` |
88
+ | `<input type="range">` | `SSlider` |
88
89
  | `<input type="checkbox">` | `SCheckbox`, `SToggle`, `SSwitch` |
89
90
  | `<input type="radio">` | `SRadio`, `SRadioButton` |
90
91
  | `<input type="file">` | `SFilePicker` |
@@ -177,7 +178,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
177
178
  | --- | --- | --- |
178
179
  | **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SGnbSystem`(GNB 맨 아래 판 · 전폭 상단바 오른쪽 끝) `SPage` `SPageHeader`(`SLayout` 안에서 `SPage` 앞에 둔다) |
179
180
  | **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SChatMessage` `SChatSystemMessage` `SChatInput` `SLoginCard`(유일하게 `SPage` 밖에 선다 — 로그인 화면 자체가 자기 자리다, §4-6) `SForm` `SSplitter` `SScrollArea` `SCalendarBoard` `STable` `STableBar` `STreeTable` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `STimeline` `SCallout` `SContextAlert` `SBarChart` `SLineChart` `SPieChart` `SDonutChart` `STabs` `SStepper` `SPagination` `SDivider` |
180
- | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SMeatballButton` `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` `SSkeleton` |
181
+ | **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SMeatballButton` `SDropdownButton` `SSystemActionButton` `SField` `SInput` `SSearchInput` `SNumberInput` `SSlider` `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` `SSkeleton` |
181
182
  | **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `SLogo` `STextLink` `SChip` |
182
183
  | **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SLauncherListBox`(런처 버튼에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
183
184
 
@@ -538,6 +539,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
538
539
  | 제목·굵게·목록·색 같은 **서식이 남아야 하는** 글을 받는다 | `SEditor` | §3-7-1 |
539
540
  | 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
540
541
  | 숫자(수량·금액)를 받는다 | `SNumberInput` | §3-7-13 |
542
+ | 정해진 범위 안에서 수치 하나·구간을 끌어서 고르게 한다 | `SSlider` | §3-7-16 |
541
543
  | 바코드를 스캔해 받는다 | `SBarcodeInput` | |
542
544
  | 목록에서 하나 고르게 한다 | `SSelect` | §3-7-2 |
543
545
  | 선택지를 항상 펼쳐 두고 하나 고르게 한다 | `SRadioGroup` | §3-7-2 |
@@ -1910,6 +1912,16 @@ const [from, setFrom] = useState<string | null>(null);
1910
1912
 
1911
1913
  직접 쓰는 경우는 하나뿐이다 — **디자인 시스템에 없는 컨트롤**에 다른 필드와 똑같은 라벨·필수·에러 모양을 붙일 때.
1912
1914
 
1915
+ - **라벨은 `controlId` 로 컨트롤에 잇는다.** 컨트롤에 `id` 를 주고 같은 값을 `controlId` 로 넘기면 라벨이 그 컨트롤의 이름이 되고, 라벨을 누르면 컨트롤에 포커스가 간다. 컨트롤이 `input`·`textarea`·`select`·`button` 이 아니라 `role` 로 만든 요소면 `aria-labelledby={fieldLabelId(id)}` 를 직접 건다.
1916
+ - **라벨이 보이는 입력에 `aria-label` 을 다시 적지 않는다.** DS 입력 컴포넌트는 `label` 을 알아서 컨트롤에 잇는다. 화면의 라벨과 읽히는 이름을 두 번 적으면 어긋난다. `aria-label`(`SCheckbox`·`SChipInput` 은 `ariaLabel`)은 **라벨 글자 없이 서는 컨트롤**에만 준다 — 표의 행 선택 체크박스처럼.
1917
+
1918
+ ```tsx
1919
+ ✅ <SField label="색상" controlId={id}><input id={id} type="color" /></SField>
1920
+ ✅ <STextarea label="그룹명" value={name} onValueChange={setName} /> {/* 이름은 label 이 된다 */}
1921
+ ❌ <STextarea label="그룹명" aria-label="그룹명" … /> {/* 이름을 두 번 적는다 */}
1922
+ ❌ <SField label="색상"><input type="color" aria-label="색상" /></SField> {/* 라벨을 눌러도 포커스가 안 간다 */}
1923
+ ```
1924
+
1913
1925
  #### 3-7-6. 여러 건을 나열하는 넷 — STable vs SList vs SDraggableList vs STimeline
1914
1926
 
1915
1927
  | 판별 | 사용 |
@@ -1926,6 +1938,7 @@ const [from, setFrom] = useState<string | null>(null);
1926
1938
  - **`SList` 의 자식은 `SListItem` 을 권장한다.** 다른 자식도 그대로 렌더되지만, 펼치는 항목은 `SExpansionList` + `SExpansionItem` 이, 끌어서 순서를 바꾸는 항목은 `SDraggableList` + `SDraggableItem` 이 여닫힘·정렬 동작까지 함께 관리하므로 그쪽을 쓴다 (§3-7-7).
1927
1939
  - **항목 사이 구분선은 리스트가 알아서 그린다.** `SListItem`·`SExpansionItem`·`SDraggableItem` 셋 다 스스로 구분선을 그리지 않는다. `SList`·`SExpansionList`·`SDraggableList` 가 자식 **사이에** 구분선을 넣으므로 아이템에 `border-b` 를 직접 붙이지 않고, 켜는 prop 도 따로 없다. 마지막 항목 아래에는 선이 남지 않는다. 테두리형(`bordered`)은 테두리가 구분 역할을 하므로 리스트가 구분선을 빼고, `gap` 으로 띄운다 — `gap` 을 준 목록에도 구분선은 들어가지 않는다.
1928
1940
  - **`SListItem` 은 기본이 표시 전용이다.** 눌러서 이동·선택하게 하려면 `clickable` 을 준다 — hover·`selected`·`interaction="chevron"` 표현이 전부 여기에 딸려 있어서, `clickable` 없이 `selected` 만 주면 선택 표시가 나오지 않는다. `SExpansionList` 는 자식 아이템을 알아서 클릭 가능하게 만들어 이 함정을 막아 준다 — 선택 상태 자체는 앱이 든다 (§3-7-7).
1941
+ - **`clickable` 에 `onClick` 까지 주면 키보드로도 눌린다.** 항목이 버튼으로 Tab 순서에 들고 Enter·Space 가 `onClick` 을 부른다. `role`·`tabIndex`·키 처리를 직접 붙이거나, 그것을 붙인 `div` 로 항목을 감싸지 않는다.
1929
1942
 
1930
1943
  ```tsx
1931
1944
  ✅ <SList><SListItem title="일반 문의" />…</SList> {/* 줄로 구분되는 목록 — 구분선은 자동 */}
@@ -1934,6 +1947,7 @@ const [from, setFrom] = useState<string | null>(null);
1934
1947
  ❌ <SList><SListItem title="일반 문의" className="border-b" />…</SList> {/* 구분선을 직접 붙이지 않는다 */}
1935
1948
  ❌ <SList><><SListItem title="일반 문의" /><SListItem title="결제 문의" /></></SList> {/* Fragment 로 묶으면 그 안쪽은 구분되지 않는다 */}
1936
1949
  ❌ <SListItem title="일반 문의" selected /> {/* clickable 없으면 선택 표시가 안 나온다 */}
1950
+ ❌ <div role="button" tabIndex={0} onKeyDown={…}><SListItem title="일반 문의" clickable /></div> {/* 키보드 조작은 항목이 한다 */}
1937
1951
  ✅ <STimeline groups={[{ heading: '2026-10-07', entries: [{ key: id, time: '14:32', content: <Memo … /> }] }]} />
1938
1952
  ❌ <div className="grid grid-cols-[52px_8px_1fr]"><span className="rounded-full …" />…</div> {/* 타임라인 레일을 직접 그리지 않는다 */}
1939
1953
  ```
@@ -2223,6 +2237,30 @@ const [selectedId, setSelectedId] = useState<string>();
2223
2237
  ❌ <SSkeleton className="bg-grey-20" /> {/* 색은 토큰이 넣는다 — 어두운 면이면 mode="dark" */}
2224
2238
  ```
2225
2239
 
2240
+ #### 3-7-16. 숫자 둘 — SNumberInput vs SSlider
2241
+
2242
+ | 판별 | 사용 |
2243
+ | --- | --- |
2244
+ | **정확한 값**이 중요하다 (수량·금액·우선순위) | `SNumberInput` |
2245
+ | 범위가 정해져 있고 **대략의 위치**를 고르면 된다 (할인율·가중치·가격대 필터) | `SSlider` |
2246
+ | 범위의 **양 끝**을 함께 고른다 | `SSlider` 에 `value={[시작, 끝]}` |
2247
+
2248
+ - **단일·범위는 `value` 의 모양으로 정한다.** `number` 면 손잡이 하나, `[시작, 끝]` 이면 둘이다. 따로 켜는 prop 이 없다.
2249
+ - **서버 요청·목록 다시 불러오기는 `onValueCommit` 에서 한다.** `onValueChange` 는 끄는 동안 step 마다 불린다.
2250
+ - **값을 옆에 따로 적으면 `showValue="none"`, 아니면 기본(`drag`)이나 `always`.** 두 손잡이가 가까이 모일 수 있는 범위에는 `always` 를 쓰지 않는다 — 말풍선이 겹친다.
2251
+ - **`marks` 는 고를 수 있는 값이 몇 개 안 될 때만 `true` 로 준다.** step 이 촘촘하면 대표 위치만 배열로 준다.
2252
+ - **라벨을 준다.** `label` 이 손잡이의 접근성 이름이 된다.
2253
+ - 트랙·손잡이 색과 크기는 컴포넌트가 넣는다. 직접 주지 않는다.
2254
+
2255
+ ```tsx
2256
+ ✅ <SSlider label="할인율" value={rate} onValueChange={setRate} formatValue={v => `${v}%`} />
2257
+ ✅ <SSlider label="가격대" min={0} max={100000} step={1000} value={range}
2258
+ onValueChange={setRange} onValueCommit={refetch} />
2259
+
2260
+ ❌ <SSlider label="주문 수량" min={1} max={9999} … /> {/* 정확한 값을 받는 자리 — SNumberInput */}
2261
+ ❌ <SSlider marks min={0} max={100} … /> {/* step 1 마다 점 100개 — 대표 위치만 배열로 */}
2262
+ ```
2263
+
2226
2264
  ---
2227
2265
 
2228
2266
  ## 4. 페이지 레시피 — 표준 골격
@@ -4476,10 +4514,12 @@ export default function StoreSetupPage() {
4476
4514
  - **여백은 영역마다 다르다** (§2-2 상담 콘솔 예). 목록·템플릿은 같은 항목의 반복이라 목록 컴포넌트가 자기 여백을 갖고, 머리 줄도 목록 항목의 좌우 여백(`--cmp-listItem-paddingX`)에 맞춘다. 대화는 말풍선·파일·시스템 안내가 섞여 24 다 — 머리 줄·스크롤 안쪽 상자가 24 를 쓴다. 입력 영역은 `SDivider` 로 대화와 나누고, 좌우는 메시지와 끝을 맞춰 24, 위아래는 16 이다.
4477
4515
  - **대화는 최신이 바닥이다.** 쌓기 상자에 `min-h-full justify-end` 로 메시지가 적어도 바닥에 붙이고, 처음 열 때와 메시지가 늘 때 스크롤 영역(`SScrollArea` 의 ref)을 맨 아래로 내린다. 입력창은 스크롤 밖 바닥에 고정한다.
4478
4516
  - **메시지 한 건 = 한 사람이 이어 말한 묶음이다.** 같은 사람이 같은 시각에 이어 보낸 글·파일은 `SChatMessage` 하나로 묶어 `messages`·`attachments` 로 넘긴다 — 말풍선마다 컴포넌트를 따로 세우지 않는다. 파일은 `attachments` 로만 넘긴다 (`messages` 로 넘기면 칸이 두 겹이 된다).
4517
+ - **아바타 칸은 말하는 상대가 둘 이상일 때만 둔다.** 상담 콘솔처럼 고객·상담사가 오가면 켜 둔다(기본). 챗봇 답변처럼 받는 쪽이 하나뿐인 대화는 받은 메시지 전부에 `showAvatar={false}` 를 준다 — 대화 안에서 섞지 않는다. 칸을 CSS 로 숨기지 않는다.
4479
4518
  - **줄 사이 간격은 토큰이 정한다** — 메시지 묶음 사이는 `--cmp-chatMessage-group-message-gap`, 시스템 안내(`SChatSystemMessage`) 앞뒤는 그보다 넓은 `--cmp-chatMessage-group-system-gap`. 숫자로 적지 않는다.
4480
4519
  - **날짜가 바뀌는 자리에는 날짜 구분선을 끼운다** — 가로 `SDivider` 두 개 사이에 날짜(`typo-body-sm-default` + `text-fg-tertiary`). 일어난 사실이 아니라 흐름의 경계라 시스템 안내 알약(`SChatSystemMessage`)을 쓰지 않는다. **날짜는 `2026년 10월 6일 화요일`** 로 쓴다 — 말풍선 시각과 같은 대화 예외다(§1-4). `toLocaleDateString()` 은 로케일에 따라 결과가 바뀌므로 쓰지 않고 연·월·일·요일을 직접 이어 붙인다. 가로 구분선은 기본이 전체 폭이라 한 줄에 둘을 놓을 때는 `className="w-auto flex-1"` 로 남은 폭을 나눠 갖게 한다. 앞뒤 간격은 시스템 안내와 같은 토큰이다. 날짜가 다르면 같은 시각이어도 한 묶음으로 합치지 않는다.
4481
4520
  - 대화의 시각은 `오후 5:52` 로 쓴다 (§1-4 대화 예외). 읽음 표시는 보낸 메시지에만, 템플릿으로 보낸 글은 `variant="template"` 로 면을 나눈다.
4482
4521
  - **입력 중인 글은 페이지가 상담마다 든다.** 상담을 옮겨도 쓰던 글이 남고, 템플릿 패널이 그 글에 넣을 수 있다. 템플릿은 누르면 **입력창에 넣는다** — 바로 보내지 않고 상담사가 고쳐 보낸다. 보내기 전 첨부는 입력창 위에 `SChatAttachedFile` 로 쌓는다.
4522
+ - **첨부를 받는 대화면 `onAttach` 와 `onPasteFiles` 를 함께 넘긴다** — 붙여넣은 파일·캡처 이미지도 첨부 버튼으로 고른 것과 같은 자리에 쌓는다. 붙여넣기에서 파일을 골라내고 글로 들어가지 않게 막는 것은 입력창이 한다 — `onPaste` 로 직접 처리하지 않는다. 첨부가 쌓여 있으면 `hasAttachments` 를 켠다 — 글 없이 파일만 보낼 수 있고, 이때 `onSend` 에는 빈 문자열이 온다.
4483
4523
  - **상담 종료는 `SChatInput` 의 `footerAction` 이다** — 전송과 층이 다른 동작이라 입력창 아래 보조 슬롯(`STextLink`)에 두고, 실행 전에 `SModal.confirm` 으로 묻는다. 종료된 상담은 입력창을 `disabled` 로 잠그고 종료 링크를 뺀다.
4484
4524
  - 목록의 상태는 `STag` 다 — 색 점(`SBadge`)만으로 찍지 않는다 (§3-1). 템플릿 분류는 `SExpansionList` 루트 하나에 분류(`SExpansionItem`) 안 템플릿(`SListItem`)을 바로 넣는다 (§3-7-7).
4485
4525
 
@@ -4845,6 +4885,10 @@ function Conversation({
4845
4885
  onValueChange={onDraftChange}
4846
4886
  onSend={send}
4847
4887
  onAttach={() => setAttached(current => [...current, `첨부_${current.length + 1}.png`])}
4888
+ // 붙여넣은 파일·캡처 이미지도 첨부 버튼으로 고른 것과 같은 자리에 쌓는다
4889
+ onPasteFiles={files => setAttached(current => [...current, ...files.map(f => f.name)])}
4890
+ // 첨부가 쌓여 있으면 글 없이도 보낼 수 있다
4891
+ hasAttachments={attached.length > 0}
4848
4892
  // 상담 종료는 전송과 층이 다른 동작 — 입력창 아래 보조 액션 슬롯이다
4849
4893
  footerAction={!closed && <STextLink label="상담 종료" onClick={confirmClose} />}
4850
4894
  />
@@ -5612,7 +5656,7 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
5612
5656
  --sys-color-purple-strong
5613
5657
  --sys-color-purple-subtle
5614
5658
 
5615
- ## 3. 컴포넌트 인덱스 (89종)
5659
+ ## 3. 컴포넌트 인덱스 (90종)
5616
5660
 
5617
5661
  **Props 는 이 문서에 없다.** 쓸 컴포넌트를 정했으면 그 컴포넌트의 README 를 읽는다.
5618
5662
 
@@ -5621,4 +5665,4 @@ Tailwind 유틸리티는 아래 스케일에 있는 값만 사용한다. 리터
5621
5665
  예) SButton 을 쓴다면 `dist/components/SButton/README.md`.
5622
5666
  prop 을 추측해서 쓰지 말고 반드시 README 로 확인한다 (분류별 용도는 §1 의 "전체 컴포넌트 인덱스" 참조).
5623
5667
 
5624
- SAccountListBox · SActionModal · SBadge · SBarChart · SBarcodeInput · SButton · SCalendar · SCalendarBoard · SCallout · SCard · SChatAttachedFile · SChatFile · SChatInput · SChatMessage · SChatSystemMessage · SCheckbox · SChip · SChipFilter · SChipInput · SCircleProgress · SConfirmModal · SContextAlert · SDatePicker · SDateRangePicker · SDivider · SDonutChart · SDraggableItem · SDraggableList · SDrawer · SDropdownButton · SEditor · SExpansionItem · SExpansionList · SField · SFilePicker · SForm · SGhostButton · SGnb · SGnbSystem · SGuide · SIcon · SImage · SInput · SKeyValueTable · SLauncherListBox · SLayout · SLineChart · SLinearProgress · SList · SListItem · SLoadingContainer · SLoadingModal · SLoginCard · SLogo · SMeatballButton · SModal · SNumberInput · SPage · SPagination · SPieChart · SPopover · SPopup · SPortal · SRadio · SRadioButton · SRadioCard · SScrollArea · SSearchInput · SSectionHeaderCard · SSelect · SSkeleton · SSplitter · SStepper · SSwitch · SSystemActionButton · STable · STableBar · STabs · STag · STextLink · STextarea · STimePicker · STimeRangePicker · STimeline · SToast · SToggle · STooltip · STree · STreeTable
5668
+ SAccountListBox · SActionModal · SBadge · SBarChart · SBarcodeInput · SButton · SCalendar · SCalendarBoard · SCallout · SCard · SChatAttachedFile · SChatFile · SChatInput · SChatMessage · SChatSystemMessage · SCheckbox · SChip · SChipFilter · SChipInput · SCircleProgress · SConfirmModal · SContextAlert · SDatePicker · SDateRangePicker · SDivider · SDonutChart · SDraggableItem · SDraggableList · SDrawer · SDropdownButton · SEditor · SExpansionItem · SExpansionList · SField · SFilePicker · SForm · SGhostButton · SGnb · SGnbSystem · SGuide · SIcon · SImage · SInput · SKeyValueTable · SLauncherListBox · SLayout · SLineChart · SLinearProgress · SList · SListItem · SLoadingContainer · SLoadingModal · SLoginCard · SLogo · SMeatballButton · SModal · SNumberInput · SPage · SPagination · SPieChart · SPopover · SPopup · SPortal · SRadio · SRadioButton · SRadioCard · SScrollArea · SSearchInput · SSectionHeaderCard · SSelect · SSkeleton · SSlider · SSplitter · SStepper · SSwitch · SSystemActionButton · STable · STableBar · STabs · STag · STextLink · STextarea · STimePicker · STimeRangePicker · STimeline · SToast · SToggle · STooltip · STree · STreeTable
package/dist/styles.css CHANGED
@@ -2604,6 +2604,10 @@
2604
2604
  border-style: var(--tw-border-style);
2605
2605
  border-width: 0px;
2606
2606
  }
2607
+ .border-3 {
2608
+ border-style: var(--tw-border-style);
2609
+ border-width: 3px;
2610
+ }
2607
2611
  .border-\[length\:var\(--cmp-callout-border-width\)\] {
2608
2612
  border-style: var(--tw-border-style);
2609
2613
  border-width: var(--cmp-callout-border-width);
@@ -3415,6 +3419,9 @@
3415
3419
  .px-\(--cmp-chatBubble-paddingX\) {
3416
3420
  padding-inline: var(--cmp-chatBubble-paddingX);
3417
3421
  }
3422
+ .px-\(--cmp-chatInput-padding\) {
3423
+ padding-inline: var(--cmp-chatInput-padding);
3424
+ }
3418
3425
  .px-\(--cmp-chatMessage-bottomButtons-paddingX\) {
3419
3426
  padding-inline: var(--cmp-chatMessage-bottomButtons-paddingX);
3420
3427
  }
@@ -4795,6 +4802,9 @@
4795
4802
  .\[length\:var\(--x\)\] {
4796
4803
  length: var(--x);
4797
4804
  }
4805
+ .\[text-box\:trim-both_cap_alphabetic\] {
4806
+ text-box: trim-both cap alphabetic;
4807
+ }
4798
4808
  .group-hover\:visible {
4799
4809
  &:is(:where(.group):hover *) {
4800
4810
  @media (hover: hover) {
@@ -4923,11 +4933,23 @@
4923
4933
  }
4924
4934
  }
4925
4935
  }
4936
+ .group-hover\/thumb\:opacity-100 {
4937
+ &:is(:where(.group\/thumb):hover *) {
4938
+ @media (hover: hover) {
4939
+ opacity: 100%;
4940
+ }
4941
+ }
4942
+ }
4926
4943
  .group-focus-visible\:bg-\(--cmp-splitter-border-color\) {
4927
4944
  &:is(:where(.group):focus-visible *) {
4928
4945
  background-color: var(--cmp-splitter-border-color);
4929
4946
  }
4930
4947
  }
4948
+ .group-focus-visible\/thumb\:opacity-100 {
4949
+ &:is(:where(.group\/thumb):focus-visible *) {
4950
+ opacity: 100%;
4951
+ }
4952
+ }
4931
4953
  .placeholder\:text-\(--cmp-chipInput-text-placeholder\) {
4932
4954
  &::placeholder {
4933
4955
  color: var(--cmp-chipInput-text-placeholder);
@@ -5517,11 +5539,26 @@
5517
5539
  outline-width: 2px;
5518
5540
  }
5519
5541
  }
5542
+ .focus-visible\:-outline-offset-2 {
5543
+ &:focus-visible {
5544
+ outline-offset: calc(2px * -1);
5545
+ }
5546
+ }
5547
+ .focus-visible\:outline-offset-2 {
5548
+ &:focus-visible {
5549
+ outline-offset: 2px;
5550
+ }
5551
+ }
5520
5552
  .focus-visible\:outline-\(--sys-color-border-accent\) {
5521
5553
  &:focus-visible {
5522
5554
  outline-color: var(--sys-color-border-accent);
5523
5555
  }
5524
5556
  }
5557
+ .focus-visible\:outline-\(--sys-color-field-border-focus\) {
5558
+ &:focus-visible {
5559
+ outline-color: var(--sys-color-field-border-focus);
5560
+ }
5561
+ }
5525
5562
  .focus-visible\:outline-none {
5526
5563
  &:focus-visible {
5527
5564
  --tw-outline-style: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sellmate-design-system-react",
3
- "version": "9.0.0-beta.89",
3
+ "version": "9.0.0-beta.90",
4
4
  "description": "Sellmate Design System — React (TypeScript + Tailwind v4) port",
5
5
  "license": "UNLICENSED",
6
6
  "keywords": [
@@ -90,6 +90,7 @@
90
90
  "@radix-ui/react-dialog": "^1.1.18",
91
91
  "@radix-ui/react-dropdown-menu": "^2.1.19",
92
92
  "@radix-ui/react-popover": "^1.1.18",
93
+ "@radix-ui/react-slider": "^1.4.2",
93
94
  "@radix-ui/react-switch": "^1.3.2",
94
95
  "@radix-ui/react-tooltip": "^1.2.11",
95
96
  "@tiptap/core": "^3.30.2",