sellmate-design-system-react 9.0.0-beta.32 → 9.0.0-beta.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1618,7 +1618,7 @@ export default function AppShell({
1618
1618
  ```tsx
1619
1619
  {/* 폭을 기억해야 하면 menuWidth 를 앱이 쥐고 onMenuWidthChange 로 되받아 저장한다.
1620
1620
  초기값만 정하면 되면 defaultMenuWidth 하나로 끝난다. */}
1621
- <SGnb items={MENU} value={current} onValueChange={navigate} resizable defaultMenuWidth={240} />
1621
+ <SGnb items={MENU} value={current} onValueChange={navigate} resizable defaultMenuWidth={220} />
1622
1622
  ```
1623
1623
 
1624
1624
  `onMenuWidthChange` 는 **드래그를 놓는 순간**(또는 방향키 조작) 한 번만 온다 — 저장 로직을 그대로 붙여도 프레임마다 쓰이지 않는다. 접혀 있거나 레일 리프가 활성이라 깔 메뉴가 없으면 조절선은 나오지 않는다.
@@ -1739,7 +1739,12 @@ import { SModalOutlet } from 'sellmate-design-system-react';
1739
1739
  - **`header="fix"` 를 접으면 알림·계정 아이콘만 남는다** — `actions`·`domain`·`setting` 은 48px 폭에 놓을 자리가 없어 렌더되지 않고, 알림 개수는 점 배지가 된다. **알림도 계정도 안 쓰는 조합이면 접힌 패널은 아예 렌더되지 않는다** — 접힌 상태에서도 반드시 눌러야 하는 것이 그 둘 밖에 있으면 `foldedTop`/`foldedFooter` 로 따로 준다.
1740
1740
  - **`header="full"` 은 접어도 상단바가 남으므로 조각들도 그대로 남는다.** 대신 가로로 자리가 넉넉하지 않아 알림·설정이 라벨을 벗고 아이콘(알림은 개수까지)만 남는다 — 이건 컴포넌트가 알아서 하므로 앱이 라벨을 지우지 않는다.
1741
1741
  - **`account` 를 쓰면 `account.listBox` 가 필수다** (§3-5-8) — 계정 행을 누르면 이메일·이름·권한과 계정 설정·언어 변경·로그아웃이 예외 없이 뜬다. 뜨는 방향은 판이 놓인 자리가 정하므로 앱이 넘기지 않는다 — 판이면 계정 행 위로, 상단바면 아래로 펼치고, 접힌 레일도 판과 같다. `account.onClick` 은 패널을 여는 것 말고 따로 할 일(로깅 등)이 있을 때만 준다.
1742
- - **알림 개수는 `alert.count` 하나로 표현한다.** 1 이상이면 벨이 울리는 아이콘과 강조색으로 바뀐다 — 색은 GNB 색이 정하므로 앱이 직접 칠하지 않는다.
1742
+ - **알림 개수는 `alert.count` 하나로 표현한다.** 1 이상이면 벨이 울리는 아이콘과 강조색으로 바뀐다 — 색은 GNB 색이 정하므로 앱이 직접 칠하지 않는다. **`resizable` 로 메뉴를 기본 폭보다 좁게 줄이면 개수를 적을 가로가 모자라 접힘과 같은 점 배지로 바뀐다** — 컴포넌트가 알아서 하므로 앱이 폭을 보고 `count` 를 지우지 않는다.
1743
+ - **알림을 눌러 뜨는 목록은 `alert.listBox` 에 내용만 넘긴다** — `SPortal` 을 따로 띄워 알림 버튼에 붙이지 않는다. 목록에 무엇을 쌓을지는 서비스마다 다르지만 **뜨는 자리는 늘 같아야 하므로**, 방향(판·접힌 레일은 버튼 오른쪽 · 상단바는 아래로 오른쪽 끝)과 바깥 클릭·ESC 닫힘, 배경·라운드·그림자는 컴포넌트가 쥔다. 여닫는 일도 알림 버튼이 하므로 **`onOpenChange` 는 그 사실을 알아야 할 때만** 받는다(연 김에 읽음 처리하는 화면 등) — 이 값으로 목록을 대신 열거나 막지 않는다. 계정 행(`account.listBox`)과 같은 규칙이다.
1744
+ - **알림에 안내 툴팁을 띄우려면 `alert.tooltip` 에 문구만 넘긴다** — `STooltip` 을 직접 세워 알림 버튼에 붙이지 않는다. 색(판이 light 면 기본 · 그 밖은 강조)과 방향(판·접힌 레일은 오른쪽 · 상단바는 아래)은 자리가 정하고, 스스로 사라지지 않다가 **알림을 누르거나 버튼에서 커서를 뺄 때** 닫힌다.
1745
+ - **닫힘은 `onTooltipDismiss` 로 받아 `tooltip` 을 비워 둔다.** 같은 값을 계속 넘기는 동안에는 다시 뜨지 않고, **비웠다 다시 넘기면 문구가 같아도 새 안내로 뜬다** — 새 알림마다 같은 안내를 다시 띄우는 화면이 막히지 않는다.
1746
+ - 재노출 신호는 문구가 **없음 → 있음** 으로 바뀌는 것이다. 한 이벤트 핸들러 안에서 비웠다 곧바로 넣으면 렌더가 한 번으로 묶여 신호가 되지 않는다 — 닫을 때 비우고, 다시 필요할 때 넘긴다.
1747
+ - 컴포넌트가 아는 범위는 그 자리가 서 있는 동안뿐이다(새로고침하거나 GNB 를 접어 알림이 다른 자리로 옮겨 서면 새로 마운트된다). **그 사용자에게 두 번 다시 보이지 않아야 하면 앱이 닫힘을 저장해 두고 `tooltip` 을 넘기지 않는다.**
1743
1748
  - **`header="fix"` + `useRail` 에서 하위 메뉴가 없는 레일 아이템이 활성이면 메뉴 컬럼째 사라지므로 이 패널도 함께 빠진다.** 항상 보여야 하는 계정·알림이라면 그런 레일 리프를 두지 않는다 — 또는 `header="full"` 로 두면 상단바에 남는다(그런 레일 리프가 있으면 GNB 가 `header` 를 `full` 로 강제한다).
1744
1749
 
1745
1750
  ### 4-2. 목록 페이지 (필터 + 테이블)
@@ -32,6 +32,12 @@ export interface SGnbSystemContextValue {
32
32
  * 것을 스스로 닫아야 하고, 그 신호가 이 값이다.
33
33
  */
34
34
  active: boolean;
35
+ /**
36
+ * 이 자리가 메뉴 기본 폭(GNB_MENU_WIDTH)보다 좁은가 — `resizable` 로 줄인 메뉴 컬럼이 그렇다.
37
+ * 좁아진 판에서는 알림 개수를 적을 가로가 모자라 개수 대신 점 배지가 붙는다.
38
+ * 없으면 좁지 않은 것으로 본다(접힘 레일·상단바는 자기 규칙을 따르므로 이 값과 무관하다).
39
+ */
40
+ narrow?: boolean;
35
41
  }
36
42
  export declare const SGnbSystemContext: import("react").Context<SGnbSystemContextValue | null>;
37
43
  export interface SGnbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'> {
@@ -116,13 +116,13 @@ export declare const GNB_EXPAND_MS = 200;
116
116
  * 메뉴 고정 폭. 두 header 모두 메뉴는 이 폭으로 좌측에 붙는다(토큰 없음 → 상수).
117
117
  * header="full" 에서 SLayout 이 "메뉴 오른쪽 = 페이지" 영역을 잡을 때도 이 값을 쓴다.
118
118
  */
119
- export declare const GNB_MENU_WIDTH = 240;
119
+ export declare const GNB_MENU_WIDTH = 214;
120
120
  /**
121
121
  * resizable 일 때 메뉴 폭이 움직일 수 있는 범위(px). 레일 폭은 고정이고 메뉴 컬럼만 늘고 준다.
122
- * 디자인이 아직 확정한 값이 아니라 임시다 확정되면 토큰으로 내려온다.
122
+ * 기본 폭(GNB_MENU_WIDTH)이 범위 안에 있다. 대응 토큰이 없어 상수로 둔다.
123
123
  */
124
124
  export declare const GNB_MENU_MIN_WIDTH = 200;
125
- export declare const GNB_MENU_MAX_WIDTH = 300;
125
+ export declare const GNB_MENU_MAX_WIDTH = 240;
126
126
  /** 메뉴 폭 조절에서 방향키 한 번에 움직이는 양(px). Shift 를 함께 누르면 large. */
127
127
  export declare const GNB_MENU_RESIZE_KEY_STEP: {
128
128
  normal: number;
@@ -52,12 +52,50 @@ export interface SGnbSystemAlert {
52
52
  /** 라벨 텍스트. 기본 `'알림'` */
53
53
  label?: string;
54
54
  /**
55
- * 읽지 않은 알림 개수. 1 이상이면 벨이 울리는 아이콘·강조색으로 바뀌고 개수(접히면 점)가 붙는다.
55
+ * 읽지 않은 알림 개수. 1 이상이면 벨이 울리는 아이콘·강조색으로 바뀌고 개수가 붙는다.
56
+ * 개수를 적을 가로가 모자란 자리(접힌 레일 · 기본 폭보다 좁게 줄인 판)에서는 점 배지로 바뀐다.
56
57
  * 표기는 컴포넌트가 맡는다 — 세 자리마다 콤마를 넣어 그리므로 앱이 포맷한 문자열을 넘기지 않는다.
57
58
  */
58
59
  count?: number;
59
60
  /** 클릭 */
60
61
  onClick?: () => void;
62
+ /**
63
+ * 알림 버튼에 붙는 안내 툴팁의 문구(한 줄이 한 항목). 주면 뜬다 — **문구만 앱이 정하고**
64
+ * 색·방향·여닫힘은 컴포넌트가 쥔다: 색은 판이 light 면 기본, 그 밖(dark·상단바)이면 강조색이고
65
+ * 방향은 자리가 정한다(판·접힌 레일이면 오른쪽, 상단바면 아래).
66
+ *
67
+ * 스스로 사라지지 않는다 — **알림 버튼을 누르거나, 버튼에 얹었다 커서를 뺄 때** 닫히고,
68
+ * 같은 값을 계속 넘기는 동안에는 다시 뜨지 않는다.
69
+ *
70
+ * **다시 띄우려면 비웠다가 다시 넘긴다**(`undefined` → 문구). 문구가 같아도 상관없다 —
71
+ * 컴포넌트는 닫았다는 사실만 기억하고 문구 내용은 기억하지 않으므로, 새 알림마다 같은 안내를
72
+ * 다시 띄우는 화면도 막히지 않는다.
73
+ */
74
+ tooltip?: string[];
75
+ /**
76
+ * 알림을 누르면 뜨는 목록의 **내용**. 주면 컴포넌트가 알림 버튼에 붙여 띄운다 —
77
+ * 목록에 무엇을 쌓을지는 서비스마다 다르지만 **뜨는 자리는 늘 같아야 하므로**, 방향·앵커·
78
+ * 여닫힘(바깥 클릭·ESC 포함)과 배경·라운드·그림자는 컴포넌트가 쥐고 앱은 안에 담을 것만 준다.
79
+ * 방향은 자리가 정한다 — 판·접힌 레일이면 버튼 오른쪽, 상단바면 아래.
80
+ */
81
+ listBox?: ReactNode;
82
+ /**
83
+ * 알림 목록이 열리고 닫힐 때. 여닫는 것은 알림 버튼이 스스로 하므로 **그 사실을 알아야 할
84
+ * 때만** 준다(연 김에 읽음 처리하거나, 열려 있는 동안 다른 층을 접어 두는 화면).
85
+ *
86
+ * 앱이 누르지 않은 닫힘도 온다: GNB 를 접거나 펴면 알림 버튼이 다른 자리로 옮겨 서므로
87
+ * 목록이 닫히고 `false` 가 온다(계정 행과 같다).
88
+ */
89
+ onOpenChange?: (open: boolean) => void;
90
+ /**
91
+ * 안내 툴팁이 닫힐 때. 받아서 `tooltip` 을 비워 두는 것이 기본 사용법이다 — 그러면 앱이
92
+ * 다음에 넘기는 문구가 곧 다음 안내가 된다.
93
+ *
94
+ * 컴포넌트가 아는 "다시 뜨지 않는다" 는 **이 자리가 서 있는 동안**뿐이라(새로고침하거나
95
+ * GNB 를 접어 알림이 다른 자리로 옮겨 서면 새로 마운트된다), 그 사용자에게 두 번 다시 보이지
96
+ * 않아야 하면 앱이 이 신호를 저장해 두고 `tooltip` 을 그만 넘긴다.
97
+ */
98
+ onTooltipDismiss?: () => void;
61
99
  }
62
100
  ```
63
101
 
@@ -130,7 +168,9 @@ export const GNB_SYSTEM_COLORS = ['light', 'dark'] as const;
130
168
  - [SAccountListBox](../SAccountListBox)
131
169
  - [SGnb](../SGnb)
132
170
  - [SIcon](../SIcon)
171
+ - [SPortal](../SPortal)
133
172
  - [SSystemActionButton](../SSystemActionButton)
173
+ - [STooltip](../STooltip)
134
174
 
135
175
  ### Graph
136
176
 
@@ -139,6 +179,8 @@ graph TD;
139
179
  SGnbSystem --> SAccountListBox
140
180
  SGnbSystem --> SGnb
141
181
  SGnbSystem --> SIcon
182
+ SGnbSystem --> SPortal
142
183
  SGnbSystem --> SSystemActionButton
184
+ SGnbSystem --> STooltip
143
185
  style SGnbSystem fill:#f9f,stroke:#333,stroke-width:4px
144
186
  ```
@@ -1,4 +1,4 @@
1
- import { type HTMLAttributes } from 'react';
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
2
  import { type SAccountListBoxContent } from '../SAccountListBox';
3
3
  import { type SSystemActionButtonProps } from '../SSystemActionButton';
4
4
  import { type SGnbSystemColor } from './gnbSystem.config';
@@ -23,12 +23,50 @@ export interface SGnbSystemAlert {
23
23
  /** 라벨 텍스트. 기본 `'알림'` */
24
24
  label?: string;
25
25
  /**
26
- * 읽지 않은 알림 개수. 1 이상이면 벨이 울리는 아이콘·강조색으로 바뀌고 개수(접히면 점)가 붙는다.
26
+ * 읽지 않은 알림 개수. 1 이상이면 벨이 울리는 아이콘·강조색으로 바뀌고 개수가 붙는다.
27
+ * 개수를 적을 가로가 모자란 자리(접힌 레일 · 기본 폭보다 좁게 줄인 판)에서는 점 배지로 바뀐다.
27
28
  * 표기는 컴포넌트가 맡는다 — 세 자리마다 콤마를 넣어 그리므로 앱이 포맷한 문자열을 넘기지 않는다.
28
29
  */
29
30
  count?: number;
30
31
  /** 클릭 */
31
32
  onClick?: () => void;
33
+ /**
34
+ * 알림 버튼에 붙는 안내 툴팁의 문구(한 줄이 한 항목). 주면 뜬다 — **문구만 앱이 정하고**
35
+ * 색·방향·여닫힘은 컴포넌트가 쥔다: 색은 판이 light 면 기본, 그 밖(dark·상단바)이면 강조색이고
36
+ * 방향은 자리가 정한다(판·접힌 레일이면 오른쪽, 상단바면 아래).
37
+ *
38
+ * 스스로 사라지지 않는다 — **알림 버튼을 누르거나, 버튼에 얹었다 커서를 뺄 때** 닫히고,
39
+ * 같은 값을 계속 넘기는 동안에는 다시 뜨지 않는다.
40
+ *
41
+ * **다시 띄우려면 비웠다가 다시 넘긴다**(`undefined` → 문구). 문구가 같아도 상관없다 —
42
+ * 컴포넌트는 닫았다는 사실만 기억하고 문구 내용은 기억하지 않으므로, 새 알림마다 같은 안내를
43
+ * 다시 띄우는 화면도 막히지 않는다.
44
+ */
45
+ tooltip?: string[];
46
+ /**
47
+ * 알림을 누르면 뜨는 목록의 **내용**. 주면 컴포넌트가 알림 버튼에 붙여 띄운다 —
48
+ * 목록에 무엇을 쌓을지는 서비스마다 다르지만 **뜨는 자리는 늘 같아야 하므로**, 방향·앵커·
49
+ * 여닫힘(바깥 클릭·ESC 포함)과 배경·라운드·그림자는 컴포넌트가 쥐고 앱은 안에 담을 것만 준다.
50
+ * 방향은 자리가 정한다 — 판·접힌 레일이면 버튼 오른쪽, 상단바면 아래.
51
+ */
52
+ listBox?: ReactNode;
53
+ /**
54
+ * 알림 목록이 열리고 닫힐 때. 여닫는 것은 알림 버튼이 스스로 하므로 **그 사실을 알아야 할
55
+ * 때만** 준다(연 김에 읽음 처리하거나, 열려 있는 동안 다른 층을 접어 두는 화면).
56
+ *
57
+ * 앱이 누르지 않은 닫힘도 온다: GNB 를 접거나 펴면 알림 버튼이 다른 자리로 옮겨 서므로
58
+ * 목록이 닫히고 `false` 가 온다(계정 행과 같다).
59
+ */
60
+ onOpenChange?: (open: boolean) => void;
61
+ /**
62
+ * 안내 툴팁이 닫힐 때. 받아서 `tooltip` 을 비워 두는 것이 기본 사용법이다 — 그러면 앱이
63
+ * 다음에 넘기는 문구가 곧 다음 안내가 된다.
64
+ *
65
+ * 컴포넌트가 아는 "다시 뜨지 않는다" 는 **이 자리가 서 있는 동안**뿐이라(새로고침하거나
66
+ * GNB 를 접어 알림이 다른 자리로 옮겨 서면 새로 마운트된다), 그 사용자에게 두 번 다시 보이지
67
+ * 않아야 하면 앱이 이 신호를 저장해 두고 `tooltip` 을 그만 넘긴다.
68
+ */
69
+ onTooltipDismiss?: () => void;
32
70
  }
33
71
  /** 설정 버튼 */
34
72
  export interface SGnbSystemSetting {
@@ -121,6 +159,14 @@ export interface SGnbSystemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'c
121
159
  * `alert.count` 가 1 이상이면 벨이 울리는 아이콘(`bellRinging`)과 강조색으로 바뀐다 — light 는 파랑,
122
160
  * 어두운 면은 노랑(토큰이 정한다). 0 이거나 없으면 조용한 벨(`bell`)에 기본 색이다.
123
161
  *
162
+ * 안내 툴팁(`alert.tooltip`)은 문구를 받은 동안 떠 있다가 알림을 누르거나 커서를 빼면 닫힌다.
163
+ * 다시 띄우는 신호는 문구가 **없음 → 있음** 으로 바뀌는 것이라, 같은 문구도 비웠다 다시 넘기면
164
+ * 새 안내로 뜬다.
165
+ *
166
+ * 판이 **메뉴 기본 폭보다 좁으면**(GNB 의 `resizable` 로 줄인 컬럼) 알림 개수를 적을 가로가
167
+ * 모자라 접힘과 같은 점 배지로 바뀐다 — 숫자가 설정 버튼을 밀어내거나 잘리는 대신, 알림이
168
+ * 있다는 사실만 남긴다. 좁은지는 담은 SGnb 가 내려주므로 앱이 고르는 prop 이 없다.
169
+ *
124
170
  * 색은 놓인 면을 따른다: 판 위는 `light`/`dark`(오션블루 판), 판 없이 상단바 면에 바로 얹는 `top` 의
125
171
  * dark 는 한 단계 어두운 `darker`(네이비) 색표다 — GNB_SYSTEM_TOP_COLOR_CONFIG 가 그 대응을 쥔다.
126
172
  *
@@ -54,6 +54,7 @@ export type SPortalAlign = 'start' | 'center' | 'end';
54
54
 
55
55
  - [SAccountListBox](../SAccountListBox)
56
56
  - [SChipInput](../SChipInput)
57
+ - [SGnbSystem](../SGnbSystem)
57
58
  - [SSelect](../SSelect)
58
59
 
59
60
  ### Graph
@@ -62,6 +63,7 @@ export type SPortalAlign = 'start' | 'center' | 'end';
62
63
  graph TD;
63
64
  SAccountListBox --> SPortal
64
65
  SChipInput --> SPortal
66
+ SGnbSystem --> SPortal
65
67
  SSelect --> SPortal
66
68
  style SPortal fill:#f9f,stroke:#333,stroke-width:4px
67
69
  ```
@@ -66,6 +66,7 @@ export type STooltipType = 'default' | 'danger' | 'warning' | 'accent';
66
66
 
67
67
  - [SChipFilter](../SChipFilter)
68
68
  - [SField](../SField)
69
+ - [SGnbSystem](../SGnbSystem)
69
70
  - [SKeyValueTable](../SKeyValueTable)
70
71
  - [SSectionHeaderCard](../SSectionHeaderCard)
71
72
  - [SStepper](../SStepper)
@@ -84,6 +85,7 @@ graph TD;
84
85
  STooltip --> SIcon
85
86
  SChipFilter --> STooltip
86
87
  SField --> STooltip
88
+ SGnbSystem --> STooltip
87
89
  SKeyValueTable --> STooltip
88
90
  SSectionHeaderCard --> STooltip
89
91
  SStepper --> STooltip
package/dist/index.cjs CHANGED
@@ -9270,9 +9270,9 @@ var GNB_RAIL = {
9270
9270
  var GNB_RAIL_WIDTH = 68;
9271
9271
  var GNB_FOLD_MS = 200;
9272
9272
  var GNB_EXPAND_MS = 200;
9273
- var GNB_MENU_WIDTH = 240;
9273
+ var GNB_MENU_WIDTH = 214;
9274
9274
  var GNB_MENU_MIN_WIDTH = 200;
9275
- var GNB_MENU_MAX_WIDTH = 300;
9275
+ var GNB_MENU_MAX_WIDTH = 240;
9276
9276
  var GNB_MENU_RESIZE_KEY_STEP = { normal: 10, large: 50 };
9277
9277
  var clampGnbMenuWidth = (width) => Math.min(GNB_MENU_MAX_WIDTH, Math.max(GNB_MENU_MIN_WIDTH, Math.round(width)));
9278
9278
  var GNB_FULL_LOGO_WIDTH = 140;
@@ -9830,8 +9830,15 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
9830
9830
  "topContent"
9831
9831
  ) : null;
9832
9832
  const systemMenuContext = react.useMemo(
9833
- () => ({ color, folded: false, placement: "panel", active: !railFolded }),
9834
- [color, railFolded]
9833
+ // 기본 폭보다 줄여 놓은 메뉴 컬럼이면 판에 알린다 알림 개수가 점 배지로 줄어든다.
9834
+ () => ({
9835
+ color,
9836
+ folded: false,
9837
+ placement: "panel",
9838
+ active: !railFolded,
9839
+ narrow: menuWidth < GNB_MENU_WIDTH
9840
+ }),
9841
+ [color, railFolded, menuWidth]
9835
9842
  );
9836
9843
  const systemFoldedContext = react.useMemo(
9837
9844
  () => ({ color, folded: true, placement: "panel", active: true }),
@@ -10098,7 +10105,7 @@ var GNB_SYSTEM_SETTING_LABEL = "\uC124\uC815";
10098
10105
  function TokenIcon3({ name, size }) {
10099
10106
  return /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name, size: "100%", style: { width: size, height: size } });
10100
10107
  }
10101
- function SystemButton({
10108
+ var SystemButton = /* @__PURE__ */ react.forwardRef(function SystemButton2({
10102
10109
  color,
10103
10110
  icon,
10104
10111
  label,
@@ -10110,21 +10117,22 @@ function SystemButton({
10110
10117
  dot,
10111
10118
  accentColor,
10112
10119
  stretch,
10113
- buttonRef,
10114
10120
  hasPopup,
10115
10121
  expanded,
10116
- onClick
10117
- }) {
10122
+ onClick,
10123
+ onMouseLeave
10124
+ }, ref) {
10118
10125
  const c = SYSTEM_ACTION_BUTTON_COLOR_CONFIG[color];
10119
10126
  return /* @__PURE__ */ jsxRuntime.jsxs(
10120
10127
  "button",
10121
10128
  {
10122
- ref: buttonRef,
10129
+ ref,
10123
10130
  type: "button",
10124
10131
  "aria-label": label ? void 0 : ariaLabel,
10125
10132
  "aria-haspopup": hasPopup ? "dialog" : void 0,
10126
10133
  "aria-expanded": hasPopup ? expanded : void 0,
10127
10134
  onClick,
10135
+ onMouseLeave,
10128
10136
  className: cn(
10129
10137
  "group box-border flex cursor-pointer items-center border-none bg-(--gsb-bg) text-(--gsb-text) transition-colors hover:bg-(--gsb-bg-hover)",
10130
10138
  stretch ? "min-w-0 flex-1" : "shrink-0",
@@ -10175,7 +10183,7 @@ function SystemButton({
10175
10183
  ]
10176
10184
  }
10177
10185
  );
10178
- }
10186
+ });
10179
10187
  var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10180
10188
  color: colorProp,
10181
10189
  folded: foldedProp,
@@ -10194,6 +10202,7 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10194
10202
  const isTop = placement === "top";
10195
10203
  const folded = !isTop && (foldedProp ?? gnb2?.folded ?? false);
10196
10204
  const active = gnb2?.active ?? true;
10205
+ const narrow = !isTop && !folded && (gnb2?.narrow ?? false);
10197
10206
  const c = GNB_SYSTEM_COLOR_CONFIG[color];
10198
10207
  const t = GNB_SYSTEM_TOP_COLOR_CONFIG[color];
10199
10208
  const itemColor = isTop ? t.item : color;
@@ -10212,13 +10221,39 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10212
10221
  setAccountOpen(next);
10213
10222
  account?.onOpenChange?.(next);
10214
10223
  };
10224
+ const alertButtonRef = react.useRef(null);
10225
+ const [alertListOpen, setAlertListOpen] = react.useState(false);
10226
+ const hasAlertList = alert2?.listBox != null;
10227
+ const commitAlertListOpen = (next) => {
10228
+ setAlertListOpen(next);
10229
+ alert2?.onOpenChange?.(next);
10230
+ };
10215
10231
  react.useEffect(() => {
10216
10232
  if (active || !accountOpen) return;
10217
10233
  setAccountOpen(false);
10218
10234
  account?.onOpenChange?.(false);
10219
10235
  }, [active, accountOpen, account]);
10236
+ react.useEffect(() => {
10237
+ if (active || !alertListOpen) return;
10238
+ setAlertListOpen(false);
10239
+ alert2?.onOpenChange?.(false);
10240
+ }, [active, alertListOpen, alert2]);
10220
10241
  const alertCount = alert2?.count ?? 0;
10221
10242
  const notified = alertCount > 0;
10243
+ const alertTooltip = (alert2?.tooltip ?? []).filter((line) => line.trim() !== "");
10244
+ const hasAlertTooltip = alertTooltip.length > 0;
10245
+ const [alertTooltipDismissed, setAlertTooltipDismissed] = react.useState(false);
10246
+ const hadAlertTooltip = react.useRef(hasAlertTooltip);
10247
+ if (hadAlertTooltip.current !== hasAlertTooltip) {
10248
+ hadAlertTooltip.current = hasAlertTooltip;
10249
+ if (hasAlertTooltip && alertTooltipDismissed) setAlertTooltipDismissed(false);
10250
+ }
10251
+ const alertTooltipOpen = hasAlertTooltip && active && !alertTooltipDismissed;
10252
+ const dismissAlertTooltip = () => {
10253
+ if (!alertTooltipOpen) return;
10254
+ setAlertTooltipDismissed(true);
10255
+ alert2?.onTooltipDismiss?.();
10256
+ };
10222
10257
  const alertLabel = alert2?.label ?? GNB_SYSTEM_ALERT_LABEL;
10223
10258
  const settingLabel = setting?.label ?? GNB_SYSTEM_SETTING_LABEL;
10224
10259
  const alertButton = alert2 && /* @__PURE__ */ jsxRuntime.jsx(
@@ -10231,13 +10266,51 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10231
10266
  paddingX: GNB_SYSTEM_LAYOUT.alertPaddingX,
10232
10267
  iconColor: notified ? alertIconColor : void 0,
10233
10268
  hoverBg: notified ? alertHoverBg : void 0,
10234
- count: folded ? void 0 : alertCount,
10235
- dot: folded && notified,
10269
+ count: folded || narrow ? void 0 : alertCount,
10270
+ dot: (folded || narrow) && notified,
10236
10271
  accentColor: alertCountColor,
10237
10272
  stretch: !folded && !isTop,
10238
- onClick: alert2.onClick
10273
+ ref: alertButtonRef,
10274
+ hasPopup: hasAlertList,
10275
+ expanded: alertListOpen,
10276
+ onClick: () => {
10277
+ dismissAlertTooltip();
10278
+ alert2.onClick?.();
10279
+ if (hasAlertList) commitAlertListOpen(!alertListOpen);
10280
+ },
10281
+ onMouseLeave: hasAlertTooltip ? dismissAlertTooltip : void 0
10239
10282
  }
10240
10283
  );
10284
+ const alertListBox = alert2 && hasAlertList && /* @__PURE__ */ jsxRuntime.jsx(
10285
+ SPortal,
10286
+ {
10287
+ open: alertListOpen && active,
10288
+ onClose: () => commitAlertListOpen(false),
10289
+ anchorRef: alertButtonRef,
10290
+ placement: isTop ? "bottom" : "right",
10291
+ align: "end",
10292
+ offset: 8,
10293
+ viewportPadding: 8,
10294
+ ariaLabel: alert2.label ?? GNB_SYSTEM_ALERT_LABEL,
10295
+ onOpenAutoFocus: (event) => event.preventDefault(),
10296
+ className: "rounded-(--radius-8)",
10297
+ children: alert2.listBox
10298
+ }
10299
+ );
10300
+ const alertNode = alertButton && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10301
+ hasAlertTooltip ? /* @__PURE__ */ jsxRuntime.jsx(
10302
+ STooltip2,
10303
+ {
10304
+ trigger: "none",
10305
+ open: alertTooltipOpen,
10306
+ message: alertTooltip,
10307
+ tooltipType: color === "light" ? "default" : "accent",
10308
+ placement: isTop ? "bottom" : "right",
10309
+ children: alertButton
10310
+ }
10311
+ ) : alertButton,
10312
+ alertListBox
10313
+ ] });
10241
10314
  const settingButton = setting && /* @__PURE__ */ jsxRuntime.jsx(
10242
10315
  SystemButton,
10243
10316
  {
@@ -10293,7 +10366,7 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10293
10366
  )
10294
10367
  })
10295
10368
  );
10296
- if (alertButton) segments.push({ key: "alert", node: alertButton });
10369
+ if (alertNode) segments.push({ key: "alert", node: alertNode });
10297
10370
  if (settingButton) segments.push({ key: "setting", node: settingButton });
10298
10371
  if (domainTag || accountButton) {
10299
10372
  segments.push({
@@ -10351,7 +10424,7 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10351
10424
  "data-folded": "",
10352
10425
  ...rest,
10353
10426
  children: [
10354
- alertButton,
10427
+ alertNode,
10355
10428
  account && /* @__PURE__ */ jsxRuntime.jsx(
10356
10429
  SystemButton,
10357
10430
  {
@@ -10359,7 +10432,7 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10359
10432
  icon: "user",
10360
10433
  ariaLabel: account.label ?? "account",
10361
10434
  paddingX: GNB_SYSTEM_LAYOUT.alertPaddingX,
10362
- buttonRef: foldedAccountRef,
10435
+ ref: foldedAccountRef,
10363
10436
  hasPopup: true,
10364
10437
  expanded: accountOpen,
10365
10438
  onClick: () => {
@@ -10405,7 +10478,7 @@ var SGnbSystem = /* @__PURE__ */ react.forwardRef(function SGnbSystem2({
10405
10478
  domainTag,
10406
10479
  hasUserBlock && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
10407
10480
  (alert2 || setting) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", style: { gap: GNB_SYSTEM_LAYOUT.gap }, children: [
10408
- alertButton,
10481
+ alertNode,
10409
10482
  alert2 && setting && /* @__PURE__ */ jsxRuntime.jsx(
10410
10483
  "span",
10411
10484
  {