sellmate-design-system-react 9.0.0-beta.0 → 9.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +20 -2
- package/dist/components/SCircleProgress/README.md +3 -0
- package/dist/components/SCircleProgress/SCircleProgress.d.ts +7 -1
- package/dist/components/SIcon/README.md +2 -0
- package/dist/components/SImage/README.md +44 -0
- package/dist/components/SImage/SImage.d.ts +41 -0
- package/dist/components/SImage/index.d.ts +1 -0
- package/dist/components/SToggle/README.md +1 -0
- package/dist/components/SToggle/SToggle.d.ts +4 -1
- package/dist/index.cjs +217 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +216 -116
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +66 -3
- package/dist/llms.txt +24 -5
- package/dist/styles.css +21 -11
- package/dist/theme.css +12 -5
- package/eslint/scale.gen.mjs +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
33
33
|
| **레이아웃** | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
34
34
|
| **내비게이션** | `STabs` `SPagination` `SStepper` |
|
|
35
|
-
| **표시·상태** | `STag` `SBadge` `SIcon` `SCallout` `SGuide` |
|
|
35
|
+
| **표시·상태** | `STag` `SBadge` `SIcon` `SImage` `SCallout` `SGuide` |
|
|
36
36
|
| **진행·로딩** | `SLinearProgress` `SCircleProgress` `SLoadingContainer` `SLoadingModal` |
|
|
37
37
|
| **오버레이** | `STooltip` `SPopover` `SPopup` `SDrawer` `SPortal` |
|
|
38
38
|
| **모달** | `SModal.confirm()` `SModal.create()` + `SActionModal` `SConfirmModal` `SModalOutlet`(앱 루트 1회) |
|
|
@@ -138,7 +138,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
138
138
|
| --- | --- | --- |
|
|
139
139
|
| **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) |
|
|
140
140
|
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
141
|
-
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SLinearProgress` `SCircleProgress` |
|
|
141
|
+
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
|
|
142
142
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
|
|
143
143
|
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
144
144
|
|
|
@@ -442,6 +442,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
442
442
|
| 상태·분류를 라벨로 찍는다 | `STag` | §3-1 |
|
|
443
443
|
| 색 점만으로 상태를 찍는다 | `SBadge` | §3-1 |
|
|
444
444
|
| 아이콘을 넣는다 | `SIcon` | |
|
|
445
|
+
| 사진·썸네일을 보여준다 (로딩·실패 상태 포함) | `SImage` | §3-7-12 |
|
|
445
446
|
| 문장 안에서 다른 화면으로 보낸다 | `STextLink` | §3-5-6 |
|
|
446
447
|
|
|
447
448
|
#### C. 동작을 실행시킨다
|
|
@@ -1085,6 +1086,23 @@ const columns: STableColumn[] = [
|
|
|
1085
1086
|
- 검색 실행 시점이 다르다 — `SKeyValueTable` 필터는 앱이 검색 버튼을 직접 놓지만, `SChipFilter` 는 편집 팝오버가 닫히거나 "검색" 을 누를 때 `onSearch` 가 값 맵과 함께 호출된다. 값이 바뀌지 않았으면 호출되지 않는다.
|
|
1086
1087
|
- 함께 걸어야 하는 조건(예: 기간 중 하나는 필수)이 있으면 `fields` 를 그룹으로 넘긴다. 규칙을 못 채운 동안 경고 툴팁이 떠 있고 `onSearch` 가 막힌다.
|
|
1087
1088
|
|
|
1089
|
+
#### 3-7-12. 이미지 — SImage
|
|
1090
|
+
|
|
1091
|
+
사진·썸네일은 `<img>` 를 직접 쓰지 않고 `SImage` 를 쓴다. **로딩·실패 상태를 컴포넌트가 이미 처리하기 때문이다.**
|
|
1092
|
+
|
|
1093
|
+
- **로딩 스피너와 실패 표시를 앱이 만들지 않는다.** `src` 가 없거나 로드에 실패하면 빈 이미지 아이콘이, 로딩 중에는 스피너가 자동으로 놓인다. 크기는 상자 높이에 비례하므로 썸네일이든 큰 미리보기든 따로 맞출 것이 없다.
|
|
1094
|
+
- **크기는 `ratio` 와 너비로 준다.** `ratio` 기본값이 `1`(정사각형)이라 너비만 주면 정사각형이 된다. `className`/`style` 로 높이를 직접 주면 그쪽이 이기고 `ratio` 는 무시된다.
|
|
1095
|
+
- 상자를 채우는 방식은 `fit`(기본 `cover`), 붙는 위치는 `position` 이다. 안쪽 `<img>` 에 네이티브 속성이 필요하면 `imgProps` 로 넘긴다 — 목록에서 고스트 드래그를 막는 `imgProps={{ draggable: false }}` 가 대표적이다.
|
|
1096
|
+
- 썸네일이 많은 목록에서는 `loadingShowDelay` 를 준다. 캐시된 이미지가 즉시 로드될 때 스피너가 한 프레임 번쩍이는 것을 막는다.
|
|
1097
|
+
|
|
1098
|
+
```tsx
|
|
1099
|
+
✅ <SImage src={item.thumbnailUrl} alt={item.name} style={{ width: 64 }} /> {/* 정사각 썸네일 */}
|
|
1100
|
+
✅ <SImage src={banner} ratio={16 / 9} className="w-full" /> {/* 가로형 배너 */}
|
|
1101
|
+
|
|
1102
|
+
❌ <img src={item.thumbnailUrl} /> {/* 로딩·실패 상태가 없다 */}
|
|
1103
|
+
❌ {loading ? <SCircleProgress indeterminate /> : <SImage src={url} />} {/* SImage 가 이미 한다 */}
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1088
1106
|
---
|
|
1089
1107
|
|
|
1090
1108
|
## 4. 페이지 레시피 — 표준 골격
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
| `value?` | `number` | `0` | 진행률 (0–100) |
|
|
12
12
|
| `type?` | `SCircleProgressType` | `'primary'` | 색상 테마 |
|
|
13
13
|
| `indeterminate?` | `boolean` | `false` | 불확정(스피너) 모드 — value 무시 |
|
|
14
|
+
| `size?` | `number \| string` | `DEFAULT_SIZE` | 링의 지름. 숫자는 px 로 해석한다. 부모 크기에 비례시키려면 컨테이너 쿼리 단위(`'45cqh'`)를 쓴다 — 퍼센트는 부모가 inline-flex 라 기준 폭이 정해지지 않아 해석되지 않는다. |
|
|
14
15
|
| `label?` | `string` | — | 하단 레이블 |
|
|
15
16
|
| `innerValue?` | `boolean` | `false` | true면 퍼센트를 원 아래가 아닌 원 가운데에 표시 |
|
|
16
17
|
| `className?` | `string` | — | |
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
|
|
21
22
|
### Used by
|
|
22
23
|
|
|
24
|
+
- [SImage](../SImage)
|
|
23
25
|
- [SLoadingContainer](../SLoadingContainer)
|
|
24
26
|
- [SLoadingModal](../SLoadingModal)
|
|
25
27
|
- [STable](../STable)
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
|
|
29
31
|
```mermaid
|
|
30
32
|
graph TD;
|
|
33
|
+
SImage --> SCircleProgress
|
|
31
34
|
SLoadingContainer --> SCircleProgress
|
|
32
35
|
SLoadingModal --> SCircleProgress
|
|
33
36
|
STable --> SCircleProgress
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CSSProperties } from 'react';
|
|
2
|
-
export type SCircleProgressType = 'primary' | 'inverse' | 'error' | 'complete';
|
|
2
|
+
export type SCircleProgressType = 'primary' | 'inverse' | 'error' | 'complete' | 'neutral';
|
|
3
3
|
export interface SCircleProgressProps {
|
|
4
4
|
/** 진행률 (0–100) */
|
|
5
5
|
value?: number;
|
|
@@ -7,6 +7,12 @@ export interface SCircleProgressProps {
|
|
|
7
7
|
type?: SCircleProgressType;
|
|
8
8
|
/** 불확정(스피너) 모드 — value 무시 */
|
|
9
9
|
indeterminate?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 링의 지름. 숫자는 px 로 해석한다.
|
|
12
|
+
* 부모 크기에 비례시키려면 컨테이너 쿼리 단위(`'45cqh'`)를 쓴다 — 퍼센트는 부모가
|
|
13
|
+
* inline-flex 라 기준 폭이 정해지지 않아 해석되지 않는다.
|
|
14
|
+
*/
|
|
15
|
+
size?: number | string;
|
|
10
16
|
/** 하단 레이블 */
|
|
11
17
|
label?: string;
|
|
12
18
|
/** true면 퍼센트를 원 아래가 아닌 원 가운데에 표시 */
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
- [SGhostButton](../SGhostButton)
|
|
37
37
|
- [SGnb](../SGnb)
|
|
38
38
|
- [SGuide](../SGuide)
|
|
39
|
+
- [SImage](../SImage)
|
|
39
40
|
- [SKeyValueTable](../SKeyValueTable)
|
|
40
41
|
- [SListItem](../SListItem)
|
|
41
42
|
- [SLoadingModal](../SLoadingModal)
|
|
@@ -73,6 +74,7 @@ graph TD;
|
|
|
73
74
|
SGhostButton --> SIcon
|
|
74
75
|
SGnb --> SIcon
|
|
75
76
|
SGuide --> SIcon
|
|
77
|
+
SImage --> SIcon
|
|
76
78
|
SKeyValueTable --> SIcon
|
|
77
79
|
SListItem --> SIcon
|
|
78
80
|
SLoadingModal --> SIcon
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# SImage
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SImage
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `src?` | `string` | — | 이미지 경로. 없으면 빈 이미지 아이콘이 나온다 |
|
|
12
|
+
| `alt?` | `string` | `''` | 대체 텍스트 |
|
|
13
|
+
| `ratio?` | `number` | `1` | 가로세로 비율. 소비자가 높이를 직접 주면 그쪽이 이긴다 |
|
|
14
|
+
| `fit?` | `SImageFit` | `'cover'` | 이미지가 상자를 채우는 방식 (object-fit) |
|
|
15
|
+
| `position?` | `string` | `'50% 50%'` | 이미지 정렬 위치 (object-position) |
|
|
16
|
+
| `loading?` | `'lazy' \| 'eager'` | `'lazy'` | 브라우저 로딩 방식 |
|
|
17
|
+
| `loadingShowDelay?` | `number` | `0` | 스피너를 띄우기까지 기다리는 시간(ms). 이 시간 안에 로드되면 스피너를 아예 그리지 않는다 — 캐시된 이미지에서 스피너가 한 프레임 번쩍이는 것을 막는다. |
|
|
18
|
+
| `noSpinner?` | `boolean` | `false` | 로딩 중에도 스피너를 그리지 않는다 |
|
|
19
|
+
| `loadingSlot?` | `ReactNode` | — | 로딩 중 표시를 직접 지정한다 (기본: 스피너) |
|
|
20
|
+
| `errorSlot?` | `ReactNode` | — | 실패·빈 상태 표시를 직접 지정한다 (기본: 빈 이미지 아이콘) |
|
|
21
|
+
| `imgProps?` | `ImgHTMLAttributes<HTMLImageElement>` | — | 안쪽 `<img>` 로 넘길 네이티브 속성 (srcSet · sizes · crossOrigin · draggable 등) |
|
|
22
|
+
|
|
23
|
+
#### Events
|
|
24
|
+
|
|
25
|
+
| Event | Type | Description |
|
|
26
|
+
|-------|------|-------------|
|
|
27
|
+
| `onLoad` | `(src: string) => void` | 로드 완료. 로드된 src 를 넘긴다 |
|
|
28
|
+
| `onError` | `(event: SyntheticEvent<HTMLImageElement>) => void` | 로드 실패 |
|
|
29
|
+
|
|
30
|
+
## Dependencies
|
|
31
|
+
|
|
32
|
+
### Depends on
|
|
33
|
+
|
|
34
|
+
- [SCircleProgress](../SCircleProgress)
|
|
35
|
+
- [SIcon](../SIcon)
|
|
36
|
+
|
|
37
|
+
### Graph
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
graph TD;
|
|
41
|
+
SImage --> SCircleProgress
|
|
42
|
+
SImage --> SIcon
|
|
43
|
+
style SImage fill:#f9f,stroke:#333,stroke-width:4px
|
|
44
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ImgHTMLAttributes, type ReactNode, type SyntheticEvent } from 'react';
|
|
2
|
+
export declare const IMAGE_FITS: readonly ["cover", "contain", "fill", "none", "scale-down"];
|
|
3
|
+
export type SImageFit = (typeof IMAGE_FITS)[number];
|
|
4
|
+
export interface SImageProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad' | 'onError'> {
|
|
5
|
+
/** 이미지 경로. 없으면 빈 이미지 아이콘이 나온다 */
|
|
6
|
+
src?: string;
|
|
7
|
+
/** 대체 텍스트 */
|
|
8
|
+
alt?: string;
|
|
9
|
+
/** 가로세로 비율. 소비자가 높이를 직접 주면 그쪽이 이긴다 */
|
|
10
|
+
ratio?: number;
|
|
11
|
+
/** 이미지가 상자를 채우는 방식 (object-fit) */
|
|
12
|
+
fit?: SImageFit;
|
|
13
|
+
/** 이미지 정렬 위치 (object-position) */
|
|
14
|
+
position?: string;
|
|
15
|
+
/** 브라우저 로딩 방식 */
|
|
16
|
+
loading?: 'lazy' | 'eager';
|
|
17
|
+
/**
|
|
18
|
+
* 스피너를 띄우기까지 기다리는 시간(ms). 이 시간 안에 로드되면 스피너를 아예 그리지 않는다 —
|
|
19
|
+
* 캐시된 이미지에서 스피너가 한 프레임 번쩍이는 것을 막는다.
|
|
20
|
+
*/
|
|
21
|
+
loadingShowDelay?: number;
|
|
22
|
+
/** 로딩 중에도 스피너를 그리지 않는다 */
|
|
23
|
+
noSpinner?: boolean;
|
|
24
|
+
/** 로딩 중 표시를 직접 지정한다 (기본: 스피너) */
|
|
25
|
+
loadingSlot?: ReactNode;
|
|
26
|
+
/** 실패·빈 상태 표시를 직접 지정한다 (기본: 빈 이미지 아이콘) */
|
|
27
|
+
errorSlot?: ReactNode;
|
|
28
|
+
/** 안쪽 `<img>` 로 넘길 네이티브 속성 (srcSet · sizes · crossOrigin · draggable 등) */
|
|
29
|
+
imgProps?: ImgHTMLAttributes<HTMLImageElement>;
|
|
30
|
+
/** 로드 완료. 로드된 src 를 넘긴다 */
|
|
31
|
+
onLoad?: (src: string) => void;
|
|
32
|
+
/** 로드 실패 */
|
|
33
|
+
onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* SImage — 이미지 로딩·실패 상태를 스스로 표현하는 이미지 상자.
|
|
37
|
+
*
|
|
38
|
+
* 로딩 중에는 스피너, `src` 가 없거나 로드에 실패하면 빈 이미지 아이콘을 컨테이너 높이에
|
|
39
|
+
* 비례한 크기로 가운데에 그린다.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SImage: import("react").ForwardRefExoticComponent<SImageProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SImage, IMAGE_FITS, type SImageProps, type SImageFit } from './SImage';
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
| `value?` | `boolean` | `false` | 선택(on/off) 상태 |
|
|
12
12
|
| `label?` | `string` | `''` | 라벨 |
|
|
13
13
|
| `disabled?` | `boolean` | `false` | 비활성 |
|
|
14
|
+
| `size?` | `SToggleSize` | `'sm'` | 크기 |
|
|
14
15
|
| `className?` | `string` | — | |
|
|
15
16
|
| `style?` | `CSSProperties` | — | |
|
|
16
17
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CSSProperties } from 'react';
|
|
2
|
+
export type SToggleSize = 'xs' | 'sm';
|
|
2
3
|
export interface SToggleProps {
|
|
3
4
|
/** 선택(on/off) 상태 */
|
|
4
5
|
value?: boolean;
|
|
@@ -6,6 +7,8 @@ export interface SToggleProps {
|
|
|
6
7
|
label?: string;
|
|
7
8
|
/** 비활성 */
|
|
8
9
|
disabled?: boolean;
|
|
10
|
+
/** 크기 */
|
|
11
|
+
size?: SToggleSize;
|
|
9
12
|
/** 변경 (sdUpdate) */
|
|
10
13
|
onValueChange?: (value: boolean) => void;
|
|
11
14
|
className?: string;
|
|
@@ -15,4 +18,4 @@ export interface SToggleProps {
|
|
|
15
18
|
* SToggle — sd-toggle 포팅. 알약형 토글 버튼.
|
|
16
19
|
* default(흰 배경/회색), hover(파란 채움), select(흰 배경/파란 테두리·글자), disabled.
|
|
17
20
|
*/
|
|
18
|
-
export declare function SToggle({ value, label, disabled, onValueChange, className, style, }: SToggleProps): import("react").JSX.Element;
|
|
21
|
+
export declare function SToggle({ value, label, disabled, size, onValueChange, className, style, }: SToggleProps): import("react").JSX.Element;
|
package/dist/index.cjs
CHANGED
|
@@ -3141,6 +3141,209 @@ var SIcon = /* @__PURE__ */ react.forwardRef(function SIcon2({ name, size = 24,
|
|
|
3141
3141
|
}
|
|
3142
3142
|
);
|
|
3143
3143
|
});
|
|
3144
|
+
var VIEWBOX = 80;
|
|
3145
|
+
var RADIUS = 34;
|
|
3146
|
+
var STROKE = 12;
|
|
3147
|
+
var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
3148
|
+
var COLOR_MAP = {
|
|
3149
|
+
primary: { arc: "var(--cmp-progress-color-active)", track: "var(--cmp-progress-color-track)" },
|
|
3150
|
+
// inverse: 어두운 배경용 — arc 흰색, track 은 흰색 40%(회색 링)
|
|
3151
|
+
inverse: { arc: "var(--color-white)", track: "rgba(255,255,255,0.4)" },
|
|
3152
|
+
error: { arc: "var(--color-red-75)", track: "var(--cmp-progress-color-track)" },
|
|
3153
|
+
complete: { arc: "#01BB4B", track: "var(--cmp-progress-color-track)" },
|
|
3154
|
+
// neutral: 콘텐츠가 아직 없는 자리를 채우는 용도 — arc 도 회색이라 진행률보다 "대기"로 읽힌다
|
|
3155
|
+
neutral: { arc: "var(--color-grey-35)", track: "var(--cmp-progress-color-track)" }
|
|
3156
|
+
};
|
|
3157
|
+
var DEFAULT_SIZE = 80;
|
|
3158
|
+
var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
|
|
3159
|
+
var SCircleProgress = /* @__PURE__ */ react.forwardRef(
|
|
3160
|
+
function SCircleProgress2({
|
|
3161
|
+
value = 0,
|
|
3162
|
+
type = "primary",
|
|
3163
|
+
indeterminate = false,
|
|
3164
|
+
size = DEFAULT_SIZE,
|
|
3165
|
+
label,
|
|
3166
|
+
innerValue = false,
|
|
3167
|
+
className,
|
|
3168
|
+
style
|
|
3169
|
+
}, ref) {
|
|
3170
|
+
const v = clamp(value);
|
|
3171
|
+
const dimension = typeof size === "number" ? `${size}px` : size;
|
|
3172
|
+
const colors = COLOR_MAP[type];
|
|
3173
|
+
const c = VIEWBOX / 2;
|
|
3174
|
+
const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
|
|
3175
|
+
const filled = v / 100 * CIRCUMFERENCE;
|
|
3176
|
+
const showPercent = !indeterminate;
|
|
3177
|
+
const percentNode = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3178
|
+
"span",
|
|
3179
|
+
{
|
|
3180
|
+
className: cn(
|
|
3181
|
+
"text-center text-[16px] font-bold",
|
|
3182
|
+
innerValue ? (
|
|
3183
|
+
// leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
|
|
3184
|
+
// 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
|
|
3185
|
+
"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
|
|
3186
|
+
) : "leading-[26px]"
|
|
3187
|
+
),
|
|
3188
|
+
style: { color: percentColor },
|
|
3189
|
+
children: [
|
|
3190
|
+
Math.round(v),
|
|
3191
|
+
"%"
|
|
3192
|
+
]
|
|
3193
|
+
}
|
|
3194
|
+
);
|
|
3195
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3196
|
+
"div",
|
|
3197
|
+
{
|
|
3198
|
+
ref,
|
|
3199
|
+
className: cn(
|
|
3200
|
+
"inline-flex flex-col items-center gap-[var(--cmp-progress-circular-gap)]",
|
|
3201
|
+
className
|
|
3202
|
+
),
|
|
3203
|
+
style,
|
|
3204
|
+
children: [
|
|
3205
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(innerValue && "relative"), children: [
|
|
3206
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3207
|
+
"svg",
|
|
3208
|
+
{
|
|
3209
|
+
viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
|
|
3210
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3211
|
+
className: cn(indeterminate && "origin-center animate-circle-spin"),
|
|
3212
|
+
style: { width: dimension, height: dimension },
|
|
3213
|
+
children: [
|
|
3214
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3215
|
+
"circle",
|
|
3216
|
+
{
|
|
3217
|
+
cx: c,
|
|
3218
|
+
cy: c,
|
|
3219
|
+
r: RADIUS,
|
|
3220
|
+
fill: "none",
|
|
3221
|
+
strokeWidth: STROKE,
|
|
3222
|
+
style: { stroke: colors.track }
|
|
3223
|
+
}
|
|
3224
|
+
),
|
|
3225
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3226
|
+
"circle",
|
|
3227
|
+
{
|
|
3228
|
+
cx: c,
|
|
3229
|
+
cy: c,
|
|
3230
|
+
r: RADIUS,
|
|
3231
|
+
fill: "none",
|
|
3232
|
+
strokeWidth: STROKE,
|
|
3233
|
+
strokeLinecap: "round",
|
|
3234
|
+
className: cn(
|
|
3235
|
+
"origin-center",
|
|
3236
|
+
indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
|
|
3237
|
+
),
|
|
3238
|
+
style: {
|
|
3239
|
+
stroke: colors.arc,
|
|
3240
|
+
...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
)
|
|
3244
|
+
]
|
|
3245
|
+
}
|
|
3246
|
+
),
|
|
3247
|
+
showPercent && innerValue && percentNode
|
|
3248
|
+
] }),
|
|
3249
|
+
showPercent && !innerValue && percentNode,
|
|
3250
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
|
|
3251
|
+
]
|
|
3252
|
+
}
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
);
|
|
3256
|
+
var IMAGE_FITS = ["cover", "contain", "fill", "none", "scale-down"];
|
|
3257
|
+
var EMPTY_ICON_SIZE = "60cqh";
|
|
3258
|
+
var SPINNER_SIZE = "45cqh";
|
|
3259
|
+
var SImage = /* @__PURE__ */ react.forwardRef(function SImage2({
|
|
3260
|
+
src,
|
|
3261
|
+
alt = "",
|
|
3262
|
+
ratio = 1,
|
|
3263
|
+
fit = "cover",
|
|
3264
|
+
position = "50% 50%",
|
|
3265
|
+
loading: loading2 = "lazy",
|
|
3266
|
+
loadingShowDelay = 0,
|
|
3267
|
+
noSpinner = false,
|
|
3268
|
+
loadingSlot,
|
|
3269
|
+
errorSlot,
|
|
3270
|
+
imgProps,
|
|
3271
|
+
onLoad,
|
|
3272
|
+
onError,
|
|
3273
|
+
className,
|
|
3274
|
+
style,
|
|
3275
|
+
...rest
|
|
3276
|
+
}, ref) {
|
|
3277
|
+
const [status, setStatus] = react.useState("loading");
|
|
3278
|
+
const [spinnerVisible, setSpinnerVisible] = react.useState(loadingShowDelay <= 0);
|
|
3279
|
+
const [trackedSrc, setTrackedSrc] = react.useState(src);
|
|
3280
|
+
const imgRef = react.useRef(null);
|
|
3281
|
+
if (src !== trackedSrc) {
|
|
3282
|
+
setTrackedSrc(src);
|
|
3283
|
+
setStatus("loading");
|
|
3284
|
+
}
|
|
3285
|
+
react.useEffect(() => {
|
|
3286
|
+
if (!src) return;
|
|
3287
|
+
const node = imgRef.current;
|
|
3288
|
+
if (node?.complete && node.naturalWidth > 0) {
|
|
3289
|
+
setStatus((current) => current === "loading" ? "loaded" : current);
|
|
3290
|
+
}
|
|
3291
|
+
}, [src]);
|
|
3292
|
+
react.useEffect(() => {
|
|
3293
|
+
if (status !== "loading" || loadingShowDelay <= 0) {
|
|
3294
|
+
setSpinnerVisible(status === "loading");
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
setSpinnerVisible(false);
|
|
3298
|
+
const timer = setTimeout(() => setSpinnerVisible(true), loadingShowDelay);
|
|
3299
|
+
return () => clearTimeout(timer);
|
|
3300
|
+
}, [status, loadingShowDelay, src]);
|
|
3301
|
+
const isEmpty = !src;
|
|
3302
|
+
const showFallback = isEmpty || status === "error";
|
|
3303
|
+
const showSpinner = !isEmpty && status === "loading" && !noSpinner && spinnerVisible;
|
|
3304
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3305
|
+
"div",
|
|
3306
|
+
{
|
|
3307
|
+
ref,
|
|
3308
|
+
className: cn("relative overflow-hidden bg-(--cmp-image-bg)", className),
|
|
3309
|
+
style: { containerType: "size", aspectRatio: ratio, ...style },
|
|
3310
|
+
...rest,
|
|
3311
|
+
children: [
|
|
3312
|
+
!isEmpty && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3313
|
+
"img",
|
|
3314
|
+
{
|
|
3315
|
+
...imgProps,
|
|
3316
|
+
ref: imgRef,
|
|
3317
|
+
src,
|
|
3318
|
+
alt,
|
|
3319
|
+
loading: loading2,
|
|
3320
|
+
onLoad: () => {
|
|
3321
|
+
setStatus("loaded");
|
|
3322
|
+
onLoad?.(src);
|
|
3323
|
+
},
|
|
3324
|
+
onError: (event) => {
|
|
3325
|
+
setStatus("error");
|
|
3326
|
+
onError?.(event);
|
|
3327
|
+
},
|
|
3328
|
+
className: cn(
|
|
3329
|
+
"h-full w-full",
|
|
3330
|
+
// 로딩 중에도 자리를 지켜야 브라우저가 계속 받아온다 — 숨기되 없애지 않는다
|
|
3331
|
+
status === "loaded" ? "visible" : "invisible",
|
|
3332
|
+
imgProps?.className
|
|
3333
|
+
),
|
|
3334
|
+
style: {
|
|
3335
|
+
objectFit: fit,
|
|
3336
|
+
objectPosition: position,
|
|
3337
|
+
...imgProps?.style
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
),
|
|
3341
|
+
showFallback && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center", children: errorSlot ?? /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "image", size: EMPTY_ICON_SIZE, color: "var(--cmp-image-icon)" }) }),
|
|
3342
|
+
showSpinner && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center", children: loadingSlot ?? /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: SPINNER_SIZE }) })
|
|
3343
|
+
]
|
|
3344
|
+
}
|
|
3345
|
+
);
|
|
3346
|
+
});
|
|
3144
3347
|
var BADGE_COLORS = [
|
|
3145
3348
|
"red",
|
|
3146
3349
|
"orange",
|
|
@@ -5582,14 +5785,14 @@ var SCallout = /* @__PURE__ */ react.forwardRef(function SCallout2({ type = "def
|
|
|
5582
5785
|
);
|
|
5583
5786
|
});
|
|
5584
5787
|
var FILL_COLOR = {
|
|
5585
|
-
primary: "var(--cmp-progress-active
|
|
5586
|
-
error: "var(--cmp-progress-error
|
|
5587
|
-
complete: "var(--cmp-progress-success
|
|
5788
|
+
primary: "var(--cmp-progress-color-active)",
|
|
5789
|
+
error: "var(--cmp-progress-color-error)",
|
|
5790
|
+
complete: "var(--cmp-progress-color-success)"
|
|
5588
5791
|
};
|
|
5589
|
-
var
|
|
5792
|
+
var clamp2 = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
|
|
5590
5793
|
var SLinearProgress = /* @__PURE__ */ react.forwardRef(
|
|
5591
5794
|
function SLinearProgress2({ value = 0, type = "primary", indeterminate = false, label, className, style }, ref) {
|
|
5592
|
-
const v =
|
|
5795
|
+
const v = clamp2(value);
|
|
5593
5796
|
const fill = FILL_COLOR[type];
|
|
5594
5797
|
const valueColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : fill;
|
|
5595
5798
|
const valueText = `${Math.round(v)}%`;
|
|
@@ -5636,115 +5839,6 @@ var SLinearProgress = /* @__PURE__ */ react.forwardRef(
|
|
|
5636
5839
|
);
|
|
5637
5840
|
}
|
|
5638
5841
|
);
|
|
5639
|
-
var VIEWBOX = 80;
|
|
5640
|
-
var RADIUS = 34;
|
|
5641
|
-
var STROKE = 12;
|
|
5642
|
-
var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
5643
|
-
var COLOR_MAP = {
|
|
5644
|
-
primary: { arc: "var(--cmp-progress-active-color)", track: "var(--cmp-progress-color-track)" },
|
|
5645
|
-
// inverse: 어두운 배경용 — arc 흰색, track 은 흰색 40%(회색 링)
|
|
5646
|
-
inverse: { arc: "var(--color-white)", track: "rgba(255,255,255,0.4)" },
|
|
5647
|
-
error: { arc: "var(--color-red-75)", track: "var(--cmp-progress-color-track)" },
|
|
5648
|
-
complete: { arc: "#01BB4B", track: "var(--cmp-progress-color-track)" }
|
|
5649
|
-
};
|
|
5650
|
-
var clamp2 = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
|
|
5651
|
-
var SCircleProgress = /* @__PURE__ */ react.forwardRef(
|
|
5652
|
-
function SCircleProgress2({
|
|
5653
|
-
value = 0,
|
|
5654
|
-
type = "primary",
|
|
5655
|
-
indeterminate = false,
|
|
5656
|
-
label,
|
|
5657
|
-
innerValue = false,
|
|
5658
|
-
className,
|
|
5659
|
-
style
|
|
5660
|
-
}, ref) {
|
|
5661
|
-
const v = clamp2(value);
|
|
5662
|
-
const colors = COLOR_MAP[type];
|
|
5663
|
-
const c = VIEWBOX / 2;
|
|
5664
|
-
const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
|
|
5665
|
-
const filled = v / 100 * CIRCUMFERENCE;
|
|
5666
|
-
const showPercent = !indeterminate;
|
|
5667
|
-
const percentNode = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5668
|
-
"span",
|
|
5669
|
-
{
|
|
5670
|
-
className: cn(
|
|
5671
|
-
"text-center text-[16px] font-bold",
|
|
5672
|
-
innerValue ? (
|
|
5673
|
-
// leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
|
|
5674
|
-
// 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
|
|
5675
|
-
"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
|
|
5676
|
-
) : "leading-[26px]"
|
|
5677
|
-
),
|
|
5678
|
-
style: { color: percentColor },
|
|
5679
|
-
children: [
|
|
5680
|
-
Math.round(v),
|
|
5681
|
-
"%"
|
|
5682
|
-
]
|
|
5683
|
-
}
|
|
5684
|
-
);
|
|
5685
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5686
|
-
"div",
|
|
5687
|
-
{
|
|
5688
|
-
ref,
|
|
5689
|
-
className: cn(
|
|
5690
|
-
"inline-flex flex-col items-center gap-[var(--cmp-progress-circular-gap)]",
|
|
5691
|
-
className
|
|
5692
|
-
),
|
|
5693
|
-
style,
|
|
5694
|
-
children: [
|
|
5695
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(innerValue && "relative"), children: [
|
|
5696
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5697
|
-
"svg",
|
|
5698
|
-
{
|
|
5699
|
-
viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
|
|
5700
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
5701
|
-
className: cn(
|
|
5702
|
-
"h-[80px] w-[80px]",
|
|
5703
|
-
indeterminate && "origin-center animate-circle-spin"
|
|
5704
|
-
),
|
|
5705
|
-
children: [
|
|
5706
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5707
|
-
"circle",
|
|
5708
|
-
{
|
|
5709
|
-
cx: c,
|
|
5710
|
-
cy: c,
|
|
5711
|
-
r: RADIUS,
|
|
5712
|
-
fill: "none",
|
|
5713
|
-
strokeWidth: STROKE,
|
|
5714
|
-
style: { stroke: colors.track }
|
|
5715
|
-
}
|
|
5716
|
-
),
|
|
5717
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5718
|
-
"circle",
|
|
5719
|
-
{
|
|
5720
|
-
cx: c,
|
|
5721
|
-
cy: c,
|
|
5722
|
-
r: RADIUS,
|
|
5723
|
-
fill: "none",
|
|
5724
|
-
strokeWidth: STROKE,
|
|
5725
|
-
strokeLinecap: "round",
|
|
5726
|
-
className: cn(
|
|
5727
|
-
"origin-center",
|
|
5728
|
-
indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
|
|
5729
|
-
),
|
|
5730
|
-
style: {
|
|
5731
|
-
stroke: colors.arc,
|
|
5732
|
-
...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
|
|
5733
|
-
}
|
|
5734
|
-
}
|
|
5735
|
-
)
|
|
5736
|
-
]
|
|
5737
|
-
}
|
|
5738
|
-
),
|
|
5739
|
-
showPercent && innerValue && percentNode
|
|
5740
|
-
] }),
|
|
5741
|
-
showPercent && !innerValue && percentNode,
|
|
5742
|
-
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
|
|
5743
|
-
]
|
|
5744
|
-
}
|
|
5745
|
-
);
|
|
5746
|
-
}
|
|
5747
|
-
);
|
|
5748
5842
|
|
|
5749
5843
|
// src/lib/global-overlay.ts
|
|
5750
5844
|
var GLOBAL_OVERLAY_ATTR = "data-s-global-overlay";
|
|
@@ -6230,10 +6324,15 @@ function SSwitch({
|
|
|
6230
6324
|
}
|
|
6231
6325
|
);
|
|
6232
6326
|
}
|
|
6327
|
+
var SIZE_CLASSES = {
|
|
6328
|
+
xs: "h-[24px] px-[8px] text-[12px] font-normal leading-[20px]",
|
|
6329
|
+
sm: "h-[28px] px-[12px] text-[14px] font-medium leading-[24px]"
|
|
6330
|
+
};
|
|
6233
6331
|
function SToggle({
|
|
6234
6332
|
value = false,
|
|
6235
6333
|
label = "",
|
|
6236
6334
|
disabled = false,
|
|
6335
|
+
size = "sm",
|
|
6237
6336
|
onValueChange,
|
|
6238
6337
|
className,
|
|
6239
6338
|
style
|
|
@@ -6249,7 +6348,8 @@ function SToggle({
|
|
|
6249
6348
|
"aria-pressed": value,
|
|
6250
6349
|
onClick: () => !disabled && onValueChange?.(!value),
|
|
6251
6350
|
className: cn(
|
|
6252
|
-
"box-border inline-flex
|
|
6351
|
+
"box-border inline-flex items-center justify-center whitespace-nowrap rounded-full border border-solid transition-colors",
|
|
6352
|
+
SIZE_CLASSES[size],
|
|
6253
6353
|
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
6254
6354
|
// 미선택(off) hover 시 파란 채움 + 흰 글자
|
|
6255
6355
|
!disabled && !value && "hover:!bg-[var(--cmp-toggle-bg-hover)] hover:!text-[color:var(--cmp-toggle-content-hover)] hover:!border-[color:var(--cmp-toggle-bg-hover)]",
|
|
@@ -16402,6 +16502,7 @@ exports.GNB_MENU_WIDTH = GNB_MENU_WIDTH;
|
|
|
16402
16502
|
exports.GNB_RAIL = GNB_RAIL;
|
|
16403
16503
|
exports.GNB_RAIL_WIDTH = GNB_RAIL_WIDTH;
|
|
16404
16504
|
exports.ICONS = ICONS;
|
|
16505
|
+
exports.IMAGE_FITS = IMAGE_FITS;
|
|
16405
16506
|
exports.PAGE_BACKGROUNDS = PAGE_BACKGROUNDS;
|
|
16406
16507
|
exports.RangeCalendar = RangeCalendar;
|
|
16407
16508
|
exports.SActionModal = SActionModal;
|
|
@@ -16436,6 +16537,7 @@ exports.SGhostButton = SGhostButton;
|
|
|
16436
16537
|
exports.SGnb = SGnb;
|
|
16437
16538
|
exports.SGuide = SGuide;
|
|
16438
16539
|
exports.SIcon = SIcon;
|
|
16540
|
+
exports.SImage = SImage;
|
|
16439
16541
|
exports.SInput = SInput;
|
|
16440
16542
|
exports.SKeyValueTable = SKeyValueTable;
|
|
16441
16543
|
exports.SLAYOUT_MIN_WIDTH = SLAYOUT_MIN_WIDTH;
|