sellmate-design-system-react 0.4.0 → 0.6.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/README.md +20 -0
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs +180 -37
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +6 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.js +180 -37
- package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
- package/dist/components/SGnb/SGnb.cjs +329 -100
- package/dist/components/SGnb/SGnb.cjs.map +1 -1
- package/dist/components/SGnb/SGnb.d.ts +24 -5
- package/dist/components/SGnb/SGnb.js +330 -101
- 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/SKeyValueTable/SKeyValueTable.cjs +180 -37
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.js +180 -37
- package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
- package/dist/components/SLayout/SLayout.cjs +37 -21
- package/dist/components/SLayout/SLayout.cjs.map +1 -1
- package/dist/components/SLayout/SLayout.d.ts +20 -1
- package/dist/components/SLayout/SLayout.js +37 -21
- package/dist/components/SLayout/SLayout.js.map +1 -1
- package/dist/components/SPage/SPage.cjs +38 -22
- package/dist/components/SPage/SPage.cjs.map +1 -1
- package/dist/components/SPage/SPage.js +38 -22
- 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 +531 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +528 -165
- package/dist/index.js.map +1 -1
- package/dist/styles.css +73 -4
- package/dist/theme.css +1 -1
- package/package.json +3 -1
|
@@ -3,7 +3,7 @@ import { createContext, forwardRef, useState, useCallback, useMemo, useContext,
|
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { extendTailwindMerge } from 'tailwind-merge';
|
|
5
5
|
import * as RPopover from '@radix-ui/react-popover';
|
|
6
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
var twMerge = extendTailwindMerge({
|
|
9
9
|
extend: {
|
|
@@ -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,
|
|
@@ -3211,21 +3233,40 @@ forwardRef(function SLayout2({
|
|
|
3211
3233
|
},
|
|
3212
3234
|
[isControlled, onFoldedChange]
|
|
3213
3235
|
);
|
|
3214
|
-
const
|
|
3215
|
-
|
|
3216
|
-
|
|
3236
|
+
const [gnbState, setGnbState] = useState({ forceFull: false, menuVisible: true });
|
|
3237
|
+
const reportGnbState = useCallback(
|
|
3238
|
+
(next) => setGnbState(
|
|
3239
|
+
(prev) => prev.forceFull === next.forceFull && prev.menuVisible === next.menuVisible ? prev : next
|
|
3240
|
+
),
|
|
3241
|
+
[]
|
|
3217
3242
|
);
|
|
3218
|
-
const
|
|
3243
|
+
const effectiveHeader = header === "full" || gnbState.forceFull ? "full" : "fix";
|
|
3244
|
+
const ctx = useMemo(
|
|
3245
|
+
() => ({
|
|
3246
|
+
type,
|
|
3247
|
+
header: effectiveHeader,
|
|
3248
|
+
useRail,
|
|
3249
|
+
folded: currentFolded,
|
|
3250
|
+
setFolded,
|
|
3251
|
+
reportGnbState
|
|
3252
|
+
}),
|
|
3253
|
+
[type, effectiveHeader, useRail, currentFolded, setFolded, reportGnbState]
|
|
3254
|
+
);
|
|
3255
|
+
const isFullHeader = effectiveHeader === "full";
|
|
3256
|
+
const gnbColumnWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (gnbState.menuVisible ? GNB_MENU_WIDTH : 0);
|
|
3219
3257
|
const frameStyle = isFullHeader ? {
|
|
3220
|
-
gridTemplateColumns: `${currentFolded ? 0 :
|
|
3221
|
-
gridTemplateRows: `${GNB_HEADER[
|
|
3258
|
+
gridTemplateColumns: `${currentFolded ? 0 : gnbColumnWidth}px 1fr`,
|
|
3259
|
+
gridTemplateRows: `${GNB_HEADER[effectiveHeader].topHeight} 1fr`,
|
|
3260
|
+
// 메뉴가 미끄러지는 동안 페이지 열도 같은 길이로 따라와야 한 덩어리로 읽힌다.
|
|
3261
|
+
// (fix 는 SGnb 가 제 폭을 직접 전환하므로 프레임이 손댈 게 없다)
|
|
3262
|
+
transition: `grid-template-columns ${GNB_FOLD_MS}ms ease-out`
|
|
3222
3263
|
} : void 0;
|
|
3223
3264
|
return /* @__PURE__ */ jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
3224
3265
|
"div",
|
|
3225
3266
|
{
|
|
3226
3267
|
ref,
|
|
3227
3268
|
"data-type": type,
|
|
3228
|
-
"data-header":
|
|
3269
|
+
"data-header": effectiveHeader,
|
|
3229
3270
|
className: cn(
|
|
3230
3271
|
"box-border h-full overflow-hidden",
|
|
3231
3272
|
isFullHeader ? "grid" : "flex flex-row",
|
|
@@ -3243,8 +3284,10 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3243
3284
|
header: headerProp,
|
|
3244
3285
|
color = "light",
|
|
3245
3286
|
items,
|
|
3287
|
+
useRail: useRailProp,
|
|
3246
3288
|
value = "",
|
|
3247
3289
|
onValueChange,
|
|
3290
|
+
onRailChange,
|
|
3248
3291
|
folded: foldedProp,
|
|
3249
3292
|
onFoldChange,
|
|
3250
3293
|
onLauncherClick,
|
|
@@ -3256,8 +3299,10 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3256
3299
|
}, ref) {
|
|
3257
3300
|
const layout = useContext(SLayoutContext);
|
|
3258
3301
|
const type = typeProp ?? layout?.type ?? "box";
|
|
3259
|
-
const header = headerProp ?? layout?.header ?? "fix";
|
|
3260
3302
|
const folded = foldedProp ?? layout?.folded ?? false;
|
|
3303
|
+
const useRail = useRailProp ?? layout?.useRail ?? false;
|
|
3304
|
+
const railHasLeaf = useRail && items.some((item) => !item.children || item.children.length === 0);
|
|
3305
|
+
const header = railHasLeaf ? "full" : headerProp ?? layout?.header ?? "fix";
|
|
3261
3306
|
const H = GNB_HEADER[header];
|
|
3262
3307
|
const M = GNB_MENU[type];
|
|
3263
3308
|
const C = GNB_COLORS[color];
|
|
@@ -3265,6 +3310,9 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3265
3310
|
() => new Set(findGnbAncestors(items, value))
|
|
3266
3311
|
);
|
|
3267
3312
|
const [hoveredKey, setHoveredKey] = useState(null);
|
|
3313
|
+
const [activeRail, setActiveRail] = useState(
|
|
3314
|
+
() => findGnbRailValue(items, value) ?? items[0]?.value
|
|
3315
|
+
);
|
|
3268
3316
|
useEffect(() => {
|
|
3269
3317
|
const ancestors = findGnbAncestors(items, value);
|
|
3270
3318
|
if (ancestors.length === 0) return;
|
|
@@ -3275,6 +3323,15 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3275
3323
|
return next;
|
|
3276
3324
|
});
|
|
3277
3325
|
}, [items, value]);
|
|
3326
|
+
useEffect(() => {
|
|
3327
|
+
const owner = findGnbRailValue(items, value);
|
|
3328
|
+
if (owner !== void 0) setActiveRail(owner);
|
|
3329
|
+
}, [items, value]);
|
|
3330
|
+
useEffect(() => {
|
|
3331
|
+
setActiveRail(
|
|
3332
|
+
(prev) => prev !== void 0 && items.some((item) => item.value === prev) ? prev : items[0]?.value
|
|
3333
|
+
);
|
|
3334
|
+
}, [items]);
|
|
3278
3335
|
const toggleExpand = (key) => {
|
|
3279
3336
|
setExpandedKeys((prev) => {
|
|
3280
3337
|
const next = new Set(prev);
|
|
@@ -3291,16 +3348,58 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3291
3348
|
}
|
|
3292
3349
|
onValueChange?.(item.value);
|
|
3293
3350
|
};
|
|
3351
|
+
const handleRailClick = (item) => {
|
|
3352
|
+
if (item.disabled) return;
|
|
3353
|
+
setActiveRail(item.value);
|
|
3354
|
+
onRailChange?.(item.value);
|
|
3355
|
+
if (!item.children || item.children.length === 0) onValueChange?.(item.value);
|
|
3356
|
+
};
|
|
3294
3357
|
const handleFoldToggle = () => {
|
|
3295
3358
|
const next = !folded;
|
|
3296
3359
|
onFoldChange?.(next);
|
|
3297
3360
|
if (foldedProp === void 0) layout?.setFolded(next);
|
|
3298
3361
|
};
|
|
3299
|
-
const
|
|
3362
|
+
const renderRailItem = (item) => {
|
|
3363
|
+
const isActive = item.value === activeRail;
|
|
3364
|
+
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
3365
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
3366
|
+
"button",
|
|
3367
|
+
{
|
|
3368
|
+
type: "button",
|
|
3369
|
+
disabled: item.disabled,
|
|
3370
|
+
"aria-current": isActive ? "true" : void 0,
|
|
3371
|
+
className: "box-border flex select-none flex-col items-center border-none bg-transparent transition-colors duration-150",
|
|
3372
|
+
style: {
|
|
3373
|
+
width: GNB_RAIL.itemWidth,
|
|
3374
|
+
paddingTop: GNB_RAIL.itemPaddingTop,
|
|
3375
|
+
paddingBottom: GNB_RAIL.itemPaddingBottom,
|
|
3376
|
+
borderRadius: GNB_RAIL.itemRadius,
|
|
3377
|
+
backgroundColor: isActive ? C.itemBgSelected : isHovered ? C.itemBgHover : C.itemBgDefault,
|
|
3378
|
+
color: isActive ? C.itemColorActive : C.railFgDefault,
|
|
3379
|
+
// <button> 의 UA 폰트를 끊고 앱 폰트를 잇는다 (메뉴 아이템과 같은 이유)
|
|
3380
|
+
fontFamily: "inherit",
|
|
3381
|
+
fontSize: GNB_RAIL.itemFontSize,
|
|
3382
|
+
lineHeight: `${GNB_RAIL.itemLineHeight}px`,
|
|
3383
|
+
fontWeight: isActive ? GNB_RAIL.itemFontWeightSelected : GNB_RAIL.itemFontWeight,
|
|
3384
|
+
opacity: item.disabled ? 0.4 : 1,
|
|
3385
|
+
cursor: item.disabled ? "not-allowed" : "pointer"
|
|
3386
|
+
},
|
|
3387
|
+
onClick: () => handleRailClick(item),
|
|
3388
|
+
onMouseEnter: () => setHoveredKey(item.value),
|
|
3389
|
+
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
3390
|
+
children: [
|
|
3391
|
+
item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: GNB_RAIL.itemIcon, className: "shrink-0" }),
|
|
3392
|
+
/* @__PURE__ */ jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-center", children: item.label })
|
|
3393
|
+
]
|
|
3394
|
+
}
|
|
3395
|
+
) }, item.value);
|
|
3396
|
+
};
|
|
3397
|
+
const renderItem = (item, depth, branchHasIcon = false) => {
|
|
3300
3398
|
const hasChildren = !!(item.children && item.children.length > 0);
|
|
3301
3399
|
const isSelected = !hasChildren && item.value === value;
|
|
3302
3400
|
const isExpanded = expandedKeys.has(item.value);
|
|
3303
3401
|
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
3402
|
+
const iconBranch = depth === 1 ? !!item.icon : branchHasIcon;
|
|
3304
3403
|
const itemStyle = {
|
|
3305
3404
|
gap: M.itemGap,
|
|
3306
3405
|
paddingTop: M.itemPaddingY,
|
|
@@ -3337,8 +3436,11 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3337
3436
|
onMouseEnter: () => setHoveredKey(item.value),
|
|
3338
3437
|
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
3339
3438
|
children: [
|
|
3340
|
-
depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, className: "shrink-0" }),
|
|
3341
|
-
/* @__PURE__ */
|
|
3439
|
+
depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, color: C.itemIcon, className: "shrink-0" }),
|
|
3440
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap flex flex-no-wrap items-center", children: [
|
|
3441
|
+
depth > 1 && iconBranch ? /* @__PURE__ */ jsx("div", { className: "w-[20px] h-[24px] flex items-center justify-center mr-[8px]", children: "-" }) : null,
|
|
3442
|
+
item.label
|
|
3443
|
+
] }),
|
|
3342
3444
|
item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsx(
|
|
3343
3445
|
STag,
|
|
3344
3446
|
{
|
|
@@ -3356,94 +3458,221 @@ var SGnb = forwardRef(function SGnb2({
|
|
|
3356
3458
|
size: M.itemArrow,
|
|
3357
3459
|
color: C.itemChevron,
|
|
3358
3460
|
rotate: isExpanded ? 180 : 0,
|
|
3359
|
-
className: "shrink-0 transition-transform duration-
|
|
3461
|
+
className: "shrink-0 transition-transform duration-200"
|
|
3360
3462
|
}
|
|
3361
3463
|
)
|
|
3362
3464
|
]
|
|
3363
3465
|
}
|
|
3364
3466
|
),
|
|
3365
|
-
hasChildren &&
|
|
3467
|
+
hasChildren && /* @__PURE__ */ jsx(
|
|
3468
|
+
"div",
|
|
3469
|
+
{
|
|
3470
|
+
className: "grid",
|
|
3471
|
+
style: {
|
|
3472
|
+
gridTemplateRows: isExpanded ? "1fr" : "0fr",
|
|
3473
|
+
transition: `grid-template-rows ${GNB_EXPAND_MS}ms ease-out`
|
|
3474
|
+
},
|
|
3475
|
+
children: /* @__PURE__ */ jsx(
|
|
3476
|
+
"ul",
|
|
3477
|
+
{
|
|
3478
|
+
className: "m-0 flex min-h-0 list-none flex-col overflow-hidden p-0",
|
|
3479
|
+
style: {
|
|
3480
|
+
visibility: isExpanded ? "visible" : "hidden",
|
|
3481
|
+
transition: `visibility 0s linear ${isExpanded ? "0s" : `${GNB_EXPAND_MS}ms`}`
|
|
3482
|
+
},
|
|
3483
|
+
children: item.children.map((child) => renderItem(child, depth + 1, iconBranch))
|
|
3484
|
+
}
|
|
3485
|
+
)
|
|
3486
|
+
}
|
|
3487
|
+
)
|
|
3366
3488
|
] }, item.value)
|
|
3367
3489
|
);
|
|
3368
3490
|
};
|
|
3491
|
+
const railItems = useRail ? items : [];
|
|
3492
|
+
const menuItems = useRail ? items.find((item) => item.value === activeRail)?.children ?? [] : items;
|
|
3493
|
+
const showMenu = !useRail || menuItems.length > 0;
|
|
3494
|
+
const bodyWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (showMenu ? GNB_MENU_WIDTH : 0);
|
|
3495
|
+
useEffect(() => {
|
|
3496
|
+
layout?.reportGnbState({ forceFull: railHasLeaf, menuVisible: showMenu });
|
|
3497
|
+
}, [layout, railHasLeaf, showMenu]);
|
|
3369
3498
|
const railFolded = folded && header === "fix";
|
|
3370
|
-
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth :
|
|
3499
|
+
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : bodyWidth;
|
|
3371
3500
|
const rootStyle = {
|
|
3372
3501
|
width: rootWidth,
|
|
3502
|
+
transition: `width ${GNB_FOLD_MS}ms ease-out`,
|
|
3373
3503
|
...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
|
|
3374
3504
|
...style
|
|
3375
3505
|
};
|
|
3506
|
+
const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
|
|
3507
|
+
const foldInline = !!logo;
|
|
3508
|
+
const sideFoldStyle = {
|
|
3509
|
+
opacity: railFolded ? 0 : 1,
|
|
3510
|
+
position: railFolded ? "absolute" : "static",
|
|
3511
|
+
visibility: railFolded ? "hidden" : "visible",
|
|
3512
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}`
|
|
3513
|
+
};
|
|
3376
3514
|
return /* @__PURE__ */ jsxs(
|
|
3377
3515
|
"div",
|
|
3378
3516
|
{
|
|
3379
3517
|
ref,
|
|
3380
|
-
className: cn("box-border flex h-full shrink-0 flex-col", className),
|
|
3518
|
+
className: cn("box-border flex h-full shrink-0 flex-col overflow-hidden", className),
|
|
3381
3519
|
style: rootStyle,
|
|
3382
3520
|
"data-type": type,
|
|
3383
3521
|
"data-header": header,
|
|
3384
3522
|
"data-color": color,
|
|
3385
3523
|
...rest,
|
|
3386
3524
|
children: [
|
|
3387
|
-
/* @__PURE__ */
|
|
3525
|
+
/* @__PURE__ */ jsx(
|
|
3388
3526
|
"div",
|
|
3389
3527
|
{
|
|
3390
|
-
className:
|
|
3528
|
+
className: "relative box-border flex shrink-0 items-center overflow-hidden",
|
|
3391
3529
|
style: {
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3530
|
+
height: H.topHeight,
|
|
3531
|
+
// 인라인 간격은 컨테이너 gap 대신 각 요소 margin 으로 준다 — 런처↔폴드(8px)와
|
|
3532
|
+
// 폴드↔로고(top-gap)를 다르게 두기 위함. 접히면 런처·로고가 흐름에서 빠져 margin 도 무의미해진다.
|
|
3533
|
+
padding: `0 ${railFolded ? GNB_RAIL.padding : H.topPaddingX}`,
|
|
3395
3534
|
borderBottom: `1px solid ${C.topBorder}`,
|
|
3396
3535
|
backgroundColor: C.panelBg,
|
|
3397
3536
|
pointerEvents: "auto",
|
|
3537
|
+
transition: `padding ${GNB_FOLD_MS}ms ease-out`,
|
|
3398
3538
|
// 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
|
|
3399
3539
|
color: C.itemColorDefault
|
|
3400
3540
|
},
|
|
3401
|
-
children:
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3541
|
+
children: foldInline ? (
|
|
3542
|
+
// 런처(있을 때)·폴드·로고를 흐름에 나란히 둔다 → 폴드 버튼이 (런처와) 로고 사이에 선다.
|
|
3543
|
+
// 접히면 런처·로고가 페이드하며 흐름에서 빠지고(sideFoldStyle), 폴드 버튼만 남아 레일 가운데에 선다.
|
|
3544
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3545
|
+
onLauncherClick && /* @__PURE__ */ jsx(
|
|
3546
|
+
SGhostButton,
|
|
3547
|
+
{
|
|
3548
|
+
icon: "launcher",
|
|
3549
|
+
size: "xs",
|
|
3550
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3551
|
+
ariaLabel: "app launcher",
|
|
3552
|
+
onClick: onLauncherClick,
|
|
3553
|
+
className: "shrink-0",
|
|
3554
|
+
style: { ...sideFoldStyle, marginRight: "8px" }
|
|
3555
|
+
}
|
|
3556
|
+
),
|
|
3557
|
+
/* @__PURE__ */ jsx(
|
|
3558
|
+
SGhostButton,
|
|
3559
|
+
{
|
|
3560
|
+
icon: "sidebar",
|
|
3561
|
+
size: "xs",
|
|
3562
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3563
|
+
ariaLabel: "toggle navigation",
|
|
3564
|
+
ariaPressed: folded,
|
|
3565
|
+
onClick: handleFoldToggle,
|
|
3566
|
+
className: "shrink-0"
|
|
3567
|
+
}
|
|
3568
|
+
),
|
|
3569
|
+
/* @__PURE__ */ jsx(
|
|
3570
|
+
"div",
|
|
3571
|
+
{
|
|
3572
|
+
className: "flex shrink-0 items-center",
|
|
3573
|
+
style: { height: H.logoHeight, marginLeft: H.topGap, ...sideFoldStyle },
|
|
3574
|
+
children: logo
|
|
3575
|
+
}
|
|
3576
|
+
)
|
|
3577
|
+
] })
|
|
3578
|
+
) : (
|
|
3579
|
+
// 런처·로고는 좌측 그룹(흐름에서 빼 폭 축소 중 폴드 버튼을 밀어내지 않는다),
|
|
3580
|
+
// 폴드 버튼은 우측 끝(ml-auto)이라 폭·패딩이 줄면 저절로 레일 가운데에 선다.
|
|
3581
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3582
|
+
(onLauncherClick || logo) && /* @__PURE__ */ jsxs(
|
|
3583
|
+
"div",
|
|
3584
|
+
{
|
|
3585
|
+
className: "absolute inset-y-0 flex items-center",
|
|
3586
|
+
style: {
|
|
3587
|
+
left: H.topPaddingX,
|
|
3588
|
+
gap: H.topGap,
|
|
3589
|
+
opacity: railFolded ? 0 : 1,
|
|
3590
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
|
|
3591
|
+
// 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
|
|
3592
|
+
transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
|
|
3593
|
+
visibility: railFolded ? "hidden" : "visible"
|
|
3594
|
+
},
|
|
3595
|
+
children: [
|
|
3596
|
+
onLauncherClick && /* @__PURE__ */ jsx(
|
|
3597
|
+
SGhostButton,
|
|
3598
|
+
{
|
|
3599
|
+
icon: "launcher",
|
|
3600
|
+
size: "xs",
|
|
3601
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3602
|
+
ariaLabel: "app launcher",
|
|
3603
|
+
onClick: onLauncherClick,
|
|
3604
|
+
className: "shrink-0"
|
|
3605
|
+
}
|
|
3606
|
+
),
|
|
3607
|
+
logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
|
|
3608
|
+
]
|
|
3609
|
+
}
|
|
3610
|
+
),
|
|
3611
|
+
/* @__PURE__ */ jsx(
|
|
3612
|
+
SGhostButton,
|
|
3613
|
+
{
|
|
3614
|
+
icon: "sidebar",
|
|
3615
|
+
size: "xs",
|
|
3616
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3617
|
+
ariaLabel: "toggle navigation",
|
|
3618
|
+
ariaPressed: folded,
|
|
3619
|
+
onClick: handleFoldToggle,
|
|
3620
|
+
className: "ml-auto shrink-0"
|
|
3621
|
+
}
|
|
3622
|
+
)
|
|
3623
|
+
] })
|
|
3624
|
+
)
|
|
3427
3625
|
}
|
|
3428
3626
|
),
|
|
3429
|
-
|
|
3430
|
-
"
|
|
3627
|
+
/* @__PURE__ */ jsxs(
|
|
3628
|
+
"div",
|
|
3431
3629
|
{
|
|
3432
|
-
"
|
|
3433
|
-
className: "flex flex-col overflow-y-auto",
|
|
3630
|
+
className: "flex flex-row",
|
|
3434
3631
|
style: {
|
|
3435
|
-
width:
|
|
3632
|
+
width: bodyWidth,
|
|
3436
3633
|
alignSelf: "flex-start",
|
|
3437
3634
|
flexGrow: 1,
|
|
3438
3635
|
flexShrink: 1,
|
|
3439
3636
|
flexBasis: "auto",
|
|
3440
3637
|
minHeight: 0,
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
pointerEvents: "auto"
|
|
3638
|
+
transform: folded ? "translateX(-100%)" : "none",
|
|
3639
|
+
visibility: folded ? "hidden" : "visible",
|
|
3640
|
+
transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
|
|
3445
3641
|
},
|
|
3446
|
-
children:
|
|
3642
|
+
children: [
|
|
3643
|
+
useRail && // 레일도 내비게이션 landmark — nav 로 세워야 aria-label(레일)이 스크린리더에 노출된다
|
|
3644
|
+
// (role 없는 ul 의 aria-label 은 영역명으로 읽히지 않는다). 메뉴 nav 와는 레이블로 구분된다.
|
|
3645
|
+
/* @__PURE__ */ jsx(
|
|
3646
|
+
"nav",
|
|
3647
|
+
{
|
|
3648
|
+
"aria-label": `${ariaLabel} rail`,
|
|
3649
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
3650
|
+
style: {
|
|
3651
|
+
width: GNB_RAIL_WIDTH,
|
|
3652
|
+
padding: GNB_RAIL.padding,
|
|
3653
|
+
borderRight: `1px solid ${C.topBorder}`,
|
|
3654
|
+
backgroundColor: C.panelBg,
|
|
3655
|
+
pointerEvents: "auto"
|
|
3656
|
+
},
|
|
3657
|
+
children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", style: { gap: GNB_RAIL.gap }, children: railItems.map(renderRailItem) })
|
|
3658
|
+
}
|
|
3659
|
+
),
|
|
3660
|
+
showMenu && /* @__PURE__ */ jsx(
|
|
3661
|
+
"nav",
|
|
3662
|
+
{
|
|
3663
|
+
"aria-label": ariaLabel,
|
|
3664
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
3665
|
+
style: {
|
|
3666
|
+
width: GNB_MENU_WIDTH,
|
|
3667
|
+
paddingTop: M.bodyPaddingY,
|
|
3668
|
+
paddingBottom: M.bodyPaddingY,
|
|
3669
|
+
backgroundColor: C.panelBg,
|
|
3670
|
+
pointerEvents: "auto"
|
|
3671
|
+
},
|
|
3672
|
+
children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: menuItems.map((item) => renderItem(item, 1)) })
|
|
3673
|
+
}
|
|
3674
|
+
)
|
|
3675
|
+
]
|
|
3447
3676
|
}
|
|
3448
3677
|
)
|
|
3449
3678
|
]
|