sellmate-design-system-react 9.0.0-beta.80 → 9.0.0-beta.83
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 +10 -3
- package/dist/components/SSkeleton/README.md +13 -1
- package/dist/components/SSkeleton/SSkeleton.d.ts +7 -3
- package/dist/components/SSkeleton/index.d.ts +1 -1
- package/dist/components/SSkeleton/skeleton.config.d.ts +8 -6
- package/dist/components/STimeline/README.md +41 -0
- package/dist/components/STimeline/STimeline.d.ts +27 -0
- package/dist/components/STimeline/index.d.ts +1 -0
- package/dist/index.cjs +98 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +97 -16
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +68 -5
- package/dist/llms.txt +14 -6
- package/dist/styles.css +96 -1
- package/dist/theme.css +20 -1
- package/eslint/scale.gen.mjs +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
| **버튼·링크** | `SButton` `SGhostButton` `SMeatballButton`(⋯ 로 '더 보기' 메뉴를 여는 아이콘 버튼) `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
|
|
43
43
|
| **입력 (폼)** | `SForm` `SField` `SInput` `SChatInput`(대화 입력창) `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SRadioCard` `SRadioCardGroup` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
44
44
|
| **날짜·시간** | `SCalendar` `SCalendarBoard`(한 달치 일정 판) `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
45
|
-
| **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
45
|
+
| **표·목록** | `SChatMessage`(대화의 메시지 한 건) `SChatAttachedFile`(입력창 위, 아직 보내지 않은 첨부 한 칸) `SChatFile`(대화 흐름에 선, 보낸 파일 한 칸) `SChatSystemMessage`(대화 흐름 가운데 서는 시스템 안내) `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` `STimeline`(날짜로 묶인 시간순 기록 — 작업 로그·메모 이력) |
|
|
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
48
|
| **차트** | `SBarChart`(막대 그래프 — 항목끼리 크기를 견준다. `stacked` 로 항목 안의 구성까지) `SLineChart`(꺾은선 그래프 — 순서가 있는 항목의 추이를 본다. `area` 로 크기까지, `stacked` 로 구성까지) `SPieChart`(원그래프 — 한 시점의 전체를 몇 개의 몫으로 나눈다) `SDonutChart`(도넛 그래프 — 가운데가 빈 원그래프. 구멍에 글자를 두지 않는다) |
|
|
@@ -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` `SContextAlert` `SBarChart` `SLineChart` `SPieChart` `SDonutChart` `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` `STimeline` `SCallout` `SContextAlert` `SBarChart` `SLineChart` `SPieChart` `SDonutChart` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
171
171
|
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `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` |
|
|
172
172
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `SLogo` `STextLink` `SChip` |
|
|
173
173
|
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SAccountListBox`(계정 행에 붙어 뜬다 — 직접 띄우지 않는다) `SLauncherListBox`(런처 버튼에 붙어 뜬다 — 직접 띄우지 않는다) `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
@@ -563,6 +563,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
563
563
|
| 여러 건을 여러 열로 보여주고 열끼리 비교하게 한다 | `STable` | §3-7-6 |
|
|
564
564
|
| 항목 하나의 속성들을 `라벨: 값` 으로 보여준다 | `SKeyValueTable` | §4-4 |
|
|
565
565
|
| 한 줄로 읽히는 항목을 세로로 나열한다 | `SList` + `SListItem` | §3-7-6 |
|
|
566
|
+
| 시각이 붙은 기록을 날짜별로 묶어 시간순으로 보여준다 (작업 로그·메모 이력) | `STimeline` | §3-7-6 |
|
|
566
567
|
| 대화의 메시지 한 건을 보여준다 | `SChatMessage` | |
|
|
567
568
|
| 대화 중간에 시스템이 알리는 사실을 끼운다 (상담사 배정·대화 종료 등) | `SChatSystemMessage` | 사람이 보낸 말이면 `SChatMessage` |
|
|
568
569
|
| 나열한 항목을 펼쳐 하위 내용을 보여준다 | `SExpansionList` + `SExpansionItem` | §3-7-7 |
|
|
@@ -1898,15 +1899,17 @@ const [from, setFrom] = useState<string | null>(null);
|
|
|
1898
1899
|
|
|
1899
1900
|
직접 쓰는 경우는 하나뿐이다 — **디자인 시스템에 없는 컨트롤**에 다른 필드와 똑같은 라벨·필수·에러 모양을 붙일 때.
|
|
1900
1901
|
|
|
1901
|
-
#### 3-7-6. 여러 건을 나열하는
|
|
1902
|
+
#### 3-7-6. 여러 건을 나열하는 넷 — STable vs SList vs SDraggableList vs STimeline
|
|
1902
1903
|
|
|
1903
1904
|
| 판별 | 사용 |
|
|
1904
1905
|
| --- | --- |
|
|
1905
1906
|
| 열이 둘 이상이고 **열끼리 값을 비교**한다 (정렬·합계·자릿수 맞춤) | `STable` |
|
|
1906
1907
|
| 한 항목이 **한 줄로 읽힌다** (제목 + 보조 텍스트) | `SList` + `SListItem` |
|
|
1907
1908
|
| **순서 자체가 데이터**라 사용자가 끌어서 바꾼다 | `SDraggableList` + `SDraggableItem` |
|
|
1909
|
+
| 항목마다 **시각**이 있고 날짜 머리글로 묶여 위에서 아래로 흐른다 (작업 로그·메모 이력) | `STimeline` |
|
|
1908
1910
|
|
|
1909
1911
|
- **항목 하나의 속성을 나열하는 것은 목록이 아니다.** `라벨: 값` 이 세로로 쌓이는 것은 `SKeyValueTable` 이다 (§4-4).
|
|
1912
|
+
- **`STimeline` 의 레일(점·선)과 머리글·시각 열은 컴포넌트가 그린다.** `groups` 에 데이터만 넘기고, 점·선을 `border-l` 이나 절대 위치 `span` 으로 직접 그리지 않는다 — 선이 묶음 경계와 마지막 줄에서 끊기는 규칙을 컴포넌트가 계산한다. 작성자·편집 버튼 같은 업무 내용은 각 항목의 `content` 에 넣는다. 빈 상태 안내도 `emptyText` 로 넘기고, 불러오는 중에는 `emptyText={null}` 로 빈 안내를 숨긴다.
|
|
1910
1913
|
- `SList` 는 레이아웃만 담당한다. depth 별 단일 펼침이 필요하면 `SExpansionList` 다 (§3-7-7).
|
|
1911
1914
|
- **`SList` 의 자식은 `SListItem` 을 권장한다.** 다른 자식도 그대로 렌더되지만, 펼치는 항목은 `SExpansionList` + `SExpansionItem` 이, 끌어서 순서를 바꾸는 항목은 `SDraggableList` + `SDraggableItem` 이 여닫힘·정렬 동작까지 함께 관리하므로 그쪽을 쓴다 (§3-7-7).
|
|
1912
1915
|
- **항목 사이 구분선은 리스트가 알아서 그린다.** `SListItem`·`SExpansionItem`·`SDraggableItem` 셋 다 스스로 구분선을 그리지 않는다. `SList`·`SExpansionList`·`SDraggableList` 가 자식 **사이에** 구분선을 넣으므로 아이템에 `border-b` 를 직접 붙이지 않고, 켜는 prop 도 따로 없다. 마지막 항목 아래에는 선이 남지 않는다. 테두리형(`bordered`)은 테두리가 구분 역할을 하므로 리스트가 구분선을 빼고, `gap` 으로 띄운다 — `gap` 을 준 목록에도 구분선은 들어가지 않는다.
|
|
@@ -1919,6 +1922,8 @@ const [from, setFrom] = useState<string | null>(null);
|
|
|
1919
1922
|
❌ <SList><SListItem title="일반 문의" className="border-b" />…</SList> {/* 구분선을 직접 붙이지 않는다 */}
|
|
1920
1923
|
❌ <SList><><SListItem title="일반 문의" /><SListItem title="결제 문의" /></></SList> {/* Fragment 로 묶으면 그 안쪽은 구분되지 않는다 */}
|
|
1921
1924
|
❌ <SListItem title="일반 문의" selected /> {/* clickable 없으면 선택 표시가 안 나온다 */}
|
|
1925
|
+
✅ <STimeline groups={[{ heading: '2026-10-07', entries: [{ key: id, time: '14:32', content: <Memo … /> }] }]} />
|
|
1926
|
+
❌ <div className="grid grid-cols-[52px_8px_1fr]"><span className="rounded-full …" />…</div> {/* 타임라인 레일을 직접 그리지 않는다 */}
|
|
1922
1927
|
```
|
|
1923
1928
|
|
|
1924
1929
|
##### 행 안에 컨트롤을 둘 때
|
|
@@ -2169,6 +2174,7 @@ const [selectedId, setSelectedId] = useState<string>();
|
|
|
2169
2174
|
- **실제 레이아웃을 그대로 따라 그린다.** 폼 컨트롤 자리는 `type="input"`·`"button"`·`"checkbox"` 처럼 DS 모양을 쓴다. 해당 컴포넌트 기본 size 의 크기·모서리를 토큰으로 넣으므로 **높이·radius 를 직접 주지 않는다** — 직접 주면 데이터가 왔을 때 레이아웃이 튄다. checkbox·radio·switch 는 크기를 바꾸지 않는다.
|
|
2170
2175
|
- **텍스트 줄은 `type="text"` 를 실제 텍스트와 같은 typo 클래스 안에 둔다.** 줄 높이를 부모 line-height 에서 받으므로 typo 밖에 두면 높이가 어긋난다. `text` 에 `height` 를 주지 않는다 — 높이가 필요하면 `rect` 다.
|
|
2171
2176
|
- **원은 `size` 하나로 준다.** `width`·`height` 를 따로 주면 타원이 된다.
|
|
2177
|
+
- **색은 바탕 밝기로 `mode` 만 고른다.** 흰 면·회색 면 위는 기본(`light`), 어두운 면 위는 `mode="dark"`. 색·투명도·모서리는 `--cmp-skeleton-*` 토큰으로 이미 들어가므로 `background`·`opacity`·`rounded-*` 를 직접 주지 않는다.
|
|
2172
2178
|
- **넓은 영역에는 `pulse` 를 쓰지 않는다.** 블록 전체가 줄었다 커져 레이아웃이 흔들려 보인다. 기본 `wave` 를 쓰고, 한 화면에 아주 많으면 `none`.
|
|
2173
2179
|
- **로딩 중이라는 사실은 감싸는 영역에 `aria-busy` 로 알린다.** `SSkeleton` 자체는 장식이라 보조기기에 숨겨져 있다.
|
|
2174
2180
|
|
|
@@ -2181,6 +2187,7 @@ const [selectedId, setSelectedId] = useState<string>();
|
|
|
2181
2187
|
|
|
2182
2188
|
❌ <SSkeleton type="input" height={40} /> {/* 실제 SInput 과 높이가 달라 데이터가 오면 튄다 */}
|
|
2183
2189
|
❌ <SSkeleton type="text" height={24} /> {/* text 는 세로 절반만 칠한다 — rect 를 쓴다 */}
|
|
2190
|
+
❌ <SSkeleton className="bg-grey-20" /> {/* 색은 토큰이 넣는다 — 어두운 면이면 mode="dark" */}
|
|
2184
2191
|
```
|
|
2185
2192
|
|
|
2186
2193
|
---
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
| Prop | Type | Default | Description |
|
|
10
10
|
|------|------|---------|-------------|
|
|
11
11
|
| `type?` | `SSkeletonType` | `'rect'` | 자리표시 모양 - `rect`: 사각형. 폭은 부모를, 높이는 한 줄 높이를 따른다 — 이미지·카드 영역은 `height` 를 준다 - `circle`: 원형(아바타 등) - `text`: 텍스트 한 줄. 부모 타이포의 line-height 를 따르고 세로로 절반만 칠한다 - `button`·`input`·`tag`·`checkbox`·`radio`·`switch`: 해당 컴포넌트 기본 size 의 크기·모서리 |
|
|
12
|
+
| `mode?` | `SSkeletonMode` | `'light'` | 놓이는 바탕의 밝기 - `light`: 흰 면·회색 면 위. 바탕보다 짙은 반투명 회색 - `dark`: 어두운 면 위. 바탕보다 밝은 반투명 회색 |
|
|
12
13
|
| `animation?` | `SSkeletonAnimation` | `'wave'` | 로딩 애니메이션 - `wave`: 하이라이트 띠가 왼쪽에서 오른쪽으로 지나간다 - `pulse`: 크기가 숨쉬듯 줄었다 커진다. 블록 전체가 움직이므로 아바타처럼 작은 모양에만 쓴다 - `fade`: 투명도가 숨쉬듯 바뀐다 - `none`: 애니메이션 없음 OS 의 동작 줄이기 설정이 켜져 있으면 어떤 값이든 멈춘다. |
|
|
13
14
|
| `animationSpeed?` | `number` | `1500` | 애니메이션 한 주기 (ms) |
|
|
14
15
|
| `square?` | `boolean` | `false` | 모서리를 각지게 |
|
|
15
|
-
| `bordered?` | `boolean` | `false` | 테두리 |
|
|
16
16
|
| `size?` | `string \| number` | — | 가로·세로를 한 번에 지정. `width`·`height` 가 있으면 그쪽이 우선한다. 숫자는 px |
|
|
17
17
|
| `width?` | `string \| number` | — | 폭. 숫자는 px |
|
|
18
18
|
| `height?` | `string \| number` | — | 높이. 숫자는 px |
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
export type SSkeletonType = (typeof SKELETON_TYPES)[number];
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### SSkeletonMode
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export type SSkeletonMode = (typeof SKELETON_MODES)[number];
|
|
32
|
+
```
|
|
33
|
+
|
|
28
34
|
### SSkeletonAnimation
|
|
29
35
|
|
|
30
36
|
```ts
|
|
@@ -47,6 +53,12 @@ export const SKELETON_TYPES = [
|
|
|
47
53
|
] as const;
|
|
48
54
|
```
|
|
49
55
|
|
|
56
|
+
### SKELETON_MODES
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
export const SKELETON_MODES = ['light', 'dark'] as const;
|
|
60
|
+
```
|
|
61
|
+
|
|
50
62
|
### SKELETON_ANIMATIONS
|
|
51
63
|
|
|
52
64
|
```ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type HTMLAttributes } from 'react';
|
|
2
|
-
import { type SSkeletonAnimation, type SSkeletonType } from './skeleton.config';
|
|
2
|
+
import { type SSkeletonAnimation, type SSkeletonMode, type SSkeletonType } from './skeleton.config';
|
|
3
3
|
export interface SSkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
4
|
/**
|
|
5
5
|
* 자리표시 모양
|
|
@@ -9,6 +9,12 @@ export interface SSkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
9
9
|
* - `button`·`input`·`tag`·`checkbox`·`radio`·`switch`: 해당 컴포넌트 기본 size 의 크기·모서리
|
|
10
10
|
*/
|
|
11
11
|
type?: SSkeletonType;
|
|
12
|
+
/**
|
|
13
|
+
* 놓이는 바탕의 밝기
|
|
14
|
+
* - `light`: 흰 면·회색 면 위. 바탕보다 짙은 반투명 회색
|
|
15
|
+
* - `dark`: 어두운 면 위. 바탕보다 밝은 반투명 회색
|
|
16
|
+
*/
|
|
17
|
+
mode?: SSkeletonMode;
|
|
12
18
|
/**
|
|
13
19
|
* 로딩 애니메이션
|
|
14
20
|
* - `wave`: 하이라이트 띠가 왼쪽에서 오른쪽으로 지나간다
|
|
@@ -23,8 +29,6 @@ export interface SSkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
23
29
|
animationSpeed?: number;
|
|
24
30
|
/** 모서리를 각지게 */
|
|
25
31
|
square?: boolean;
|
|
26
|
-
/** 테두리 */
|
|
27
|
-
bordered?: boolean;
|
|
28
32
|
/** 가로·세로를 한 번에 지정. `width`·`height` 가 있으면 그쪽이 우선한다. 숫자는 px */
|
|
29
33
|
size?: string | number;
|
|
30
34
|
/** 폭. 숫자는 px */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { SSkeleton, type SSkeletonProps } from './SSkeleton';
|
|
2
|
-
export { SKELETON_TYPES, SKELETON_ANIMATIONS, SKELETON_TYPE_CONFIG, type SSkeletonType, type SSkeletonAnimation, } from './skeleton.config';
|
|
2
|
+
export { SKELETON_TYPES, SKELETON_ANIMATIONS, SKELETON_MODES, SKELETON_TYPE_CONFIG, type SSkeletonType, type SSkeletonAnimation, type SSkeletonMode, } from './skeleton.config';
|
|
@@ -2,6 +2,8 @@ export declare const SKELETON_TYPES: readonly ["rect", "circle", "text", "button
|
|
|
2
2
|
export type SSkeletonType = (typeof SKELETON_TYPES)[number];
|
|
3
3
|
export declare const SKELETON_ANIMATIONS: readonly ["wave", "pulse", "fade", "none"];
|
|
4
4
|
export type SSkeletonAnimation = (typeof SKELETON_ANIMATIONS)[number];
|
|
5
|
+
export declare const SKELETON_MODES: readonly ["light", "dark"];
|
|
6
|
+
export type SSkeletonMode = (typeof SKELETON_MODES)[number];
|
|
5
7
|
/**
|
|
6
8
|
* type 별 기본 크기·모서리. 값이 없는 축은 내용(공백 한 글자)과 부모 폭을 따른다.
|
|
7
9
|
* DS 모양(button·input…)은 해당 컴포넌트의 **기본 size** 토큰을 그대로 가리켜
|
|
@@ -12,11 +14,11 @@ export declare const SKELETON_TYPE_CONFIG: Record<SSkeletonType, {
|
|
|
12
14
|
height?: string;
|
|
13
15
|
radius: string;
|
|
14
16
|
}>;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* mode 별 색. 배경색을 반투명(`cmp-skeleton-opacity`)으로 깔아 흰 면·회색 면 어디서든 바탕보다 한 단계 짙게(dark 는 밝게) 보인다.
|
|
19
|
+
* 밝은 면(light)에는 어두운 색(`bg-dark`)을, 어두운 면(dark)에는 밝은 색(`bg-light`)을 쓴다 — Quasar 의 검정·흰색 반투명과 같은 축.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SKELETON_COLORS: Record<SSkeletonMode, {
|
|
17
22
|
bg: string;
|
|
18
|
-
/** Quasar rgba(0,0,0,.05) 테두리 — 배경보다 한 단계 짙게 */
|
|
19
|
-
border: string;
|
|
20
|
-
/** Quasar rgba(255,255,255,.5) 하이라이트 띠 */
|
|
21
23
|
wave: string;
|
|
22
|
-
}
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# STimeline
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### STimeline
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `groups` | `STimelineGroup[]` | — | 머리글로 묶은 항목 목록. 순서 그대로 그린다 |
|
|
12
|
+
| `emptyText?` | `ReactNode` | `'내역이 없습니다.'` | 항목이 하나도 없을 때 가운데 띄우는 안내. 불러오는 중에는 `null` 을 넘겨 빈 안내가 먼저 깜빡이지 않게 한다. |
|
|
13
|
+
|
|
14
|
+
## Types
|
|
15
|
+
|
|
16
|
+
### STimelineGroup
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
export interface STimelineGroup {
|
|
20
|
+
/** React key. 생략하면 순서로 대신한다 */
|
|
21
|
+
key?: Key;
|
|
22
|
+
/** 묶음 머리글 (예: 날짜) */
|
|
23
|
+
heading: ReactNode;
|
|
24
|
+
/** 머리글 아래 항목. 비어 있으면 머리글째 그리지 않는다 */
|
|
25
|
+
entries: STimelineEntry[];
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### STimelineEntry
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
export interface STimelineEntry {
|
|
33
|
+
/** React key — 묶음 안에서 유일해야 한다 */
|
|
34
|
+
key: Key;
|
|
35
|
+
/** 레일 왼쪽 열에 오른쪽 정렬로 서는 시각 (예: `14:32`) */
|
|
36
|
+
time?: ReactNode;
|
|
37
|
+
/** 레일 오른쪽 본문. 타이포·색은 감싸는 쪽을 따른다 — 업무 내용은 소비 앱이 그린다 */
|
|
38
|
+
content: ReactNode;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type HTMLAttributes, type Key, type ReactNode } from 'react';
|
|
2
|
+
export interface STimelineEntry {
|
|
3
|
+
/** React key — 묶음 안에서 유일해야 한다 */
|
|
4
|
+
key: Key;
|
|
5
|
+
/** 레일 왼쪽 열에 오른쪽 정렬로 서는 시각 (예: `14:32`) */
|
|
6
|
+
time?: ReactNode;
|
|
7
|
+
/** 레일 오른쪽 본문. 타이포·색은 감싸는 쪽을 따른다 — 업무 내용은 소비 앱이 그린다 */
|
|
8
|
+
content: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export interface STimelineGroup {
|
|
11
|
+
/** React key. 생략하면 순서로 대신한다 */
|
|
12
|
+
key?: Key;
|
|
13
|
+
/** 묶음 머리글 (예: 날짜) */
|
|
14
|
+
heading: ReactNode;
|
|
15
|
+
/** 머리글 아래 항목. 비어 있으면 머리글째 그리지 않는다 */
|
|
16
|
+
entries: STimelineEntry[];
|
|
17
|
+
}
|
|
18
|
+
export interface STimelineProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
19
|
+
/** 머리글로 묶은 항목 목록. 순서 그대로 그린다 */
|
|
20
|
+
groups: STimelineGroup[];
|
|
21
|
+
/**
|
|
22
|
+
* 항목이 하나도 없을 때 가운데 띄우는 안내.
|
|
23
|
+
* 불러오는 중에는 `null` 을 넘겨 빈 안내가 먼저 깜빡이지 않게 한다.
|
|
24
|
+
*/
|
|
25
|
+
emptyText?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare const STimeline: import("react").ForwardRefExoticComponent<STimelineProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { STimeline, type STimelineEntry, type STimelineGroup, type STimelineProps, } from './STimeline';
|
package/dist/index.cjs
CHANGED
|
@@ -5170,16 +5170,22 @@ var STextarea = /* @__PURE__ */ react.forwardRef(function STextarea2({
|
|
|
5170
5170
|
},
|
|
5171
5171
|
[ref]
|
|
5172
5172
|
);
|
|
5173
|
+
const autoSizedRef = react.useRef(false);
|
|
5174
|
+
const styleHeight = textareaStyle?.height;
|
|
5173
5175
|
react.useLayoutEffect(() => {
|
|
5174
5176
|
const el = innerRef.current;
|
|
5175
5177
|
if (el == null) return;
|
|
5176
5178
|
if (!autogrow) {
|
|
5177
|
-
|
|
5179
|
+
if (autoSizedRef.current) {
|
|
5180
|
+
el.style.height = typeof styleHeight === "number" ? `${styleHeight}px` : styleHeight ?? "";
|
|
5181
|
+
autoSizedRef.current = false;
|
|
5182
|
+
}
|
|
5178
5183
|
return;
|
|
5179
5184
|
}
|
|
5180
5185
|
el.style.height = "auto";
|
|
5181
5186
|
el.style.height = `${el.scrollHeight}px`;
|
|
5182
|
-
|
|
5187
|
+
autoSizedRef.current = true;
|
|
5188
|
+
}, [autogrow, maxHeight, minHeight, rows, styleHeight, value]);
|
|
5183
5189
|
useFormField({
|
|
5184
5190
|
name: rest.name,
|
|
5185
5191
|
validate: () => {
|
|
@@ -22649,10 +22655,13 @@ var SKELETON_TYPES = [
|
|
|
22649
22655
|
"switch"
|
|
22650
22656
|
];
|
|
22651
22657
|
var SKELETON_ANIMATIONS = ["wave", "pulse", "fade", "none"];
|
|
22658
|
+
var SKELETON_MODES = ["light", "dark"];
|
|
22652
22659
|
var SKELETON_TYPE_CONFIG = {
|
|
22653
|
-
rect: { radius: "var(--
|
|
22660
|
+
rect: { radius: "var(--cmp-skeleton-radius)" },
|
|
22654
22661
|
circle: { width: "48px", height: "48px", radius: "50%" },
|
|
22655
|
-
|
|
22662
|
+
// 세로를 절반(scale-y-50)으로 줄여 그리므로 세로 반지름을 두 배로 줘야 모서리가 둥글게 남는다.
|
|
22663
|
+
// 칠하는 높이가 한 줄의 절반이라 스켈레톤 radius 는 알약처럼 보여 한 단계 작은 값을 쓴다
|
|
22664
|
+
text: { radius: "var(--radius-2) / calc(var(--radius-2) * 2)" },
|
|
22656
22665
|
button: {
|
|
22657
22666
|
width: "90px",
|
|
22658
22667
|
height: "var(--cmp-button-sm-height)",
|
|
@@ -22676,13 +22685,18 @@ var SKELETON_TYPE_CONFIG = {
|
|
|
22676
22685
|
radius: "var(--cmp-switch-radius)"
|
|
22677
22686
|
}
|
|
22678
22687
|
};
|
|
22688
|
+
var wave = (alpha) => `linear-gradient(90deg, transparent, color-mix(in srgb, var(--cmp-skeleton-highlight) ${alpha}, transparent), transparent)`;
|
|
22679
22689
|
var SKELETON_COLORS = {
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22690
|
+
light: {
|
|
22691
|
+
bg: "color-mix(in srgb, var(--cmp-skeleton-bg-dark) var(--cmp-skeleton-opacity), transparent)",
|
|
22692
|
+
/** Quasar rgba(255,255,255,.5) 하이라이트 띠 */
|
|
22693
|
+
wave: wave("50%")
|
|
22694
|
+
},
|
|
22695
|
+
dark: {
|
|
22696
|
+
bg: "color-mix(in srgb, var(--cmp-skeleton-bg-light) var(--cmp-skeleton-opacity), transparent)",
|
|
22697
|
+
/** 어두운 면에서 50% 흰 띠는 너무 튀어 바탕과 같은 투명도로 낮춘다 (Quasar dark 도 .1) */
|
|
22698
|
+
wave: wave("var(--cmp-skeleton-opacity)")
|
|
22699
|
+
}
|
|
22686
22700
|
};
|
|
22687
22701
|
var ANIMATION_CLASS = {
|
|
22688
22702
|
pulse: "animate-skeleton-pulse",
|
|
@@ -22691,10 +22705,10 @@ var ANIMATION_CLASS = {
|
|
|
22691
22705
|
var toCss = (v) => typeof v === "number" ? `${v}px` : v;
|
|
22692
22706
|
var SSkeleton = /* @__PURE__ */ react.forwardRef(function SSkeleton2({
|
|
22693
22707
|
type = "rect",
|
|
22708
|
+
mode = "light",
|
|
22694
22709
|
animation = "wave",
|
|
22695
22710
|
animationSpeed = 1500,
|
|
22696
22711
|
square = false,
|
|
22697
|
-
bordered = false,
|
|
22698
22712
|
size,
|
|
22699
22713
|
width,
|
|
22700
22714
|
height,
|
|
@@ -22703,6 +22717,7 @@ var SSkeleton = /* @__PURE__ */ react.forwardRef(function SSkeleton2({
|
|
|
22703
22717
|
...props
|
|
22704
22718
|
}, ref) {
|
|
22705
22719
|
const t = SKELETON_TYPE_CONFIG[type];
|
|
22720
|
+
const colors = SKELETON_COLORS[mode];
|
|
22706
22721
|
const duration = `${animationSpeed}ms`;
|
|
22707
22722
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
22708
22723
|
"div",
|
|
@@ -22714,7 +22729,6 @@ var SSkeleton = /* @__PURE__ */ react.forwardRef(function SSkeleton2({
|
|
|
22714
22729
|
"box-border cursor-wait select-none",
|
|
22715
22730
|
// text 는 transform 이 아니라 scale 속성으로 줄여서 pulse 의 transform 애니메이션과 겹치지 않는다
|
|
22716
22731
|
type === "text" && "scale-y-50",
|
|
22717
|
-
bordered && "border border-solid",
|
|
22718
22732
|
animation === "wave" && "relative overflow-hidden",
|
|
22719
22733
|
animation !== "wave" && animation !== "none" && ANIMATION_CLASS[animation],
|
|
22720
22734
|
"motion-reduce:animate-none",
|
|
@@ -22724,8 +22738,7 @@ var SSkeleton = /* @__PURE__ */ react.forwardRef(function SSkeleton2({
|
|
|
22724
22738
|
width: toCss(width ?? size) ?? t.width,
|
|
22725
22739
|
height: toCss(height ?? size) ?? t.height,
|
|
22726
22740
|
borderRadius: square ? 0 : t.radius,
|
|
22727
|
-
background:
|
|
22728
|
-
borderColor: bordered ? SKELETON_COLORS.border : void 0,
|
|
22741
|
+
background: colors.bg,
|
|
22729
22742
|
animationDuration: animation === "pulse" || animation === "fade" ? duration : void 0,
|
|
22730
22743
|
...style
|
|
22731
22744
|
},
|
|
@@ -22735,13 +22748,81 @@ var SSkeleton = /* @__PURE__ */ react.forwardRef(function SSkeleton2({
|
|
|
22735
22748
|
"span",
|
|
22736
22749
|
{
|
|
22737
22750
|
className: "absolute inset-0 animate-skeleton-wave motion-reduce:hidden",
|
|
22738
|
-
style: {
|
|
22751
|
+
style: {
|
|
22752
|
+
background: colors.wave,
|
|
22753
|
+
animationDuration: duration,
|
|
22754
|
+
transform: "translateX(-100%)"
|
|
22755
|
+
}
|
|
22739
22756
|
}
|
|
22740
22757
|
)
|
|
22741
22758
|
]
|
|
22742
22759
|
}
|
|
22743
22760
|
);
|
|
22744
22761
|
});
|
|
22762
|
+
var STimelineImpl = /* @__PURE__ */ react.forwardRef(function STimeline({ groups, emptyText = "\uB0B4\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.", className, ...rest }, ref) {
|
|
22763
|
+
const filled = groups.filter((group) => group.entries.length > 0);
|
|
22764
|
+
if (filled.length === 0) {
|
|
22765
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
22766
|
+
"div",
|
|
22767
|
+
{
|
|
22768
|
+
ref,
|
|
22769
|
+
className: cn(
|
|
22770
|
+
"typo-body-sm-default flex h-full items-center justify-center text-[color:var(--cmp-timeline-empty-color)]",
|
|
22771
|
+
className
|
|
22772
|
+
),
|
|
22773
|
+
...rest,
|
|
22774
|
+
children: emptyText
|
|
22775
|
+
}
|
|
22776
|
+
);
|
|
22777
|
+
}
|
|
22778
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex flex-col", className), ...rest, children: filled.map((group, groupIndex) => {
|
|
22779
|
+
const isLastGroup = groupIndex === filled.length - 1;
|
|
22780
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
22781
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "typo-body-sm-bold py-[var(--cmp-timeline-heading-paddingY)] text-[color:var(--cmp-timeline-heading-color)]", children: group.heading }),
|
|
22782
|
+
/* @__PURE__ */ jsxRuntime.jsx("ol", { className: "m-0 flex list-none flex-col p-0", children: group.entries.map((entry, entryIndex) => {
|
|
22783
|
+
const isFirstEntry = entryIndex === 0;
|
|
22784
|
+
const isLastEntry = entryIndex === group.entries.length - 1;
|
|
22785
|
+
return (
|
|
22786
|
+
// 시각 열 너비는 토큰이 없다 — 줄마다 grid 가 따로라 auto 로 두면 열이 어긋나므로 고정한다.
|
|
22787
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
22788
|
+
"li",
|
|
22789
|
+
{
|
|
22790
|
+
className: "grid grid-cols-[52px_var(--cmp-timeline-rail-dot-size)_minmax(0,1fr)]",
|
|
22791
|
+
children: [
|
|
22792
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "typo-body-sm-medium whitespace-nowrap pr-[var(--cmp-timeline-entry-horizontalGap)] text-right text-[color:var(--cmp-timeline-time-color)]", children: entry.time }),
|
|
22793
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { "aria-hidden": true, className: "relative", children: [
|
|
22794
|
+
!(isLastGroup && isLastEntry) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
22795
|
+
"span",
|
|
22796
|
+
{
|
|
22797
|
+
className: cn(
|
|
22798
|
+
"absolute left-1/2 w-[var(--cmp-timeline-rail-line-width)] -translate-x-1/2 bg-[var(--cmp-timeline-rail-line-color)]",
|
|
22799
|
+
isFirstEntry && groupIndex > 0 ? "top-0" : "top-[14px]",
|
|
22800
|
+
isLastEntry ? "bottom-0" : "bottom-[-8px]"
|
|
22801
|
+
)
|
|
22802
|
+
}
|
|
22803
|
+
),
|
|
22804
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute left-0 top-[6px] size-[var(--cmp-timeline-rail-dot-size)] rounded-[var(--cmp-timeline-rail-dot-radius)] bg-[var(--cmp-timeline-rail-dot-bg)]" })
|
|
22805
|
+
] }),
|
|
22806
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
22807
|
+
"div",
|
|
22808
|
+
{
|
|
22809
|
+
className: cn(
|
|
22810
|
+
"min-w-0 pl-[var(--cmp-timeline-entry-horizontalGap)]",
|
|
22811
|
+
!isLastEntry && "pb-[var(--cmp-timeline-entry-gap)]"
|
|
22812
|
+
),
|
|
22813
|
+
children: entry.content
|
|
22814
|
+
}
|
|
22815
|
+
)
|
|
22816
|
+
]
|
|
22817
|
+
},
|
|
22818
|
+
entry.key
|
|
22819
|
+
)
|
|
22820
|
+
);
|
|
22821
|
+
}) })
|
|
22822
|
+
] }, group.key ?? groupIndex);
|
|
22823
|
+
}) });
|
|
22824
|
+
});
|
|
22825
|
+
var STimeline2 = /* @__PURE__ */ withDisplayName(STimelineImpl, "STimeline");
|
|
22745
22826
|
var SForm = /* @__PURE__ */ react.forwardRef(function SForm2({ formClassName, onSubmit, onValidationError, children, className, style }, ref) {
|
|
22746
22827
|
const formRef = react.useRef(null);
|
|
22747
22828
|
const fieldsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
@@ -28598,6 +28679,7 @@ exports.SIcon = SIcon;
|
|
|
28598
28679
|
exports.SImage = SImage;
|
|
28599
28680
|
exports.SInput = SInput;
|
|
28600
28681
|
exports.SKELETON_ANIMATIONS = SKELETON_ANIMATIONS;
|
|
28682
|
+
exports.SKELETON_MODES = SKELETON_MODES;
|
|
28601
28683
|
exports.SKELETON_TYPES = SKELETON_TYPES;
|
|
28602
28684
|
exports.SKELETON_TYPE_CONFIG = SKELETON_TYPE_CONFIG;
|
|
28603
28685
|
exports.SKeyValueTable = SKeyValueTable;
|
|
@@ -28648,6 +28730,7 @@ exports.STextLink = STextLink;
|
|
|
28648
28730
|
exports.STextarea = STextarea;
|
|
28649
28731
|
exports.STimePicker = STimePicker;
|
|
28650
28732
|
exports.STimeRangePicker = STimeRangePicker;
|
|
28733
|
+
exports.STimeline = STimeline2;
|
|
28651
28734
|
exports.SToast = SToast;
|
|
28652
28735
|
exports.SToastContainer = SToastContainer;
|
|
28653
28736
|
exports.SToggle = SToggle;
|