sellmate-design-system-react 4.2.0 → 4.3.1
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 +7 -1
- package/README.md +19 -3
- package/dist/components/SButton/README.md +2 -0
- package/dist/components/SDrawer/README.md +42 -0
- package/dist/components/SDrawer/SDrawer.d.ts +39 -0
- package/dist/components/SDrawer/index.d.ts +1 -0
- package/dist/components/SGhostButton/README.md +2 -0
- package/dist/components/SGnb/gnb.config.d.ts +11 -0
- package/dist/index.cjs +215 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +215 -15
- package/dist/index.js.map +1 -1
- package/dist/lib/global-overlay.d.ts +17 -0
- package/dist/llms-full.txt +47 -1
- package/dist/llms.txt +9 -3
- package/dist/styles.css +97 -9
- package/dist/theme.css +7 -0
- package/eslint/rules/no-off-scale-spacing.mjs +114 -35
- package/package.json +3 -1
package/AGENTS.md
CHANGED
|
@@ -153,6 +153,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
153
153
|
- **간격 유틸리티는 `sd-` 접두를 붙인다** — `gap-sd-8`, `p-sd-16`, `mt-sd-12`. 숫자 = px 다.
|
|
154
154
|
접두를 빼면 Tailwind 기본 스케일이 적용된다(`gap-8` = 32px). 접두는 속성 뒤, 숫자 앞에 온다.
|
|
155
155
|
- 스케일: `2 4 6 8 10 12 16 19 20 22 24 28 32 36 40 44 48 56 60 62 80`
|
|
156
|
+
- **스케일에 없는 간격이 필요하면 임의 값(`p-[64px]`)으로 우회하지 않는다.** 가까운 스케일 값으로 맞추거나, 디자인상 그 값이어야 하면 **토큰 추가를 요청한다.** 이미 있는 토큰을 정확히 써야 할 때만 `gap-[var(--토큰명)]` 으로 참조한다.
|
|
156
157
|
- 형제 요소 간격은 margin 대신 부모의 `flex`/`grid` + `gap-sd-*`으로 잡는다.
|
|
157
158
|
- 시맨틱 간격 토큰 (텍스트 덩어리·요소 사이 기본 리듬):
|
|
158
159
|
|
|
@@ -490,6 +491,8 @@ const columns: STableColumn[] = [
|
|
|
490
491
|
| 테이블 **행 내부** 인라인 액션 | `xs` |
|
|
491
492
|
| 화면·목록 액션 (툴바, `STableBar`) | `sm` |
|
|
492
493
|
| **모달·위저드 푸터** | `md` |
|
|
494
|
+
| **페이지 하단 실행 버튼** (폼 저장·상세 수정 등) | `md` |
|
|
495
|
+
| 스크롤 시 헤더로 승격된 버튼 | `sm` (최대) |
|
|
493
496
|
|
|
494
497
|
`lg` 는 현재 사용처가 없다. 페이지 골격에서 쓰지 않는다.
|
|
495
498
|
|
|
@@ -622,11 +625,12 @@ export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
|
622
625
|
| `header` | 상단바 배치 | 로고 폭 | `topContent` |
|
|
623
626
|
| --- | --- | --- | --- |
|
|
624
627
|
| `"fix"` (기본) | `[런처 · 로고 … 폴드]` — 상단바가 GNB 컬럼 안에 있고 폴드가 컬럼 오른쪽 끝 | 내용 폭 | **렌더되지 않는다** (놓을 자리가 없다) |
|
|
625
|
-
| `"full"` | `[런처 · 폴드 · 로고 · topContent]` — 상단바가 화면 전폭 | **140px 고정** | 로고 오른쪽 남는 폭 전체 |
|
|
628
|
+
| `"full"` | `[런처 · 폴드 · 로고 · topContent]` — 상단바가 화면 전폭 | **140px 고정** (런처 없으면 172px) | 로고 오른쪽 남는 폭 전체 |
|
|
626
629
|
|
|
627
630
|
- `topContent` 는 상단바 로고 오른쪽 슬롯이다. 전역 검색·계정 메뉴·알림처럼 **모든 페이지에 공통인 것만** 넣는다. 페이지별 액션은 여기가 아니라 §4-2 의 `STableBar` 로 간다.
|
|
628
631
|
- 슬롯이 남는 폭을 통째로 받으므로 **정렬은 안에서 직접 잡는다** (좌측 정렬 + 우측은 `ml-auto`).
|
|
629
632
|
- `header="full"` 에서 로고 자리는 140px 로 고정된다 — 로고 내용이 바뀌어도 `topContent` 시작점이 흔들리지 않게 하기 위함이다. 로고가 그보다 넓으면 잘리므로 이 폭에 맞춰 준비한다.
|
|
633
|
+
- `onLauncherClick` 을 주지 않으면 런처가 렌더되지 않고, 그 자리(버튼 + 간격)를 로고 슬롯이 이어받아 172px 가 된다. `topContent` 시작점은 런처 유무와 관계없이 같은 자리다.
|
|
630
634
|
|
|
631
635
|
```tsx
|
|
632
636
|
<SLayout type="box" header="full">
|
|
@@ -947,4 +951,6 @@ export default function ProductDetailPage() {
|
|
|
947
951
|
|
|
948
952
|
`gap-sd-13` 처럼 스케일 밖 값은 Tailwind v4 에서 **에러 없이 조용히 무시된다**. 반대로 접두를 빠뜨린 `gap-8` 은 Tailwind 기본 스케일이라 **32px 이 적용된다**. 둘 다 "간격이 왜 이러지" 로만 보이므로 반드시 `sd-` 접두 스케일 값을 쓴다. (린트는 기본값에서 앞쪽만 잡는다 — 뒤쪽까지 강제하려면 `requirePrefix: true`)
|
|
949
953
|
|
|
954
|
+
접두 누락은 **자동 수정되지 않는다.** `p-16` 은 지금 64px 로 동작하는 코드라 `p-sd-16`(16px)으로 바꾸면 여백이 달라지기 때문이다. 린트는 "숫자를 px 로 의도했나(`p-sd-16`)" 와 "현재 값을 유지할 건가(스케일에 있으면 그 값)" 를 제안으로 내놓으니 **의도에 맞는 쪽을 골라 적용한다.**
|
|
955
|
+
|
|
950
956
|
린트가 잡지 못하는 것(§3 컴포넌트 선택, §4 페이지 골격, 버튼 배치)은 §5 체크리스트로 직접 확인한다.
|
package/README.md
CHANGED
|
@@ -252,9 +252,25 @@ export default [...sellmate.configs.strict];
|
|
|
252
252
|
{ rules: { 'sellmate/no-off-scale-spacing': ['error', { requirePrefix: true }] } }
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
접두 없는 `p-16` 은 **깨진 코드가 아니라 Tailwind 기본 스케일로 64px 이 적용되는 코드**입니다. 이걸 `p-sd-16`(16px)으로 옮기는 것은 표기 정규화가 아니라 값 변경이라, **자동 수정하지 않고 두 해석을 제안(suggestion)으로만** 냅니다.
|
|
256
|
+
|
|
257
|
+
```text
|
|
258
|
+
`p-4` 은 현재 16px 입니다 (Tailwind 기본 스케일).
|
|
259
|
+
4px 을 의도했다면 → `p-sd-4`
|
|
260
|
+
16px 을 유지하려면 → `p-sd-16`
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
편집기에서 둘 중 하나를 골라 적용하고, 스케일에 없는 값이면 대응 토큰이 없다는 안내가 나옵니다.
|
|
264
|
+
|
|
265
|
+
#### 3.x 에서 한 번에 옮길 때
|
|
266
|
+
|
|
267
|
+
3.x 이하의 간격 스케일(숫자 = px)을 쓰던 앱은 `unsafeFix` 로 예전의 일괄 수정을 켤 수 있습니다.
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
{ rules: { 'sellmate/no-off-scale-spacing': ['error', { requirePrefix: true, unsafeFix: true }] } }
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
> ⚠️ `unsafeFix` 는 **"클래스의 숫자 = px"** 를 단정해 `p-16` → `p-sd-16` 으로 바꿉니다. 기본 Tailwind 스케일(`p-4` = 16px)로 쓰던 앱에서 켜면 **여백이 1/4 로 줄어듭니다.** 마이그레이션 커밋에서만 켜고 `eslint --fix` 를 돌린 뒤 다시 끄세요.
|
|
258
274
|
|
|
259
275
|
## 제공 컴포넌트
|
|
260
276
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
- [SActionModal](../SActionModal)
|
|
23
23
|
- [SConfirmModal](../SConfirmModal)
|
|
24
|
+
- [SDrawer](../SDrawer)
|
|
24
25
|
- [SDropdownButton](../SDropdownButton)
|
|
25
26
|
- [SKeyValueTable](../SKeyValueTable)
|
|
26
27
|
- [SLoadingModal](../SLoadingModal)
|
|
@@ -39,6 +40,7 @@ graph TD;
|
|
|
39
40
|
SButton --> SIcon
|
|
40
41
|
SActionModal --> SButton
|
|
41
42
|
SConfirmModal --> SButton
|
|
43
|
+
SDrawer --> SButton
|
|
42
44
|
SDropdownButton --> SButton
|
|
43
45
|
SKeyValueTable --> SButton
|
|
44
46
|
SLoadingModal --> SButton
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# SDrawer
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SDrawer
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `open?` | `boolean` | — | 표시 여부 |
|
|
12
|
+
| `persistent?` | `boolean` | `false` | true면 backdrop·ESC로 닫히지 않고 흔들림 효과를 준다. |
|
|
13
|
+
| `title?` | `string` | `''` | 접근성 제목 및 헤더 제목 |
|
|
14
|
+
| `width?` | `number \| string` | `572` | Drawer 너비. 기본값은 Figma drawer 기준 572px이다. |
|
|
15
|
+
| `footerLeft?` | `ReactNode` | — | footer 좌측 슬롯 |
|
|
16
|
+
| `button?` | `SDrawerButton` | — | 우측 기본 액션 버튼 |
|
|
17
|
+
| `children?` | `ReactNode` | — | |
|
|
18
|
+
| `className?` | `string` | — | |
|
|
19
|
+
| `style?` | `CSSProperties` | — | |
|
|
20
|
+
|
|
21
|
+
#### Events
|
|
22
|
+
|
|
23
|
+
| Event | Type | Description |
|
|
24
|
+
|-------|------|-------------|
|
|
25
|
+
| `onOpenChange` | `(open: boolean) => void` | 표시 상태 변경 |
|
|
26
|
+
| `onClose` | `() => void` | 닫기(X) 버튼 클릭 |
|
|
27
|
+
|
|
28
|
+
## Dependencies
|
|
29
|
+
|
|
30
|
+
### Depends on
|
|
31
|
+
|
|
32
|
+
- [SButton](../SButton)
|
|
33
|
+
- [SGhostButton](../SGhostButton)
|
|
34
|
+
|
|
35
|
+
### Graph
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
graph TD;
|
|
39
|
+
SDrawer --> SButton
|
|
40
|
+
SDrawer --> SGhostButton
|
|
41
|
+
style SDrawer fill:#f9f,stroke:#333,stroke-width:4px
|
|
42
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
import { type SButtonColor, type SButtonSize } from '../SButton';
|
|
3
|
+
export interface SDrawerButton {
|
|
4
|
+
label?: string;
|
|
5
|
+
color?: SButtonColor;
|
|
6
|
+
outline?: boolean;
|
|
7
|
+
size?: SButtonSize;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
onClick?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface SDrawerProps {
|
|
12
|
+
/** 표시 여부 */
|
|
13
|
+
open?: boolean;
|
|
14
|
+
/** 표시 상태 변경 */
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** 닫기(X) 버튼 클릭 */
|
|
17
|
+
onClose?: () => void;
|
|
18
|
+
/** true면 backdrop·ESC로 닫히지 않고 흔들림 효과를 준다. */
|
|
19
|
+
persistent?: boolean;
|
|
20
|
+
/** 접근성 제목 및 헤더 제목 */
|
|
21
|
+
title?: string;
|
|
22
|
+
/** Drawer 너비. 기본값은 Figma drawer 기준 572px이다. */
|
|
23
|
+
width?: number | string;
|
|
24
|
+
/** footer 좌측 슬롯 */
|
|
25
|
+
footerLeft?: ReactNode;
|
|
26
|
+
/** 우측 기본 액션 버튼 */
|
|
27
|
+
button?: SDrawerButton;
|
|
28
|
+
children?: ReactNode;
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* SDrawer — Figma drawer 포팅.
|
|
34
|
+
*
|
|
35
|
+
* 모달과 동일한 전역 modal host와 stack을 공유한다. 따라서 Drawer가 열린 상태에서
|
|
36
|
+
* 모달을 추가로 열면 새 모달이 Drawer 위에 쌓이고, Drawer 내부 floating은 전용 portal
|
|
37
|
+
* container를 통해 Drawer의 backdrop 위에 표시된다.
|
|
38
|
+
*/
|
|
39
|
+
export declare function SDrawer({ open, onOpenChange, onClose, persistent, title, width, footerLeft, button, children, className, style, }: SDrawerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SDrawer, type SDrawerButton, type SDrawerProps } from './SDrawer';
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
- [SCalendar](../SCalendar)
|
|
36
36
|
- [SChip](../SChip)
|
|
37
37
|
- [SDateRangePicker](../SDateRangePicker)
|
|
38
|
+
- [SDrawer](../SDrawer)
|
|
38
39
|
- [SFilePicker](../SFilePicker)
|
|
39
40
|
- [SGnb](../SGnb)
|
|
40
41
|
- [SGuide](../SGuide)
|
|
@@ -63,6 +64,7 @@ graph TD;
|
|
|
63
64
|
SCalendar --> SGhostButton
|
|
64
65
|
SChip --> SGhostButton
|
|
65
66
|
SDateRangePicker --> SGhostButton
|
|
67
|
+
SDrawer --> SGhostButton
|
|
66
68
|
SFilePicker --> SGhostButton
|
|
67
69
|
SGnb --> SGhostButton
|
|
68
70
|
SGuide --> SGhostButton
|
|
@@ -119,6 +119,7 @@ export declare const GNB_MENU_WIDTH = 240;
|
|
|
119
119
|
* header="full" 상단바의 로고 슬롯 고정 폭(토큰 없음 → 상수).
|
|
120
120
|
* 전폭 상단바에서는 로고 오른쪽에 topContent 슬롯이 이어지므로, 로고 내용이 바뀌어도
|
|
121
121
|
* 그 시작점이 흔들리지 않도록 자리를 못 박는다. fix 는 상단바가 GNB 컬럼 안이라 내용 폭을 따른다.
|
|
122
|
+
* 런처를 쓰지 않으면 그 자리를 로고가 이어받는다 → GNB_FULL_LOGO_WIDTH_NO_LAUNCHER.
|
|
122
123
|
*/
|
|
123
124
|
export declare const GNB_FULL_LOGO_WIDTH = 140;
|
|
124
125
|
/**
|
|
@@ -127,6 +128,16 @@ export declare const GNB_FULL_LOGO_WIDTH = 140;
|
|
|
127
128
|
* 이보다 넓은 --cmp-gnb-top-gap 을 쓴다.
|
|
128
129
|
*/
|
|
129
130
|
export declare const GNB_TOP_ICON_GAP = 8;
|
|
131
|
+
/**
|
|
132
|
+
* 상단바 아이콘 버튼(xs 고스트) 폭 = --cmp-ghostButton-xs-size.
|
|
133
|
+
* 토큰이 CSS 변수라 JS 에서 더할 수 없어 값을 상수로 둔다(GNB_RAIL_WIDTH 와 같은 이유).
|
|
134
|
+
*/
|
|
135
|
+
export declare const GNB_TOP_ICON_SIZE = 24;
|
|
136
|
+
/**
|
|
137
|
+
* 런처를 쓰지 않는 header="full" 의 로고 슬롯 폭 = 기본 폭 + 런처가 비운 자리(버튼 + 간격).
|
|
138
|
+
* 런처 유무로 topContent 시작점이 32px 씩 밀리지 않도록, 빈 자리를 로고가 흡수한다.
|
|
139
|
+
*/
|
|
140
|
+
export declare const GNB_FULL_LOGO_WIDTH_NO_LAUNCHER: number;
|
|
130
141
|
/**
|
|
131
142
|
* useRail 에서 value 를 품은 첫 depth(레일) 아이템의 value.
|
|
132
143
|
* value 자체가 레일 아이템이면 그 값을, 어디에도 없으면 undefined 를 반환한다.
|
package/dist/index.cjs
CHANGED
|
@@ -3645,9 +3645,10 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
|
|
|
3645
3645
|
className: cn(
|
|
3646
3646
|
"group/expansion-item relative overflow-hidden bg-(--cmp-listExpansionItem-title-bg-default)",
|
|
3647
3647
|
!bordered && "border-b border-(--cmp-listExpansionItem-border-default)",
|
|
3648
|
-
//
|
|
3649
|
-
bordered
|
|
3650
|
-
bordered &&
|
|
3648
|
+
// 보더 색은 시맨틱 토큰(border-default → hover strong → 펼침 accentLight)을 쓴다.
|
|
3649
|
+
// TODO: expansionItem/bordered/radius 토큰 생성 후 rounded 고정값도 토큰으로 교체한다.
|
|
3650
|
+
bordered && "rounded-[8px] border border-border-default hover:border-border-strong [&:has(>button:hover)]:border-border-strong",
|
|
3651
|
+
bordered && isExpanded && "!border-border-accentLight",
|
|
3651
3652
|
className
|
|
3652
3653
|
),
|
|
3653
3654
|
...rest,
|
|
@@ -5328,6 +5329,16 @@ var SCircleProgress = /* @__PURE__ */ react.forwardRef(
|
|
|
5328
5329
|
);
|
|
5329
5330
|
}
|
|
5330
5331
|
);
|
|
5332
|
+
|
|
5333
|
+
// src/lib/global-overlay.ts
|
|
5334
|
+
var GLOBAL_OVERLAY_ATTR = "data-s-global-overlay";
|
|
5335
|
+
var globalOverlayProps = { [GLOBAL_OVERLAY_ATTR]: "" };
|
|
5336
|
+
function isFromGlobalOverlay(event) {
|
|
5337
|
+
const detail = event.detail;
|
|
5338
|
+
const target = detail?.originalEvent?.target ?? event.target;
|
|
5339
|
+
const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
5340
|
+
return !!element?.closest(`[${GLOBAL_OVERLAY_ATTR}]`);
|
|
5341
|
+
}
|
|
5331
5342
|
var SLoadingContainer = /* @__PURE__ */ react.forwardRef(
|
|
5332
5343
|
function SLoadingContainer2({ open = false, message, zIndex = Z_INDEX.loading, className, style }, ref) {
|
|
5333
5344
|
const [mounted2, setMounted] = react.useState(false);
|
|
@@ -5364,6 +5375,7 @@ var SLoadingContainer = /* @__PURE__ */ react.forwardRef(
|
|
|
5364
5375
|
"div",
|
|
5365
5376
|
{
|
|
5366
5377
|
ref: setRef,
|
|
5378
|
+
...globalOverlayProps,
|
|
5367
5379
|
"aria-hidden": !open,
|
|
5368
5380
|
className: cn(
|
|
5369
5381
|
"fixed inset-0 flex items-center justify-center transition-opacity duration-200",
|
|
@@ -5571,6 +5583,7 @@ var SDropdownButtonImpl = /* @__PURE__ */ react.forwardRef(
|
|
|
5571
5583
|
style
|
|
5572
5584
|
}, ref) {
|
|
5573
5585
|
const portalContainer = usePortalContainer();
|
|
5586
|
+
const insideModal = useInsideModal();
|
|
5574
5587
|
const [open, setOpen] = react.useState(false);
|
|
5575
5588
|
const splitWrapRef = react.useRef(null);
|
|
5576
5589
|
const splitToggleRef = react.useRef(null);
|
|
@@ -5659,6 +5672,7 @@ var SDropdownButtonImpl = /* @__PURE__ */ react.forwardRef(
|
|
|
5659
5672
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5660
5673
|
RDropdown__namespace.Root,
|
|
5661
5674
|
{
|
|
5675
|
+
modal: !insideModal,
|
|
5662
5676
|
open,
|
|
5663
5677
|
onOpenChange: (o) => {
|
|
5664
5678
|
setOpen(o);
|
|
@@ -6093,6 +6107,7 @@ function SGuide({
|
|
|
6093
6107
|
style
|
|
6094
6108
|
}) {
|
|
6095
6109
|
const [open, setOpen] = react.useState(false);
|
|
6110
|
+
const portalContainer = usePortalContainer();
|
|
6096
6111
|
const isNotion = type === "notion";
|
|
6097
6112
|
const defaultLabel = DEFAULT_LABEL[type];
|
|
6098
6113
|
const iconName = ICON[type];
|
|
@@ -6127,7 +6142,7 @@ function SGuide({
|
|
|
6127
6142
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style, children: trigger });
|
|
6128
6143
|
return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
|
|
6129
6144
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: trigger }),
|
|
6130
|
-
/* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6145
|
+
/* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6131
6146
|
RPopover2__namespace.Content,
|
|
6132
6147
|
{
|
|
6133
6148
|
side: "bottom",
|
|
@@ -6268,6 +6283,13 @@ function SModalContainer({
|
|
|
6268
6283
|
e.preventDefault();
|
|
6269
6284
|
if (stackRegistrationRef.current?.isTop()) triggerShake();
|
|
6270
6285
|
} : void 0;
|
|
6286
|
+
const guardInteractOutside = (e) => {
|
|
6287
|
+
if (isFromGlobalOverlay(e)) {
|
|
6288
|
+
e.preventDefault();
|
|
6289
|
+
return;
|
|
6290
|
+
}
|
|
6291
|
+
guardClose?.(e);
|
|
6292
|
+
};
|
|
6271
6293
|
return /* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Portal, { container: modalHost, forceMount: rendered ? true : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none absolute inset-0", style: { zIndex: layerIndex ?? 0 }, children: [
|
|
6272
6294
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6273
6295
|
RDialog__namespace.Overlay,
|
|
@@ -6294,8 +6316,8 @@ function SModalContainer({
|
|
|
6294
6316
|
contentRef.current?.focus({ preventScroll: true });
|
|
6295
6317
|
},
|
|
6296
6318
|
onEscapeKeyDown: guardClose,
|
|
6297
|
-
onPointerDownOutside:
|
|
6298
|
-
onInteractOutside:
|
|
6319
|
+
onPointerDownOutside: guardInteractOutside,
|
|
6320
|
+
onInteractOutside: guardInteractOutside,
|
|
6299
6321
|
children: [
|
|
6300
6322
|
/* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Title, { className: "sr-only", children: ariaTitle }),
|
|
6301
6323
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -6599,6 +6621,177 @@ function SLoadingModal({
|
|
|
6599
6621
|
}
|
|
6600
6622
|
);
|
|
6601
6623
|
}
|
|
6624
|
+
var CLOSE_ANIMATION_DURATION2 = 300;
|
|
6625
|
+
function SDrawer({
|
|
6626
|
+
open,
|
|
6627
|
+
onOpenChange,
|
|
6628
|
+
onClose,
|
|
6629
|
+
persistent = false,
|
|
6630
|
+
title = "",
|
|
6631
|
+
width = 572,
|
|
6632
|
+
footerLeft,
|
|
6633
|
+
button,
|
|
6634
|
+
children,
|
|
6635
|
+
className,
|
|
6636
|
+
style
|
|
6637
|
+
}) {
|
|
6638
|
+
const [shaking, setShaking] = react.useState(false);
|
|
6639
|
+
const [rendered, setRendered] = react.useState(() => Boolean(open));
|
|
6640
|
+
const [entered, setEntered] = react.useState(false);
|
|
6641
|
+
const [portalContainer, setPortalContainer] = react.useState(null);
|
|
6642
|
+
const backdropPointerDownRef = react.useRef(false);
|
|
6643
|
+
const contentRef = react.useRef(null);
|
|
6644
|
+
const stackRegistrationRef = react.useRef(null);
|
|
6645
|
+
const [layerIndex, setLayerIndex] = react.useState(null);
|
|
6646
|
+
react.useEffect(() => {
|
|
6647
|
+
if (open) {
|
|
6648
|
+
setRendered(true);
|
|
6649
|
+
return;
|
|
6650
|
+
}
|
|
6651
|
+
if (!rendered) return;
|
|
6652
|
+
const timer = window.setTimeout(() => setRendered(false), CLOSE_ANIMATION_DURATION2);
|
|
6653
|
+
return () => window.clearTimeout(timer);
|
|
6654
|
+
}, [open, rendered]);
|
|
6655
|
+
react.useEffect(() => {
|
|
6656
|
+
if (!open) {
|
|
6657
|
+
setEntered(false);
|
|
6658
|
+
return;
|
|
6659
|
+
}
|
|
6660
|
+
setEntered(false);
|
|
6661
|
+
const frame = requestAnimationFrame(() => setEntered(true));
|
|
6662
|
+
return () => cancelAnimationFrame(frame);
|
|
6663
|
+
}, [open]);
|
|
6664
|
+
react.useEffect(() => {
|
|
6665
|
+
if (!open) return;
|
|
6666
|
+
const registration = registerModalStackEntry();
|
|
6667
|
+
stackRegistrationRef.current = registration;
|
|
6668
|
+
setLayerIndex(registration.layerIndex);
|
|
6669
|
+
return () => {
|
|
6670
|
+
registration.unregister();
|
|
6671
|
+
if (stackRegistrationRef.current === registration) stackRegistrationRef.current = null;
|
|
6672
|
+
};
|
|
6673
|
+
}, [open]);
|
|
6674
|
+
const triggerShake = react.useCallback(() => {
|
|
6675
|
+
setShaking(false);
|
|
6676
|
+
requestAnimationFrame(() => setShaking(true));
|
|
6677
|
+
}, []);
|
|
6678
|
+
const guardClose = persistent ? (event) => {
|
|
6679
|
+
event.preventDefault();
|
|
6680
|
+
if (stackRegistrationRef.current?.isTop()) triggerShake();
|
|
6681
|
+
} : void 0;
|
|
6682
|
+
const guardInteractOutside = (e) => {
|
|
6683
|
+
if (isFromGlobalOverlay(e)) {
|
|
6684
|
+
e.preventDefault();
|
|
6685
|
+
return;
|
|
6686
|
+
}
|
|
6687
|
+
guardClose?.(e);
|
|
6688
|
+
};
|
|
6689
|
+
const handleBackdropInteraction = () => {
|
|
6690
|
+
if (persistent) {
|
|
6691
|
+
if (stackRegistrationRef.current?.isTop()) triggerShake();
|
|
6692
|
+
return;
|
|
6693
|
+
}
|
|
6694
|
+
onOpenChange?.(false);
|
|
6695
|
+
};
|
|
6696
|
+
const modalHost = getModalHost();
|
|
6697
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Portal, { container: modalHost, forceMount: rendered ? true : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none absolute inset-0", style: { zIndex: layerIndex ?? 0 }, children: [
|
|
6698
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6699
|
+
RDialog__namespace.Overlay,
|
|
6700
|
+
{
|
|
6701
|
+
forceMount: rendered ? true : void 0,
|
|
6702
|
+
className: "pointer-events-auto absolute inset-0 bg-black/40 data-[state=open]:animate-overlay-in data-[state=closed]:animate-overlay-out",
|
|
6703
|
+
style: { zIndex: 0, animationFillMode: "forwards" },
|
|
6704
|
+
onPointerDown: (event) => {
|
|
6705
|
+
event.preventDefault();
|
|
6706
|
+
event.stopPropagation();
|
|
6707
|
+
backdropPointerDownRef.current = true;
|
|
6708
|
+
handleBackdropInteraction();
|
|
6709
|
+
},
|
|
6710
|
+
onClick: (event) => {
|
|
6711
|
+
event.stopPropagation();
|
|
6712
|
+
if (backdropPointerDownRef.current) {
|
|
6713
|
+
backdropPointerDownRef.current = false;
|
|
6714
|
+
return;
|
|
6715
|
+
}
|
|
6716
|
+
handleBackdropInteraction();
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6719
|
+
),
|
|
6720
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6721
|
+
RDialog__namespace.Content,
|
|
6722
|
+
{
|
|
6723
|
+
forceMount: rendered ? true : void 0,
|
|
6724
|
+
ref: contentRef,
|
|
6725
|
+
className: "pointer-events-auto absolute inset-y-0 right-0 w-fit max-w-full outline-none",
|
|
6726
|
+
onOpenAutoFocus: (event) => {
|
|
6727
|
+
event.preventDefault();
|
|
6728
|
+
contentRef.current?.focus({ preventScroll: true });
|
|
6729
|
+
},
|
|
6730
|
+
onEscapeKeyDown: guardClose,
|
|
6731
|
+
onPointerDownOutside: guardInteractOutside,
|
|
6732
|
+
onInteractOutside: guardInteractOutside,
|
|
6733
|
+
children: [
|
|
6734
|
+
/* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Title, { className: "sr-only", children: title || "Drawer" }),
|
|
6735
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6736
|
+
"div",
|
|
6737
|
+
{
|
|
6738
|
+
className: cn(
|
|
6739
|
+
"pointer-events-auto relative flex h-full max-w-full translate-x-full flex-col overflow-hidden bg-[var(--cmp-drawer-bg)] shadow-[var(--shadow-overlay)] transition-transform duration-300 ease-out data-[state=open]:translate-x-0 data-[state=closed]:translate-x-full",
|
|
6740
|
+
shaking && "animate-drawer-shake",
|
|
6741
|
+
className
|
|
6742
|
+
),
|
|
6743
|
+
"data-state": open && entered ? "open" : "closed",
|
|
6744
|
+
style: { width, ...style },
|
|
6745
|
+
onAnimationEnd: (event) => {
|
|
6746
|
+
if (event.animationName === "drawer-shake") setShaking(false);
|
|
6747
|
+
},
|
|
6748
|
+
children: [
|
|
6749
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "relative flex flex-shrink-0 items-center gap-[var(--cmp-overlay-header-gap)] bg-[var(--cmp-overlay-header-bg)] px-[var(--cmp-overlay-header-paddingX)] py-[var(--cmp-overlay-header-paddingY)]", children: [
|
|
6750
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 whitespace-nowrap text-[16px] font-bold leading-[26px] text-[color:var(--cmp-overlay-header-title-color)]", children: title }),
|
|
6751
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6752
|
+
SGhostButton,
|
|
6753
|
+
{
|
|
6754
|
+
icon: "close",
|
|
6755
|
+
ariaLabel: "\uB2EB\uAE30",
|
|
6756
|
+
onClick: () => {
|
|
6757
|
+
onClose?.();
|
|
6758
|
+
onOpenChange?.(false);
|
|
6759
|
+
},
|
|
6760
|
+
className: "absolute right-[12px] top-[12px]"
|
|
6761
|
+
}
|
|
6762
|
+
)
|
|
6763
|
+
] }),
|
|
6764
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto border-t border-solid border-[color:var(--cmp-drawer-body-border)]", children: /* @__PURE__ */ jsxRuntime.jsx(InsideModalProvider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(PortalContainerProvider, { value: portalContainer, children }) }) }),
|
|
6765
|
+
(button || footerLeft) && /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "flex flex-shrink-0 items-center justify-end gap-[var(--cmp-overlay-footer-gap)] bg-[var(--cmp-overlay-footer-bg-default)] px-[var(--cmp-overlay-footer-paddingX)] py-[var(--cmp-overlay-footer-paddingY)] shadow-[var(--shadow-overlay)]", children: [
|
|
6766
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 items-center justify-between", children: footerLeft }),
|
|
6767
|
+
button && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6768
|
+
SButton,
|
|
6769
|
+
{
|
|
6770
|
+
color: button.color ?? "primary",
|
|
6771
|
+
outline: button.outline,
|
|
6772
|
+
size: button.size ?? "md",
|
|
6773
|
+
disabled: button.disabled,
|
|
6774
|
+
onClick: () => button.onClick?.(),
|
|
6775
|
+
label: button.label ?? "\uD655\uC778"
|
|
6776
|
+
}
|
|
6777
|
+
)
|
|
6778
|
+
] })
|
|
6779
|
+
]
|
|
6780
|
+
}
|
|
6781
|
+
),
|
|
6782
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6783
|
+
"div",
|
|
6784
|
+
{
|
|
6785
|
+
ref: setPortalContainer,
|
|
6786
|
+
className: "pointer-events-auto fixed left-0 top-0",
|
|
6787
|
+
style: { zIndex: 2 }
|
|
6788
|
+
}
|
|
6789
|
+
)
|
|
6790
|
+
]
|
|
6791
|
+
}
|
|
6792
|
+
)
|
|
6793
|
+
] }) }) });
|
|
6794
|
+
}
|
|
6602
6795
|
var EXIT_DURATION = 300;
|
|
6603
6796
|
var EXIT_BUFFER = 100;
|
|
6604
6797
|
var SModalRefImpl = class {
|
|
@@ -6963,6 +7156,8 @@ var GNB_EXPAND_MS = 200;
|
|
|
6963
7156
|
var GNB_MENU_WIDTH = 240;
|
|
6964
7157
|
var GNB_FULL_LOGO_WIDTH = 140;
|
|
6965
7158
|
var GNB_TOP_ICON_GAP = 8;
|
|
7159
|
+
var GNB_TOP_ICON_SIZE = 24;
|
|
7160
|
+
var GNB_FULL_LOGO_WIDTH_NO_LAUNCHER = GNB_FULL_LOGO_WIDTH + GNB_TOP_ICON_SIZE + GNB_TOP_ICON_GAP;
|
|
6966
7161
|
function findGnbRailValue(items, value) {
|
|
6967
7162
|
if (items.some((item) => item.value === value)) return value;
|
|
6968
7163
|
return findGnbAncestors(items, value)[0];
|
|
@@ -7340,7 +7535,11 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
|
|
|
7340
7535
|
...header === "full" ? (
|
|
7341
7536
|
// full 은 전폭이라 로고 자리가 고정이다 — 폭 140px(디자인 스펙, 전용 토큰 없음)로 못 박아
|
|
7342
7537
|
// 로고 내용이 바뀌어도 오른쪽 topContent 시작점이 흔들리지 않게 한다.
|
|
7343
|
-
|
|
7538
|
+
// 런처가 없으면 그 자리(버튼 + 간격)를 로고가 이어받아, 런처 유무로 topContent 가 밀리지 않는다.
|
|
7539
|
+
{
|
|
7540
|
+
width: launcherButton ? GNB_FULL_LOGO_WIDTH : GNB_FULL_LOGO_WIDTH_NO_LAUNCHER,
|
|
7541
|
+
marginLeft: H.topGap
|
|
7542
|
+
}
|
|
7344
7543
|
) : (
|
|
7345
7544
|
// fix 는 폴드가 ml-auto 로 밀려나므로(margin-left 를 못 쓴다) 로고 오른쪽에 최소 간격을 둔다
|
|
7346
7545
|
{ marginRight: H.topGap }
|
|
@@ -7891,10 +8090,10 @@ function SStepper({
|
|
|
7891
8090
|
fill: "none",
|
|
7892
8091
|
xmlns: "http://www.w3.org/2000/svg",
|
|
7893
8092
|
children: [
|
|
7894
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "1.6", height: "1", fill: "
|
|
7895
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", width: "3.3", height: "1", fill: "
|
|
7896
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "11.7", width: "3.3", height: "1", fill: "
|
|
7897
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "18.4", width: "1.6", height: "1", fill: "
|
|
8093
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { width: "1.6", height: "1", fill: "currentColor" }),
|
|
8094
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", width: "3.3", height: "1", fill: "currentColor" }),
|
|
8095
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "11.7", width: "3.3", height: "1", fill: "currentColor" }),
|
|
8096
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "18.4", width: "1.6", height: "1", fill: "currentColor" })
|
|
7898
8097
|
]
|
|
7899
8098
|
}
|
|
7900
8099
|
)
|
|
@@ -8044,7 +8243,7 @@ var SField = /* @__PURE__ */ react.forwardRef(function SField2({
|
|
|
8044
8243
|
}
|
|
8045
8244
|
),
|
|
8046
8245
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
|
|
8047
|
-
(labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8246
|
+
(labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 flex items-center", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8048
8247
|
STooltip2,
|
|
8049
8248
|
{
|
|
8050
8249
|
message: labelTooltip !== "" ? [labelTooltip] : void 0,
|
|
@@ -11806,6 +12005,7 @@ var SToastContainer = /* @__PURE__ */ react.forwardRef(
|
|
|
11806
12005
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
11807
12006
|
"div",
|
|
11808
12007
|
{
|
|
12008
|
+
...globalOverlayProps,
|
|
11809
12009
|
style: {
|
|
11810
12010
|
position: "fixed",
|
|
11811
12011
|
zIndex,
|
|
@@ -12289,7 +12489,7 @@ var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
|
|
|
12289
12489
|
"span",
|
|
12290
12490
|
{
|
|
12291
12491
|
className: "s-chip-input__divider shrink-0",
|
|
12292
|
-
style: { width: 1, height: 12, backgroundColor: "
|
|
12492
|
+
style: { width: 1, height: 12, backgroundColor: "var(--color-divider-default)" }
|
|
12293
12493
|
}
|
|
12294
12494
|
),
|
|
12295
12495
|
showMaxCount && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -12351,7 +12551,7 @@ var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
|
|
|
12351
12551
|
role: "option",
|
|
12352
12552
|
"aria-selected": selected || active,
|
|
12353
12553
|
className: "rounded-full",
|
|
12354
|
-
style: active ? { outline: "2px solid
|
|
12554
|
+
style: active ? { outline: "2px solid var(--color-border-accent)", outlineOffset: 1 } : void 0,
|
|
12355
12555
|
onMouseDown: (e) => {
|
|
12356
12556
|
e.preventDefault();
|
|
12357
12557
|
if (!selected) addChip(item);
|
|
@@ -13482,6 +13682,7 @@ exports.SDraggableGroup = SDraggableGroup;
|
|
|
13482
13682
|
exports.SDraggableItem = SDraggableItem2;
|
|
13483
13683
|
exports.SDraggableList = SDraggableList;
|
|
13484
13684
|
exports.SDraggableProvider = SDraggableProvider;
|
|
13685
|
+
exports.SDrawer = SDrawer;
|
|
13485
13686
|
exports.SDropdownButton = SDropdownButton2;
|
|
13486
13687
|
exports.SExpansionItem = SExpansionItem;
|
|
13487
13688
|
exports.SExpansionList = SExpansionList2;
|