sellmate-design-system-react 0.4.0 → 0.5.0
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/dist/components/SGnb/SGnb.cjs +238 -79
- package/dist/components/SGnb/SGnb.cjs.map +1 -1
- package/dist/components/SGnb/SGnb.d.ts +17 -4
- package/dist/components/SGnb/SGnb.js +238 -79
- package/dist/components/SGnb/SGnb.js.map +1 -1
- package/dist/components/SGnb/gnb.config.d.ts +46 -12
- package/dist/components/SGnb/index.d.ts +1 -1
- package/dist/components/SLayout/SLayout.cjs +20 -19
- package/dist/components/SLayout/SLayout.cjs.map +1 -1
- package/dist/components/SLayout/SLayout.d.ts +4 -0
- package/dist/components/SLayout/SLayout.js +20 -19
- package/dist/components/SLayout/SLayout.js.map +1 -1
- package/dist/components/SPage/SPage.cjs +21 -20
- package/dist/components/SPage/SPage.cjs.map +1 -1
- package/dist/components/SPage/SPage.js +21 -20
- package/dist/components/SPage/SPage.js.map +1 -1
- package/dist/components/STabs/STabs.cjs +15 -26
- package/dist/components/STabs/STabs.cjs.map +1 -1
- package/dist/components/STabs/STabs.d.ts +1 -1
- package/dist/components/STabs/STabs.js +15 -26
- package/dist/components/STabs/STabs.js.map +1 -1
- package/dist/components/STimePicker/STimePicker.cjs.map +1 -1
- package/dist/components/STimePicker/STimePicker.js.map +1 -1
- package/dist/components/STimeRangePicker/STimeRangePicker.cjs +3 -1
- package/dist/components/STimeRangePicker/STimeRangePicker.cjs.map +1 -1
- package/dist/components/STimeRangePicker/STimeRangePicker.js +3 -1
- package/dist/components/STimeRangePicker/STimeRangePicker.js.map +1 -1
- package/dist/index.cjs +261 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +258 -108
- package/dist/index.js.map +1 -1
- package/dist/styles.css +12 -4
- package/dist/theme.css +1 -1
- package/package.json +1 -1
|
@@ -7,17 +7,24 @@ export interface SGnbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'>
|
|
|
7
7
|
header?: SGnbHeader;
|
|
8
8
|
/** 색상 테마: light / dark */
|
|
9
9
|
color?: SGnbColor;
|
|
10
|
-
/** 메뉴 트리 (depth1 → depth2 → depth3) */
|
|
10
|
+
/** 메뉴 트리 (depth1 → depth2 → depth3). useRail 이면 첫 depth 가 레일이 되고 그 children 이 depth1 이 된다. */
|
|
11
11
|
items: SGnbMenuItem[];
|
|
12
|
+
/**
|
|
13
|
+
* 좌측 레일 사용 여부. 켜면 items 의 첫 depth 가 아이콘+라벨 버튼의 세로 레일로 서고,
|
|
14
|
+
* 선택된 레일 아이템의 children 이 오른쪽 메뉴 패널에 깔린다. 미지정 시 SLayout 의 useRail 을 따른다.
|
|
15
|
+
*/
|
|
16
|
+
useRail?: boolean;
|
|
12
17
|
/** 현재 선택된 아이템 value */
|
|
13
18
|
value?: string;
|
|
14
19
|
/** 선택 변경 (sdUpdate) */
|
|
15
20
|
onValueChange?: (value: string) => void;
|
|
21
|
+
/** 레일 선택 변경. 레일 아이템을 눌러 패널이 바뀔 때 알린다(선택 상태는 SGnb 가 자체 관리). */
|
|
22
|
+
onRailChange?: (value: string) => void;
|
|
16
23
|
/** 접힘(레일) 상태. 미지정 시 SLayout 의 folded 를 따른다. */
|
|
17
24
|
folded?: boolean;
|
|
18
25
|
/** 접힘 토글 (sdFoldChange) */
|
|
19
26
|
onFoldChange?: (folded: boolean) => void;
|
|
20
|
-
/** 앱런처(그리드) 버튼 클릭. 미지정 시 런처 버튼을 렌더하지 않는다. */
|
|
27
|
+
/** 앱런처(그리드) 버튼 클릭. 미지정 시 런처 버튼을 렌더하지 않는다. 접힘 레일(fix)에는 폴드 버튼만 남으므로 표시되지 않는다. */
|
|
21
28
|
onLauncherClick?: () => void;
|
|
22
29
|
/** 상단바 로고 영역 (slot) */
|
|
23
30
|
logo?: ReactNode;
|
|
@@ -27,12 +34,18 @@ export interface SGnbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'>
|
|
|
27
34
|
/**
|
|
28
35
|
* SGnb — 글로벌 네비게이션.
|
|
29
36
|
* 세 축이 독립이다: type(box/belt) = 메뉴 스타일, header(fix/full) = 상단바 구조,
|
|
30
|
-
* color(light/dark) = 색. 치수는 --cmp-
|
|
37
|
+
* color(light/dark) = 색. 치수는 --cmp-gnb / --cmp-gnb-box / --cmp-gnb-belt, 색은 --cmp-gnb-light / --cmp-gnb-dark 토큰 합성.
|
|
31
38
|
*
|
|
32
39
|
* header 가 배치와 접힘 형태를 정한다:
|
|
33
|
-
* - fix: 상단바가 GNB 컬럼(메뉴 폭) 안에 있다. 접으면 48px 세로 레일로
|
|
40
|
+
* - fix: 상단바가 GNB 컬럼(메뉴 폭) 안에 있다. 접으면 48px 세로 레일로 좁혀지고 상단바에는 폴드 버튼만 남는다.
|
|
34
41
|
* - full: 상단바가 레이아웃 전폭을 가로지르고 그 아래 [메뉴 | 페이지]. 접으면 메뉴만 사라진다.
|
|
35
42
|
*
|
|
43
|
+
* useRail 이면 items 의 첫 depth 가 좌측 레일(아이콘+라벨 68px)로 서고, 선택된 레일 아이템의
|
|
44
|
+
* children 이 메뉴 패널의 depth1 이 된다 — 트리는 그대로 두고 어디부터 그릴지만 바뀐다.
|
|
45
|
+
*
|
|
46
|
+
* 접힘/펼침은 GNB_FOLD_MS 동안의 슬라이드다 — 레일·메뉴가 함께 왼쪽으로 빠져나가고 펼치면 오른쪽에서 들어온다.
|
|
47
|
+
* 페이지 쪽 폭은 fix 면 이 컴포넌트의 폭 전환이, full 이면 SLayout 의 메뉴 열 전환이 같은 길이로 맞춘다.
|
|
48
|
+
*
|
|
36
49
|
* SLayout 안에서 쓰면 type·header·folded 를 context 에서 읽어 앱이 중복으로 넘길 필요가 없다.
|
|
37
50
|
* SLayout 없이 단독으로도 동작한다(prop 또는 기본값 사용).
|
|
38
51
|
*/
|
|
@@ -3073,73 +3073,70 @@ function SGhostButton({
|
|
|
3073
3073
|
}
|
|
3074
3074
|
|
|
3075
3075
|
// src/components/SGnb/gnb.config.ts
|
|
3076
|
-
var
|
|
3077
|
-
var box = (p) => `var(--cmp-
|
|
3078
|
-
var
|
|
3079
|
-
var
|
|
3076
|
+
var gnb = (p) => `var(--cmp-gnb-${p})`;
|
|
3077
|
+
var box = (p) => `var(--cmp-gnb-box-${p})`;
|
|
3078
|
+
var belt = (p) => `var(--cmp-gnb-belt-${p})`;
|
|
3079
|
+
var light = (p) => `var(--cmp-gnb-light-${p})`;
|
|
3080
|
+
var dark = (p) => `var(--cmp-gnb-dark-${p})`;
|
|
3081
|
+
var HEADER_TOP = {
|
|
3082
|
+
topHeight: gnb("top-height"),
|
|
3083
|
+
topPaddingX: gnb("top-paddingX"),
|
|
3084
|
+
topGap: gnb("top-gap"),
|
|
3085
|
+
logoHeight: gnb("logo-height")
|
|
3086
|
+
};
|
|
3080
3087
|
var GNB_HEADER = {
|
|
3081
|
-
fix:
|
|
3082
|
-
|
|
3083
|
-
topPaddingX: belt("top-paddingX"),
|
|
3084
|
-
topGap: belt("top-gap"),
|
|
3085
|
-
logoHeight: belt("logo-height")
|
|
3086
|
-
},
|
|
3087
|
-
full: {
|
|
3088
|
-
topHeight: box("top-height"),
|
|
3089
|
-
topPaddingX: box("paddingX"),
|
|
3090
|
-
topGap: box("gap"),
|
|
3091
|
-
// 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)
|
|
3092
|
-
logoHeight: box("logo-height")
|
|
3093
|
-
}
|
|
3088
|
+
fix: HEADER_TOP,
|
|
3089
|
+
full: HEADER_TOP
|
|
3094
3090
|
};
|
|
3095
3091
|
var GNB_MENU = {
|
|
3096
3092
|
box: {
|
|
3097
|
-
//
|
|
3098
|
-
bodyPaddingY:
|
|
3099
|
-
itemMarginX:
|
|
3100
|
-
|
|
3093
|
+
// 바디 패딩·세로 패딩·chevron 은 gnb 로 통합된 공통 토큰
|
|
3094
|
+
bodyPaddingY: gnb("body-padding"),
|
|
3095
|
+
itemMarginX: gnb("body-padding"),
|
|
3096
|
+
// 전방향 토큰 → 아이템 좌우 여백에도 사용
|
|
3097
|
+
itemPaddingY: gnb("item-paddingY"),
|
|
3101
3098
|
itemPaddingRight: box("item-paddingRight"),
|
|
3102
3099
|
itemGap: box("item-gap"),
|
|
3103
3100
|
itemRadius: box("item-radius"),
|
|
3104
|
-
itemArrow:
|
|
3101
|
+
itemArrow: gnb("item-chevron-size"),
|
|
3105
3102
|
depth1PaddingLeft: box("item-depth1-paddingLeft"),
|
|
3106
3103
|
depth2PaddingLeft: box("item-depth2-paddingLeft"),
|
|
3107
3104
|
depth3PaddingLeft: box("item-depth3-paddingLeft"),
|
|
3108
3105
|
depth1Icon: box("item-depth1-icon"),
|
|
3109
3106
|
depth1FontSize: 14,
|
|
3110
|
-
// token:
|
|
3107
|
+
// token: gnb.box.item.typography.selected.fontSize
|
|
3111
3108
|
depth1LineHeight: 24,
|
|
3112
|
-
// token:
|
|
3109
|
+
// token: gnb.box.item.typography.selected.lineHeight
|
|
3113
3110
|
depth1FontWeight: 700,
|
|
3114
3111
|
itemFontSize: 14,
|
|
3115
|
-
// token:
|
|
3112
|
+
// token: gnb.box.item.typography.default.fontSize
|
|
3116
3113
|
itemLineHeight: 24
|
|
3117
|
-
// token:
|
|
3114
|
+
// token: gnb.box.item.typography.default.lineHeight
|
|
3118
3115
|
},
|
|
3119
3116
|
belt: {
|
|
3120
|
-
bodyPaddingY:
|
|
3117
|
+
bodyPaddingY: gnb("body-padding"),
|
|
3121
3118
|
itemMarginX: "0px",
|
|
3122
3119
|
// belt 토큰에 가로 여백 없음 → 풀폭 행 (들여쓰기는 depth*-paddingLeft 전담)
|
|
3123
|
-
itemPaddingY:
|
|
3120
|
+
itemPaddingY: gnb("item-paddingY"),
|
|
3124
3121
|
itemPaddingRight: belt("item-paddingRight"),
|
|
3125
3122
|
itemGap: belt("item-gap"),
|
|
3126
3123
|
itemRadius: "0px",
|
|
3127
3124
|
// belt 토큰에 radius 없음 → 풀폭 행
|
|
3128
|
-
itemArrow:
|
|
3125
|
+
itemArrow: gnb("item-chevron-size"),
|
|
3129
3126
|
depth1PaddingLeft: belt("item-depth1-paddingLeft"),
|
|
3130
3127
|
depth2PaddingLeft: belt("item-depth2-paddingLeft"),
|
|
3131
3128
|
depth3PaddingLeft: belt("item-depth3-paddingLeft"),
|
|
3132
|
-
depth1Icon:
|
|
3133
|
-
// belt
|
|
3129
|
+
depth1Icon: belt("item-depth1-icon"),
|
|
3130
|
+
// belt 전용 depth1 아이콘 토큰(16)
|
|
3134
3131
|
depth1FontSize: 13,
|
|
3135
|
-
// token:
|
|
3132
|
+
// token: gnb.belt.item.depth1.typography.fontSize
|
|
3136
3133
|
depth1LineHeight: 22,
|
|
3137
|
-
// token:
|
|
3134
|
+
// token: gnb.belt.item.depth1.typography.lineHeight
|
|
3138
3135
|
depth1FontWeight: 700,
|
|
3139
3136
|
itemFontSize: 12,
|
|
3140
|
-
// token:
|
|
3137
|
+
// token: gnb.belt.item.typography.default.fontSize
|
|
3141
3138
|
itemLineHeight: 20
|
|
3142
|
-
// token:
|
|
3139
|
+
// token: gnb.belt.item.typography.default.lineHeight
|
|
3143
3140
|
}
|
|
3144
3141
|
};
|
|
3145
3142
|
var GNB_COLORS = {
|
|
@@ -3152,7 +3149,9 @@ var GNB_COLORS = {
|
|
|
3152
3149
|
itemColorDefault: light("item-fg-default"),
|
|
3153
3150
|
itemColorHover: light("item-fg-hover"),
|
|
3154
3151
|
itemColorActive: light("item-fg-selected"),
|
|
3155
|
-
itemChevron: light("item-chevron")
|
|
3152
|
+
itemChevron: light("item-chevron"),
|
|
3153
|
+
itemIcon: "grey_65",
|
|
3154
|
+
railFgDefault: light("rail-fg-default")
|
|
3156
3155
|
},
|
|
3157
3156
|
dark: {
|
|
3158
3157
|
panelBg: dark("bg"),
|
|
@@ -3163,19 +3162,41 @@ var GNB_COLORS = {
|
|
|
3163
3162
|
itemColorDefault: dark("item-fg-default"),
|
|
3164
3163
|
itemColorHover: dark("item-fg-hover"),
|
|
3165
3164
|
itemColorActive: dark("item-fg-selected"),
|
|
3166
|
-
itemChevron: dark("item-chevron")
|
|
3165
|
+
itemChevron: dark("item-chevron"),
|
|
3166
|
+
itemIcon: "grey_55",
|
|
3167
|
+
railFgDefault: dark("rail-fg-default")
|
|
3167
3168
|
}
|
|
3168
3169
|
};
|
|
3169
3170
|
var GNB_COMMON = {
|
|
3170
3171
|
/** 접힘 레일 폭. header="full" 은 접으면 메뉴만 사라져 레일이 없으므로 fix 전용. */
|
|
3171
|
-
foldWidth:
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3172
|
+
foldWidth: gnb("fold-width")
|
|
3173
|
+
};
|
|
3174
|
+
var GNB_RAIL = {
|
|
3175
|
+
padding: gnb("rail-padding"),
|
|
3176
|
+
gap: gnb("rail-gap"),
|
|
3177
|
+
itemWidth: gnb("rail-item-width"),
|
|
3178
|
+
itemPaddingTop: gnb("rail-item-paddingTop"),
|
|
3179
|
+
itemPaddingBottom: gnb("rail-item-paddingBottom"),
|
|
3180
|
+
itemRadius: gnb("rail-item-radius"),
|
|
3181
|
+
itemIcon: gnb("rail-item-icon"),
|
|
3182
|
+
// 타이포 (theme.css 에 gnb 타이포 변수 없음 → 토큰 값 직접 명시)
|
|
3183
|
+
itemFontSize: 11,
|
|
3184
|
+
// token: gnb.rail.item.typography.default.fontSize
|
|
3185
|
+
itemLineHeight: 18,
|
|
3186
|
+
// token: gnb.rail.item.typography.default.lineHeight
|
|
3187
|
+
itemFontWeight: 500,
|
|
3188
|
+
// token: gnb.rail.item.typography.default.fontWeight
|
|
3189
|
+
itemFontWeightSelected: 700
|
|
3190
|
+
// token: gnb.rail.item.typography.selected.fontWeight
|
|
3191
|
+
};
|
|
3192
|
+
var GNB_RAIL_WIDTH = 68;
|
|
3193
|
+
var GNB_FOLD_MS = 200;
|
|
3194
|
+
var GNB_EXPAND_MS = 200;
|
|
3178
3195
|
var GNB_MENU_WIDTH = 240;
|
|
3196
|
+
function findGnbRailValue(items, value) {
|
|
3197
|
+
if (items.some((item) => item.value === value)) return value;
|
|
3198
|
+
return findGnbAncestors(items, value)[0];
|
|
3199
|
+
}
|
|
3179
3200
|
function findGnbAncestors(items, value) {
|
|
3180
3201
|
const walk = (list, trail) => {
|
|
3181
3202
|
for (const item of list) {
|
|
@@ -3193,6 +3214,7 @@ var SLayoutContext = createContext(null);
|
|
|
3193
3214
|
forwardRef(function SLayout2({
|
|
3194
3215
|
type = "box",
|
|
3195
3216
|
header = "fix",
|
|
3217
|
+
useRail = false,
|
|
3196
3218
|
folded,
|
|
3197
3219
|
defaultFolded = false,
|
|
3198
3220
|
onFoldedChange,
|
|
@@ -3212,13 +3234,17 @@ forwardRef(function SLayout2({
|
|
|
3212
3234
|
[isControlled, onFoldedChange]
|
|
3213
3235
|
);
|
|
3214
3236
|
const ctx = useMemo(
|
|
3215
|
-
() => ({ type, header, folded: currentFolded, setFolded }),
|
|
3216
|
-
[type, header, currentFolded, setFolded]
|
|
3237
|
+
() => ({ type, header, useRail, folded: currentFolded, setFolded }),
|
|
3238
|
+
[type, header, useRail, currentFolded, setFolded]
|
|
3217
3239
|
);
|
|
3218
3240
|
const isFullHeader = header === "full";
|
|
3241
|
+
const gnbColumnWidth = useRail ? GNB_RAIL_WIDTH + GNB_MENU_WIDTH : GNB_MENU_WIDTH;
|
|
3219
3242
|
const frameStyle = isFullHeader ? {
|
|
3220
|
-
gridTemplateColumns: `${currentFolded ? 0 :
|
|
3221
|
-
gridTemplateRows: `${GNB_HEADER[header].topHeight} 1fr
|
|
3243
|
+
gridTemplateColumns: `${currentFolded ? 0 : gnbColumnWidth}px 1fr`,
|
|
3244
|
+
gridTemplateRows: `${GNB_HEADER[header].topHeight} 1fr`,
|
|
3245
|
+
// 메뉴가 미끄러지는 동안 페이지 열도 같은 길이로 따라와야 한 덩어리로 읽힌다.
|
|
3246
|
+
// (fix 는 SGnb 가 제 폭을 직접 전환하므로 프레임이 손댈 게 없다)
|
|
3247
|
+
transition: `grid-template-columns ${GNB_FOLD_MS}ms ease-out`
|
|
3222
3248
|
} : void 0;
|
|
3223
3249
|
return /* @__PURE__ */ jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
3224
3250
|
"div",
|
|
@@ -3243,8 +3269,10 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3243
3269
|
header: headerProp,
|
|
3244
3270
|
color = "light",
|
|
3245
3271
|
items,
|
|
3272
|
+
useRail: useRailProp,
|
|
3246
3273
|
value = "",
|
|
3247
3274
|
onValueChange,
|
|
3275
|
+
onRailChange,
|
|
3248
3276
|
folded: foldedProp,
|
|
3249
3277
|
onFoldChange,
|
|
3250
3278
|
onLauncherClick,
|
|
@@ -3258,6 +3286,7 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3258
3286
|
const type = typeProp ?? layout?.type ?? "box";
|
|
3259
3287
|
const header = headerProp ?? layout?.header ?? "fix";
|
|
3260
3288
|
const folded = foldedProp ?? layout?.folded ?? false;
|
|
3289
|
+
const useRail = useRailProp ?? layout?.useRail ?? false;
|
|
3261
3290
|
const H = GNB_HEADER[header];
|
|
3262
3291
|
const M = GNB_MENU[type];
|
|
3263
3292
|
const C = GNB_COLORS[color];
|
|
@@ -3265,6 +3294,9 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3265
3294
|
() => new Set(findGnbAncestors(items, value))
|
|
3266
3295
|
);
|
|
3267
3296
|
const [hoveredKey, setHoveredKey] = useState(null);
|
|
3297
|
+
const [activeRail, setActiveRail] = useState(
|
|
3298
|
+
() => findGnbRailValue(items, value) ?? items[0]?.value
|
|
3299
|
+
);
|
|
3268
3300
|
useEffect(() => {
|
|
3269
3301
|
const ancestors = findGnbAncestors(items, value);
|
|
3270
3302
|
if (ancestors.length === 0) return;
|
|
@@ -3275,6 +3307,15 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3275
3307
|
return next;
|
|
3276
3308
|
});
|
|
3277
3309
|
}, [items, value]);
|
|
3310
|
+
useEffect(() => {
|
|
3311
|
+
const owner = findGnbRailValue(items, value);
|
|
3312
|
+
if (owner !== void 0) setActiveRail(owner);
|
|
3313
|
+
}, [items, value]);
|
|
3314
|
+
useEffect(() => {
|
|
3315
|
+
setActiveRail(
|
|
3316
|
+
(prev) => prev !== void 0 && items.some((item) => item.value === prev) ? prev : items[0]?.value
|
|
3317
|
+
);
|
|
3318
|
+
}, [items]);
|
|
3278
3319
|
const toggleExpand = (key) => {
|
|
3279
3320
|
setExpandedKeys((prev) => {
|
|
3280
3321
|
const next = new Set(prev);
|
|
@@ -3291,16 +3332,58 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3291
3332
|
}
|
|
3292
3333
|
onValueChange?.(item.value);
|
|
3293
3334
|
};
|
|
3335
|
+
const handleRailClick = (item) => {
|
|
3336
|
+
if (item.disabled) return;
|
|
3337
|
+
setActiveRail(item.value);
|
|
3338
|
+
onRailChange?.(item.value);
|
|
3339
|
+
if (!item.children || item.children.length === 0) onValueChange?.(item.value);
|
|
3340
|
+
};
|
|
3294
3341
|
const handleFoldToggle = () => {
|
|
3295
3342
|
const next = !folded;
|
|
3296
3343
|
onFoldChange?.(next);
|
|
3297
3344
|
if (foldedProp === void 0) layout?.setFolded(next);
|
|
3298
3345
|
};
|
|
3299
|
-
const
|
|
3346
|
+
const renderRailItem = (item) => {
|
|
3347
|
+
const isActive = item.value === activeRail;
|
|
3348
|
+
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
3349
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
3350
|
+
"button",
|
|
3351
|
+
{
|
|
3352
|
+
type: "button",
|
|
3353
|
+
disabled: item.disabled,
|
|
3354
|
+
"aria-current": isActive ? "true" : void 0,
|
|
3355
|
+
className: "box-border flex select-none flex-col items-center border-none bg-transparent transition-colors duration-150",
|
|
3356
|
+
style: {
|
|
3357
|
+
width: GNB_RAIL.itemWidth,
|
|
3358
|
+
paddingTop: GNB_RAIL.itemPaddingTop,
|
|
3359
|
+
paddingBottom: GNB_RAIL.itemPaddingBottom,
|
|
3360
|
+
borderRadius: GNB_RAIL.itemRadius,
|
|
3361
|
+
backgroundColor: isActive ? C.itemBgSelected : isHovered ? C.itemBgHover : C.itemBgDefault,
|
|
3362
|
+
color: isActive ? C.itemColorActive : C.railFgDefault,
|
|
3363
|
+
// <button> 의 UA 폰트를 끊고 앱 폰트를 잇는다 (메뉴 아이템과 같은 이유)
|
|
3364
|
+
fontFamily: "inherit",
|
|
3365
|
+
fontSize: GNB_RAIL.itemFontSize,
|
|
3366
|
+
lineHeight: `${GNB_RAIL.itemLineHeight}px`,
|
|
3367
|
+
fontWeight: isActive ? GNB_RAIL.itemFontWeightSelected : GNB_RAIL.itemFontWeight,
|
|
3368
|
+
opacity: item.disabled ? 0.4 : 1,
|
|
3369
|
+
cursor: item.disabled ? "not-allowed" : "pointer"
|
|
3370
|
+
},
|
|
3371
|
+
onClick: () => handleRailClick(item),
|
|
3372
|
+
onMouseEnter: () => setHoveredKey(item.value),
|
|
3373
|
+
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
3374
|
+
children: [
|
|
3375
|
+
item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: GNB_RAIL.itemIcon, className: "shrink-0" }),
|
|
3376
|
+
/* @__PURE__ */ jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-center", children: item.label })
|
|
3377
|
+
]
|
|
3378
|
+
}
|
|
3379
|
+
) }, item.value);
|
|
3380
|
+
};
|
|
3381
|
+
const renderItem = (item, depth, branchHasIcon = false) => {
|
|
3300
3382
|
const hasChildren = !!(item.children && item.children.length > 0);
|
|
3301
3383
|
const isSelected = !hasChildren && item.value === value;
|
|
3302
3384
|
const isExpanded = expandedKeys.has(item.value);
|
|
3303
3385
|
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
3386
|
+
const iconBranch = depth === 1 ? !!item.icon : branchHasIcon;
|
|
3304
3387
|
const itemStyle = {
|
|
3305
3388
|
gap: M.itemGap,
|
|
3306
3389
|
paddingTop: M.itemPaddingY,
|
|
@@ -3337,8 +3420,11 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3337
3420
|
onMouseEnter: () => setHoveredKey(item.value),
|
|
3338
3421
|
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
3339
3422
|
children: [
|
|
3340
|
-
depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, className: "shrink-0" }),
|
|
3341
|
-
/* @__PURE__ */
|
|
3423
|
+
depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, color: C.itemIcon, className: "shrink-0" }),
|
|
3424
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap flex flex-no-wrap items-center", children: [
|
|
3425
|
+
depth > 1 && iconBranch ? /* @__PURE__ */ jsx("div", { className: "w-[20px] h-[24px] flex items-center justify-center mr-[8px]", children: "-" }) : null,
|
|
3426
|
+
item.label
|
|
3427
|
+
] }),
|
|
3342
3428
|
item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsx(
|
|
3343
3429
|
STag,
|
|
3344
3430
|
{
|
|
@@ -3356,28 +3442,53 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3356
3442
|
size: M.itemArrow,
|
|
3357
3443
|
color: C.itemChevron,
|
|
3358
3444
|
rotate: isExpanded ? 180 : 0,
|
|
3359
|
-
className: "shrink-0 transition-transform duration-
|
|
3445
|
+
className: "shrink-0 transition-transform duration-200"
|
|
3360
3446
|
}
|
|
3361
3447
|
)
|
|
3362
3448
|
]
|
|
3363
3449
|
}
|
|
3364
3450
|
),
|
|
3365
|
-
hasChildren &&
|
|
3451
|
+
hasChildren && /* @__PURE__ */ jsx(
|
|
3452
|
+
"div",
|
|
3453
|
+
{
|
|
3454
|
+
className: "grid",
|
|
3455
|
+
style: {
|
|
3456
|
+
gridTemplateRows: isExpanded ? "1fr" : "0fr",
|
|
3457
|
+
transition: `grid-template-rows ${GNB_EXPAND_MS}ms ease-out`
|
|
3458
|
+
},
|
|
3459
|
+
children: /* @__PURE__ */ jsx(
|
|
3460
|
+
"ul",
|
|
3461
|
+
{
|
|
3462
|
+
className: "m-0 flex min-h-0 list-none flex-col overflow-hidden p-0",
|
|
3463
|
+
style: {
|
|
3464
|
+
visibility: isExpanded ? "visible" : "hidden",
|
|
3465
|
+
transition: `visibility 0s linear ${isExpanded ? "0s" : `${GNB_EXPAND_MS}ms`}`
|
|
3466
|
+
},
|
|
3467
|
+
children: item.children.map((child) => renderItem(child, depth + 1, iconBranch))
|
|
3468
|
+
}
|
|
3469
|
+
)
|
|
3470
|
+
}
|
|
3471
|
+
)
|
|
3366
3472
|
] }, item.value)
|
|
3367
3473
|
);
|
|
3368
3474
|
};
|
|
3475
|
+
const railItems = useRail ? items : [];
|
|
3476
|
+
const menuItems = useRail ? items.find((item) => item.value === activeRail)?.children ?? [] : items;
|
|
3477
|
+
const bodyWidth = useRail ? GNB_RAIL_WIDTH + GNB_MENU_WIDTH : GNB_MENU_WIDTH;
|
|
3369
3478
|
const railFolded = folded && header === "fix";
|
|
3370
|
-
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth :
|
|
3479
|
+
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : bodyWidth;
|
|
3371
3480
|
const rootStyle = {
|
|
3372
3481
|
width: rootWidth,
|
|
3482
|
+
transition: `width ${GNB_FOLD_MS}ms ease-out`,
|
|
3373
3483
|
...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
|
|
3374
3484
|
...style
|
|
3375
3485
|
};
|
|
3486
|
+
const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
|
|
3376
3487
|
return /* @__PURE__ */ jsxs(
|
|
3377
3488
|
"div",
|
|
3378
3489
|
{
|
|
3379
3490
|
ref,
|
|
3380
|
-
className: cn("box-border flex h-full shrink-0 flex-col", className),
|
|
3491
|
+
className: cn("box-border flex h-full shrink-0 flex-col overflow-hidden", className),
|
|
3381
3492
|
style: rootStyle,
|
|
3382
3493
|
"data-type": type,
|
|
3383
3494
|
"data-header": header,
|
|
@@ -3387,30 +3498,47 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3387
3498
|
/* @__PURE__ */ jsxs(
|
|
3388
3499
|
"div",
|
|
3389
3500
|
{
|
|
3390
|
-
className:
|
|
3501
|
+
className: "relative box-border flex shrink-0 items-center overflow-hidden",
|
|
3391
3502
|
style: {
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
padding: railFolded ? `${GNB_COMMON.buttonGap} 0` : `0 ${H.topPaddingX}`,
|
|
3503
|
+
height: H.topHeight,
|
|
3504
|
+
padding: `0 ${railFolded ? GNB_RAIL.padding : H.topPaddingX}`,
|
|
3395
3505
|
borderBottom: `1px solid ${C.topBorder}`,
|
|
3396
3506
|
backgroundColor: C.panelBg,
|
|
3397
3507
|
pointerEvents: "auto",
|
|
3508
|
+
transition: `padding ${GNB_FOLD_MS}ms ease-out`,
|
|
3398
3509
|
// 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
|
|
3399
3510
|
color: C.itemColorDefault
|
|
3400
3511
|
},
|
|
3401
3512
|
children: [
|
|
3402
|
-
onLauncherClick && /* @__PURE__ */
|
|
3403
|
-
|
|
3513
|
+
(onLauncherClick || logo) && /* @__PURE__ */ jsxs(
|
|
3514
|
+
"div",
|
|
3404
3515
|
{
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3516
|
+
className: "absolute inset-y-0 flex items-center",
|
|
3517
|
+
style: {
|
|
3518
|
+
left: H.topPaddingX,
|
|
3519
|
+
gap: H.topGap,
|
|
3520
|
+
opacity: railFolded ? 0 : 1,
|
|
3521
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
|
|
3522
|
+
// 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
|
|
3523
|
+
transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
|
|
3524
|
+
visibility: railFolded ? "hidden" : "visible"
|
|
3525
|
+
},
|
|
3526
|
+
children: [
|
|
3527
|
+
onLauncherClick && /* @__PURE__ */ jsx(
|
|
3528
|
+
SGhostButton,
|
|
3529
|
+
{
|
|
3530
|
+
icon: "launcher",
|
|
3531
|
+
size: "xs",
|
|
3532
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3533
|
+
ariaLabel: "app launcher",
|
|
3534
|
+
onClick: onLauncherClick,
|
|
3535
|
+
className: "shrink-0"
|
|
3536
|
+
}
|
|
3537
|
+
),
|
|
3538
|
+
logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
|
|
3539
|
+
]
|
|
3411
3540
|
}
|
|
3412
3541
|
),
|
|
3413
|
-
!railFolded && logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo }),
|
|
3414
3542
|
/* @__PURE__ */ jsx(
|
|
3415
3543
|
SGhostButton,
|
|
3416
3544
|
{
|
|
@@ -3420,30 +3548,61 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3420
3548
|
ariaLabel: "toggle navigation",
|
|
3421
3549
|
ariaPressed: folded,
|
|
3422
3550
|
onClick: handleFoldToggle,
|
|
3423
|
-
className:
|
|
3551
|
+
className: "ml-auto shrink-0"
|
|
3424
3552
|
}
|
|
3425
3553
|
)
|
|
3426
3554
|
]
|
|
3427
3555
|
}
|
|
3428
3556
|
),
|
|
3429
|
-
|
|
3430
|
-
"
|
|
3557
|
+
/* @__PURE__ */ jsxs(
|
|
3558
|
+
"div",
|
|
3431
3559
|
{
|
|
3432
|
-
"
|
|
3433
|
-
className: "flex flex-col overflow-y-auto",
|
|
3560
|
+
className: "flex flex-row",
|
|
3434
3561
|
style: {
|
|
3435
|
-
width:
|
|
3562
|
+
width: bodyWidth,
|
|
3436
3563
|
alignSelf: "flex-start",
|
|
3437
3564
|
flexGrow: 1,
|
|
3438
3565
|
flexShrink: 1,
|
|
3439
3566
|
flexBasis: "auto",
|
|
3440
3567
|
minHeight: 0,
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
pointerEvents: "auto"
|
|
3568
|
+
transform: folded ? "translateX(-100%)" : "none",
|
|
3569
|
+
visibility: folded ? "hidden" : "visible",
|
|
3570
|
+
transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
|
|
3445
3571
|
},
|
|
3446
|
-
children:
|
|
3572
|
+
children: [
|
|
3573
|
+
useRail && // 레일도 내비게이션 landmark — nav 로 세워야 aria-label(레일)이 스크린리더에 노출된다
|
|
3574
|
+
// (role 없는 ul 의 aria-label 은 영역명으로 읽히지 않는다). 메뉴 nav 와는 레이블로 구분된다.
|
|
3575
|
+
/* @__PURE__ */ jsx(
|
|
3576
|
+
"nav",
|
|
3577
|
+
{
|
|
3578
|
+
"aria-label": `${ariaLabel} rail`,
|
|
3579
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
3580
|
+
style: {
|
|
3581
|
+
width: GNB_RAIL_WIDTH,
|
|
3582
|
+
padding: GNB_RAIL.padding,
|
|
3583
|
+
borderRight: `1px solid ${C.topBorder}`,
|
|
3584
|
+
backgroundColor: C.panelBg,
|
|
3585
|
+
pointerEvents: "auto"
|
|
3586
|
+
},
|
|
3587
|
+
children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", style: { gap: GNB_RAIL.gap }, children: railItems.map(renderRailItem) })
|
|
3588
|
+
}
|
|
3589
|
+
),
|
|
3590
|
+
/* @__PURE__ */ jsx(
|
|
3591
|
+
"nav",
|
|
3592
|
+
{
|
|
3593
|
+
"aria-label": ariaLabel,
|
|
3594
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
3595
|
+
style: {
|
|
3596
|
+
width: GNB_MENU_WIDTH,
|
|
3597
|
+
paddingTop: M.bodyPaddingY,
|
|
3598
|
+
paddingBottom: M.bodyPaddingY,
|
|
3599
|
+
backgroundColor: C.panelBg,
|
|
3600
|
+
pointerEvents: "auto"
|
|
3601
|
+
},
|
|
3602
|
+
children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: menuItems.map((item) => renderItem(item, 1)) })
|
|
3603
|
+
}
|
|
3604
|
+
)
|
|
3605
|
+
]
|
|
3447
3606
|
}
|
|
3448
3607
|
)
|
|
3449
3608
|
]
|