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
|
@@ -3095,73 +3095,70 @@ function SGhostButton({
|
|
|
3095
3095
|
}
|
|
3096
3096
|
|
|
3097
3097
|
// src/components/SGnb/gnb.config.ts
|
|
3098
|
-
var
|
|
3099
|
-
var box = (p) => `var(--cmp-
|
|
3100
|
-
var
|
|
3101
|
-
var
|
|
3098
|
+
var gnb = (p) => `var(--cmp-gnb-${p})`;
|
|
3099
|
+
var box = (p) => `var(--cmp-gnb-box-${p})`;
|
|
3100
|
+
var belt = (p) => `var(--cmp-gnb-belt-${p})`;
|
|
3101
|
+
var light = (p) => `var(--cmp-gnb-light-${p})`;
|
|
3102
|
+
var dark = (p) => `var(--cmp-gnb-dark-${p})`;
|
|
3103
|
+
var HEADER_TOP = {
|
|
3104
|
+
topHeight: gnb("top-height"),
|
|
3105
|
+
topPaddingX: gnb("top-paddingX"),
|
|
3106
|
+
topGap: gnb("top-gap"),
|
|
3107
|
+
logoHeight: gnb("logo-height")
|
|
3108
|
+
};
|
|
3102
3109
|
var GNB_HEADER = {
|
|
3103
|
-
fix:
|
|
3104
|
-
|
|
3105
|
-
topPaddingX: belt("top-paddingX"),
|
|
3106
|
-
topGap: belt("top-gap"),
|
|
3107
|
-
logoHeight: belt("logo-height")
|
|
3108
|
-
},
|
|
3109
|
-
full: {
|
|
3110
|
-
topHeight: box("top-height"),
|
|
3111
|
-
topPaddingX: box("paddingX"),
|
|
3112
|
-
topGap: box("gap"),
|
|
3113
|
-
// 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)
|
|
3114
|
-
logoHeight: box("logo-height")
|
|
3115
|
-
}
|
|
3110
|
+
fix: HEADER_TOP,
|
|
3111
|
+
full: HEADER_TOP
|
|
3116
3112
|
};
|
|
3117
3113
|
var GNB_MENU = {
|
|
3118
3114
|
box: {
|
|
3119
|
-
//
|
|
3120
|
-
bodyPaddingY:
|
|
3121
|
-
itemMarginX:
|
|
3122
|
-
|
|
3115
|
+
// 바디 패딩·세로 패딩·chevron 은 gnb 로 통합된 공통 토큰
|
|
3116
|
+
bodyPaddingY: gnb("body-padding"),
|
|
3117
|
+
itemMarginX: gnb("body-padding"),
|
|
3118
|
+
// 전방향 토큰 → 아이템 좌우 여백에도 사용
|
|
3119
|
+
itemPaddingY: gnb("item-paddingY"),
|
|
3123
3120
|
itemPaddingRight: box("item-paddingRight"),
|
|
3124
3121
|
itemGap: box("item-gap"),
|
|
3125
3122
|
itemRadius: box("item-radius"),
|
|
3126
|
-
itemArrow:
|
|
3123
|
+
itemArrow: gnb("item-chevron-size"),
|
|
3127
3124
|
depth1PaddingLeft: box("item-depth1-paddingLeft"),
|
|
3128
3125
|
depth2PaddingLeft: box("item-depth2-paddingLeft"),
|
|
3129
3126
|
depth3PaddingLeft: box("item-depth3-paddingLeft"),
|
|
3130
3127
|
depth1Icon: box("item-depth1-icon"),
|
|
3131
3128
|
depth1FontSize: 14,
|
|
3132
|
-
// token:
|
|
3129
|
+
// token: gnb.box.item.typography.selected.fontSize
|
|
3133
3130
|
depth1LineHeight: 24,
|
|
3134
|
-
// token:
|
|
3131
|
+
// token: gnb.box.item.typography.selected.lineHeight
|
|
3135
3132
|
depth1FontWeight: 700,
|
|
3136
3133
|
itemFontSize: 14,
|
|
3137
|
-
// token:
|
|
3134
|
+
// token: gnb.box.item.typography.default.fontSize
|
|
3138
3135
|
itemLineHeight: 24
|
|
3139
|
-
// token:
|
|
3136
|
+
// token: gnb.box.item.typography.default.lineHeight
|
|
3140
3137
|
},
|
|
3141
3138
|
belt: {
|
|
3142
|
-
bodyPaddingY:
|
|
3139
|
+
bodyPaddingY: gnb("body-padding"),
|
|
3143
3140
|
itemMarginX: "0px",
|
|
3144
3141
|
// belt 토큰에 가로 여백 없음 → 풀폭 행 (들여쓰기는 depth*-paddingLeft 전담)
|
|
3145
|
-
itemPaddingY:
|
|
3142
|
+
itemPaddingY: gnb("item-paddingY"),
|
|
3146
3143
|
itemPaddingRight: belt("item-paddingRight"),
|
|
3147
3144
|
itemGap: belt("item-gap"),
|
|
3148
3145
|
itemRadius: "0px",
|
|
3149
3146
|
// belt 토큰에 radius 없음 → 풀폭 행
|
|
3150
|
-
itemArrow:
|
|
3147
|
+
itemArrow: gnb("item-chevron-size"),
|
|
3151
3148
|
depth1PaddingLeft: belt("item-depth1-paddingLeft"),
|
|
3152
3149
|
depth2PaddingLeft: belt("item-depth2-paddingLeft"),
|
|
3153
3150
|
depth3PaddingLeft: belt("item-depth3-paddingLeft"),
|
|
3154
|
-
depth1Icon:
|
|
3155
|
-
// belt
|
|
3151
|
+
depth1Icon: belt("item-depth1-icon"),
|
|
3152
|
+
// belt 전용 depth1 아이콘 토큰(16)
|
|
3156
3153
|
depth1FontSize: 13,
|
|
3157
|
-
// token:
|
|
3154
|
+
// token: gnb.belt.item.depth1.typography.fontSize
|
|
3158
3155
|
depth1LineHeight: 22,
|
|
3159
|
-
// token:
|
|
3156
|
+
// token: gnb.belt.item.depth1.typography.lineHeight
|
|
3160
3157
|
depth1FontWeight: 700,
|
|
3161
3158
|
itemFontSize: 12,
|
|
3162
|
-
// token:
|
|
3159
|
+
// token: gnb.belt.item.typography.default.fontSize
|
|
3163
3160
|
itemLineHeight: 20
|
|
3164
|
-
// token:
|
|
3161
|
+
// token: gnb.belt.item.typography.default.lineHeight
|
|
3165
3162
|
}
|
|
3166
3163
|
};
|
|
3167
3164
|
var GNB_COLORS = {
|
|
@@ -3174,7 +3171,9 @@ var GNB_COLORS = {
|
|
|
3174
3171
|
itemColorDefault: light("item-fg-default"),
|
|
3175
3172
|
itemColorHover: light("item-fg-hover"),
|
|
3176
3173
|
itemColorActive: light("item-fg-selected"),
|
|
3177
|
-
itemChevron: light("item-chevron")
|
|
3174
|
+
itemChevron: light("item-chevron"),
|
|
3175
|
+
itemIcon: "grey_65",
|
|
3176
|
+
railFgDefault: light("rail-fg-default")
|
|
3178
3177
|
},
|
|
3179
3178
|
dark: {
|
|
3180
3179
|
panelBg: dark("bg"),
|
|
@@ -3185,19 +3184,41 @@ var GNB_COLORS = {
|
|
|
3185
3184
|
itemColorDefault: dark("item-fg-default"),
|
|
3186
3185
|
itemColorHover: dark("item-fg-hover"),
|
|
3187
3186
|
itemColorActive: dark("item-fg-selected"),
|
|
3188
|
-
itemChevron: dark("item-chevron")
|
|
3187
|
+
itemChevron: dark("item-chevron"),
|
|
3188
|
+
itemIcon: "grey_55",
|
|
3189
|
+
railFgDefault: dark("rail-fg-default")
|
|
3189
3190
|
}
|
|
3190
3191
|
};
|
|
3191
3192
|
var GNB_COMMON = {
|
|
3192
3193
|
/** 접힘 레일 폭. header="full" 은 접으면 메뉴만 사라져 레일이 없으므로 fix 전용. */
|
|
3193
|
-
foldWidth:
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3194
|
+
foldWidth: gnb("fold-width")
|
|
3195
|
+
};
|
|
3196
|
+
var GNB_RAIL = {
|
|
3197
|
+
padding: gnb("rail-padding"),
|
|
3198
|
+
gap: gnb("rail-gap"),
|
|
3199
|
+
itemWidth: gnb("rail-item-width"),
|
|
3200
|
+
itemPaddingTop: gnb("rail-item-paddingTop"),
|
|
3201
|
+
itemPaddingBottom: gnb("rail-item-paddingBottom"),
|
|
3202
|
+
itemRadius: gnb("rail-item-radius"),
|
|
3203
|
+
itemIcon: gnb("rail-item-icon"),
|
|
3204
|
+
// 타이포 (theme.css 에 gnb 타이포 변수 없음 → 토큰 값 직접 명시)
|
|
3205
|
+
itemFontSize: 11,
|
|
3206
|
+
// token: gnb.rail.item.typography.default.fontSize
|
|
3207
|
+
itemLineHeight: 18,
|
|
3208
|
+
// token: gnb.rail.item.typography.default.lineHeight
|
|
3209
|
+
itemFontWeight: 500,
|
|
3210
|
+
// token: gnb.rail.item.typography.default.fontWeight
|
|
3211
|
+
itemFontWeightSelected: 700
|
|
3212
|
+
// token: gnb.rail.item.typography.selected.fontWeight
|
|
3213
|
+
};
|
|
3214
|
+
var GNB_RAIL_WIDTH = 68;
|
|
3215
|
+
var GNB_FOLD_MS = 200;
|
|
3216
|
+
var GNB_EXPAND_MS = 200;
|
|
3200
3217
|
var GNB_MENU_WIDTH = 240;
|
|
3218
|
+
function findGnbRailValue(items, value) {
|
|
3219
|
+
if (items.some((item) => item.value === value)) return value;
|
|
3220
|
+
return findGnbAncestors(items, value)[0];
|
|
3221
|
+
}
|
|
3201
3222
|
function findGnbAncestors(items, value) {
|
|
3202
3223
|
const walk = (list, trail) => {
|
|
3203
3224
|
for (const item of list) {
|
|
@@ -3215,6 +3236,7 @@ var SLayoutContext = react.createContext(null);
|
|
|
3215
3236
|
react.forwardRef(function SLayout2({
|
|
3216
3237
|
type = "box",
|
|
3217
3238
|
header = "fix",
|
|
3239
|
+
useRail = false,
|
|
3218
3240
|
folded,
|
|
3219
3241
|
defaultFolded = false,
|
|
3220
3242
|
onFoldedChange,
|
|
@@ -3233,21 +3255,40 @@ react.forwardRef(function SLayout2({
|
|
|
3233
3255
|
},
|
|
3234
3256
|
[isControlled, onFoldedChange]
|
|
3235
3257
|
);
|
|
3236
|
-
const
|
|
3237
|
-
|
|
3238
|
-
|
|
3258
|
+
const [gnbState, setGnbState] = react.useState({ forceFull: false, menuVisible: true });
|
|
3259
|
+
const reportGnbState = react.useCallback(
|
|
3260
|
+
(next) => setGnbState(
|
|
3261
|
+
(prev) => prev.forceFull === next.forceFull && prev.menuVisible === next.menuVisible ? prev : next
|
|
3262
|
+
),
|
|
3263
|
+
[]
|
|
3239
3264
|
);
|
|
3240
|
-
const
|
|
3265
|
+
const effectiveHeader = header === "full" || gnbState.forceFull ? "full" : "fix";
|
|
3266
|
+
const ctx = react.useMemo(
|
|
3267
|
+
() => ({
|
|
3268
|
+
type,
|
|
3269
|
+
header: effectiveHeader,
|
|
3270
|
+
useRail,
|
|
3271
|
+
folded: currentFolded,
|
|
3272
|
+
setFolded,
|
|
3273
|
+
reportGnbState
|
|
3274
|
+
}),
|
|
3275
|
+
[type, effectiveHeader, useRail, currentFolded, setFolded, reportGnbState]
|
|
3276
|
+
);
|
|
3277
|
+
const isFullHeader = effectiveHeader === "full";
|
|
3278
|
+
const gnbColumnWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (gnbState.menuVisible ? GNB_MENU_WIDTH : 0);
|
|
3241
3279
|
const frameStyle = isFullHeader ? {
|
|
3242
|
-
gridTemplateColumns: `${currentFolded ? 0 :
|
|
3243
|
-
gridTemplateRows: `${GNB_HEADER[
|
|
3280
|
+
gridTemplateColumns: `${currentFolded ? 0 : gnbColumnWidth}px 1fr`,
|
|
3281
|
+
gridTemplateRows: `${GNB_HEADER[effectiveHeader].topHeight} 1fr`,
|
|
3282
|
+
// 메뉴가 미끄러지는 동안 페이지 열도 같은 길이로 따라와야 한 덩어리로 읽힌다.
|
|
3283
|
+
// (fix 는 SGnb 가 제 폭을 직접 전환하므로 프레임이 손댈 게 없다)
|
|
3284
|
+
transition: `grid-template-columns ${GNB_FOLD_MS}ms ease-out`
|
|
3244
3285
|
} : void 0;
|
|
3245
3286
|
return /* @__PURE__ */ jsxRuntime.jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3246
3287
|
"div",
|
|
3247
3288
|
{
|
|
3248
3289
|
ref,
|
|
3249
3290
|
"data-type": type,
|
|
3250
|
-
"data-header":
|
|
3291
|
+
"data-header": effectiveHeader,
|
|
3251
3292
|
className: cn(
|
|
3252
3293
|
"box-border h-full overflow-hidden",
|
|
3253
3294
|
isFullHeader ? "grid" : "flex flex-row",
|
|
@@ -3265,8 +3306,10 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3265
3306
|
header: headerProp,
|
|
3266
3307
|
color = "light",
|
|
3267
3308
|
items,
|
|
3309
|
+
useRail: useRailProp,
|
|
3268
3310
|
value = "",
|
|
3269
3311
|
onValueChange,
|
|
3312
|
+
onRailChange,
|
|
3270
3313
|
folded: foldedProp,
|
|
3271
3314
|
onFoldChange,
|
|
3272
3315
|
onLauncherClick,
|
|
@@ -3278,8 +3321,10 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3278
3321
|
}, ref) {
|
|
3279
3322
|
const layout = react.useContext(SLayoutContext);
|
|
3280
3323
|
const type = typeProp ?? layout?.type ?? "box";
|
|
3281
|
-
const header = headerProp ?? layout?.header ?? "fix";
|
|
3282
3324
|
const folded = foldedProp ?? layout?.folded ?? false;
|
|
3325
|
+
const useRail = useRailProp ?? layout?.useRail ?? false;
|
|
3326
|
+
const railHasLeaf = useRail && items.some((item) => !item.children || item.children.length === 0);
|
|
3327
|
+
const header = railHasLeaf ? "full" : headerProp ?? layout?.header ?? "fix";
|
|
3283
3328
|
const H = GNB_HEADER[header];
|
|
3284
3329
|
const M = GNB_MENU[type];
|
|
3285
3330
|
const C = GNB_COLORS[color];
|
|
@@ -3287,6 +3332,9 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3287
3332
|
() => new Set(findGnbAncestors(items, value))
|
|
3288
3333
|
);
|
|
3289
3334
|
const [hoveredKey, setHoveredKey] = react.useState(null);
|
|
3335
|
+
const [activeRail, setActiveRail] = react.useState(
|
|
3336
|
+
() => findGnbRailValue(items, value) ?? items[0]?.value
|
|
3337
|
+
);
|
|
3290
3338
|
react.useEffect(() => {
|
|
3291
3339
|
const ancestors = findGnbAncestors(items, value);
|
|
3292
3340
|
if (ancestors.length === 0) return;
|
|
@@ -3297,6 +3345,15 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3297
3345
|
return next;
|
|
3298
3346
|
});
|
|
3299
3347
|
}, [items, value]);
|
|
3348
|
+
react.useEffect(() => {
|
|
3349
|
+
const owner = findGnbRailValue(items, value);
|
|
3350
|
+
if (owner !== void 0) setActiveRail(owner);
|
|
3351
|
+
}, [items, value]);
|
|
3352
|
+
react.useEffect(() => {
|
|
3353
|
+
setActiveRail(
|
|
3354
|
+
(prev) => prev !== void 0 && items.some((item) => item.value === prev) ? prev : items[0]?.value
|
|
3355
|
+
);
|
|
3356
|
+
}, [items]);
|
|
3300
3357
|
const toggleExpand = (key) => {
|
|
3301
3358
|
setExpandedKeys((prev) => {
|
|
3302
3359
|
const next = new Set(prev);
|
|
@@ -3313,16 +3370,58 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3313
3370
|
}
|
|
3314
3371
|
onValueChange?.(item.value);
|
|
3315
3372
|
};
|
|
3373
|
+
const handleRailClick = (item) => {
|
|
3374
|
+
if (item.disabled) return;
|
|
3375
|
+
setActiveRail(item.value);
|
|
3376
|
+
onRailChange?.(item.value);
|
|
3377
|
+
if (!item.children || item.children.length === 0) onValueChange?.(item.value);
|
|
3378
|
+
};
|
|
3316
3379
|
const handleFoldToggle = () => {
|
|
3317
3380
|
const next = !folded;
|
|
3318
3381
|
onFoldChange?.(next);
|
|
3319
3382
|
if (foldedProp === void 0) layout?.setFolded(next);
|
|
3320
3383
|
};
|
|
3321
|
-
const
|
|
3384
|
+
const renderRailItem = (item) => {
|
|
3385
|
+
const isActive = item.value === activeRail;
|
|
3386
|
+
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
3387
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3388
|
+
"button",
|
|
3389
|
+
{
|
|
3390
|
+
type: "button",
|
|
3391
|
+
disabled: item.disabled,
|
|
3392
|
+
"aria-current": isActive ? "true" : void 0,
|
|
3393
|
+
className: "box-border flex select-none flex-col items-center border-none bg-transparent transition-colors duration-150",
|
|
3394
|
+
style: {
|
|
3395
|
+
width: GNB_RAIL.itemWidth,
|
|
3396
|
+
paddingTop: GNB_RAIL.itemPaddingTop,
|
|
3397
|
+
paddingBottom: GNB_RAIL.itemPaddingBottom,
|
|
3398
|
+
borderRadius: GNB_RAIL.itemRadius,
|
|
3399
|
+
backgroundColor: isActive ? C.itemBgSelected : isHovered ? C.itemBgHover : C.itemBgDefault,
|
|
3400
|
+
color: isActive ? C.itemColorActive : C.railFgDefault,
|
|
3401
|
+
// <button> 의 UA 폰트를 끊고 앱 폰트를 잇는다 (메뉴 아이템과 같은 이유)
|
|
3402
|
+
fontFamily: "inherit",
|
|
3403
|
+
fontSize: GNB_RAIL.itemFontSize,
|
|
3404
|
+
lineHeight: `${GNB_RAIL.itemLineHeight}px`,
|
|
3405
|
+
fontWeight: isActive ? GNB_RAIL.itemFontWeightSelected : GNB_RAIL.itemFontWeight,
|
|
3406
|
+
opacity: item.disabled ? 0.4 : 1,
|
|
3407
|
+
cursor: item.disabled ? "not-allowed" : "pointer"
|
|
3408
|
+
},
|
|
3409
|
+
onClick: () => handleRailClick(item),
|
|
3410
|
+
onMouseEnter: () => setHoveredKey(item.value),
|
|
3411
|
+
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
3412
|
+
children: [
|
|
3413
|
+
item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: GNB_RAIL.itemIcon, className: "shrink-0" }),
|
|
3414
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-center", children: item.label })
|
|
3415
|
+
]
|
|
3416
|
+
}
|
|
3417
|
+
) }, item.value);
|
|
3418
|
+
};
|
|
3419
|
+
const renderItem = (item, depth, branchHasIcon = false) => {
|
|
3322
3420
|
const hasChildren = !!(item.children && item.children.length > 0);
|
|
3323
3421
|
const isSelected = !hasChildren && item.value === value;
|
|
3324
3422
|
const isExpanded = expandedKeys.has(item.value);
|
|
3325
3423
|
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
3424
|
+
const iconBranch = depth === 1 ? !!item.icon : branchHasIcon;
|
|
3326
3425
|
const itemStyle = {
|
|
3327
3426
|
gap: M.itemGap,
|
|
3328
3427
|
paddingTop: M.itemPaddingY,
|
|
@@ -3359,8 +3458,11 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3359
3458
|
onMouseEnter: () => setHoveredKey(item.value),
|
|
3360
3459
|
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
3361
3460
|
children: [
|
|
3362
|
-
depth === 1 && item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: M.depth1Icon, className: "shrink-0" }),
|
|
3363
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3461
|
+
depth === 1 && item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: M.depth1Icon, color: C.itemIcon, className: "shrink-0" }),
|
|
3462
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap flex flex-no-wrap items-center", children: [
|
|
3463
|
+
depth > 1 && iconBranch ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[20px] h-[24px] flex items-center justify-center mr-[8px]", children: "-" }) : null,
|
|
3464
|
+
item.label
|
|
3465
|
+
] }),
|
|
3364
3466
|
item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3365
3467
|
STag,
|
|
3366
3468
|
{
|
|
@@ -3378,94 +3480,221 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
3378
3480
|
size: M.itemArrow,
|
|
3379
3481
|
color: C.itemChevron,
|
|
3380
3482
|
rotate: isExpanded ? 180 : 0,
|
|
3381
|
-
className: "shrink-0 transition-transform duration-
|
|
3483
|
+
className: "shrink-0 transition-transform duration-200"
|
|
3382
3484
|
}
|
|
3383
3485
|
)
|
|
3384
3486
|
]
|
|
3385
3487
|
}
|
|
3386
3488
|
),
|
|
3387
|
-
hasChildren &&
|
|
3489
|
+
hasChildren && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3490
|
+
"div",
|
|
3491
|
+
{
|
|
3492
|
+
className: "grid",
|
|
3493
|
+
style: {
|
|
3494
|
+
gridTemplateRows: isExpanded ? "1fr" : "0fr",
|
|
3495
|
+
transition: `grid-template-rows ${GNB_EXPAND_MS}ms ease-out`
|
|
3496
|
+
},
|
|
3497
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3498
|
+
"ul",
|
|
3499
|
+
{
|
|
3500
|
+
className: "m-0 flex min-h-0 list-none flex-col overflow-hidden p-0",
|
|
3501
|
+
style: {
|
|
3502
|
+
visibility: isExpanded ? "visible" : "hidden",
|
|
3503
|
+
transition: `visibility 0s linear ${isExpanded ? "0s" : `${GNB_EXPAND_MS}ms`}`
|
|
3504
|
+
},
|
|
3505
|
+
children: item.children.map((child) => renderItem(child, depth + 1, iconBranch))
|
|
3506
|
+
}
|
|
3507
|
+
)
|
|
3508
|
+
}
|
|
3509
|
+
)
|
|
3388
3510
|
] }, item.value)
|
|
3389
3511
|
);
|
|
3390
3512
|
};
|
|
3513
|
+
const railItems = useRail ? items : [];
|
|
3514
|
+
const menuItems = useRail ? items.find((item) => item.value === activeRail)?.children ?? [] : items;
|
|
3515
|
+
const showMenu = !useRail || menuItems.length > 0;
|
|
3516
|
+
const bodyWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (showMenu ? GNB_MENU_WIDTH : 0);
|
|
3517
|
+
react.useEffect(() => {
|
|
3518
|
+
layout?.reportGnbState({ forceFull: railHasLeaf, menuVisible: showMenu });
|
|
3519
|
+
}, [layout, railHasLeaf, showMenu]);
|
|
3391
3520
|
const railFolded = folded && header === "fix";
|
|
3392
|
-
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth :
|
|
3521
|
+
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : bodyWidth;
|
|
3393
3522
|
const rootStyle = {
|
|
3394
3523
|
width: rootWidth,
|
|
3524
|
+
transition: `width ${GNB_FOLD_MS}ms ease-out`,
|
|
3395
3525
|
...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
|
|
3396
3526
|
...style
|
|
3397
3527
|
};
|
|
3528
|
+
const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
|
|
3529
|
+
const foldInline = !!logo;
|
|
3530
|
+
const sideFoldStyle = {
|
|
3531
|
+
opacity: railFolded ? 0 : 1,
|
|
3532
|
+
position: railFolded ? "absolute" : "static",
|
|
3533
|
+
visibility: railFolded ? "hidden" : "visible",
|
|
3534
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}`
|
|
3535
|
+
};
|
|
3398
3536
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3399
3537
|
"div",
|
|
3400
3538
|
{
|
|
3401
3539
|
ref,
|
|
3402
|
-
className: cn("box-border flex h-full shrink-0 flex-col", className),
|
|
3540
|
+
className: cn("box-border flex h-full shrink-0 flex-col overflow-hidden", className),
|
|
3403
3541
|
style: rootStyle,
|
|
3404
3542
|
"data-type": type,
|
|
3405
3543
|
"data-header": header,
|
|
3406
3544
|
"data-color": color,
|
|
3407
3545
|
...rest,
|
|
3408
3546
|
children: [
|
|
3409
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3547
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3410
3548
|
"div",
|
|
3411
3549
|
{
|
|
3412
|
-
className:
|
|
3550
|
+
className: "relative box-border flex shrink-0 items-center overflow-hidden",
|
|
3413
3551
|
style: {
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3552
|
+
height: H.topHeight,
|
|
3553
|
+
// 인라인 간격은 컨테이너 gap 대신 각 요소 margin 으로 준다 — 런처↔폴드(8px)와
|
|
3554
|
+
// 폴드↔로고(top-gap)를 다르게 두기 위함. 접히면 런처·로고가 흐름에서 빠져 margin 도 무의미해진다.
|
|
3555
|
+
padding: `0 ${railFolded ? GNB_RAIL.padding : H.topPaddingX}`,
|
|
3417
3556
|
borderBottom: `1px solid ${C.topBorder}`,
|
|
3418
3557
|
backgroundColor: C.panelBg,
|
|
3419
3558
|
pointerEvents: "auto",
|
|
3559
|
+
transition: `padding ${GNB_FOLD_MS}ms ease-out`,
|
|
3420
3560
|
// 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
|
|
3421
3561
|
color: C.itemColorDefault
|
|
3422
3562
|
},
|
|
3423
|
-
children:
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3563
|
+
children: foldInline ? (
|
|
3564
|
+
// 런처(있을 때)·폴드·로고를 흐름에 나란히 둔다 → 폴드 버튼이 (런처와) 로고 사이에 선다.
|
|
3565
|
+
// 접히면 런처·로고가 페이드하며 흐름에서 빠지고(sideFoldStyle), 폴드 버튼만 남아 레일 가운데에 선다.
|
|
3566
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3567
|
+
onLauncherClick && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3568
|
+
SGhostButton,
|
|
3569
|
+
{
|
|
3570
|
+
icon: "launcher",
|
|
3571
|
+
size: "xs",
|
|
3572
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3573
|
+
ariaLabel: "app launcher",
|
|
3574
|
+
onClick: onLauncherClick,
|
|
3575
|
+
className: "shrink-0",
|
|
3576
|
+
style: { ...sideFoldStyle, marginRight: "8px" }
|
|
3577
|
+
}
|
|
3578
|
+
),
|
|
3579
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3580
|
+
SGhostButton,
|
|
3581
|
+
{
|
|
3582
|
+
icon: "sidebar",
|
|
3583
|
+
size: "xs",
|
|
3584
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3585
|
+
ariaLabel: "toggle navigation",
|
|
3586
|
+
ariaPressed: folded,
|
|
3587
|
+
onClick: handleFoldToggle,
|
|
3588
|
+
className: "shrink-0"
|
|
3589
|
+
}
|
|
3590
|
+
),
|
|
3591
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3592
|
+
"div",
|
|
3593
|
+
{
|
|
3594
|
+
className: "flex shrink-0 items-center",
|
|
3595
|
+
style: { height: H.logoHeight, marginLeft: H.topGap, ...sideFoldStyle },
|
|
3596
|
+
children: logo
|
|
3597
|
+
}
|
|
3598
|
+
)
|
|
3599
|
+
] })
|
|
3600
|
+
) : (
|
|
3601
|
+
// 런처·로고는 좌측 그룹(흐름에서 빼 폭 축소 중 폴드 버튼을 밀어내지 않는다),
|
|
3602
|
+
// 폴드 버튼은 우측 끝(ml-auto)이라 폭·패딩이 줄면 저절로 레일 가운데에 선다.
|
|
3603
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3604
|
+
(onLauncherClick || logo) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3605
|
+
"div",
|
|
3606
|
+
{
|
|
3607
|
+
className: "absolute inset-y-0 flex items-center",
|
|
3608
|
+
style: {
|
|
3609
|
+
left: H.topPaddingX,
|
|
3610
|
+
gap: H.topGap,
|
|
3611
|
+
opacity: railFolded ? 0 : 1,
|
|
3612
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
|
|
3613
|
+
// 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
|
|
3614
|
+
transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
|
|
3615
|
+
visibility: railFolded ? "hidden" : "visible"
|
|
3616
|
+
},
|
|
3617
|
+
children: [
|
|
3618
|
+
onLauncherClick && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3619
|
+
SGhostButton,
|
|
3620
|
+
{
|
|
3621
|
+
icon: "launcher",
|
|
3622
|
+
size: "xs",
|
|
3623
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3624
|
+
ariaLabel: "app launcher",
|
|
3625
|
+
onClick: onLauncherClick,
|
|
3626
|
+
className: "shrink-0"
|
|
3627
|
+
}
|
|
3628
|
+
),
|
|
3629
|
+
logo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
|
|
3630
|
+
]
|
|
3631
|
+
}
|
|
3632
|
+
),
|
|
3633
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3634
|
+
SGhostButton,
|
|
3635
|
+
{
|
|
3636
|
+
icon: "sidebar",
|
|
3637
|
+
size: "xs",
|
|
3638
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
3639
|
+
ariaLabel: "toggle navigation",
|
|
3640
|
+
ariaPressed: folded,
|
|
3641
|
+
onClick: handleFoldToggle,
|
|
3642
|
+
className: "ml-auto shrink-0"
|
|
3643
|
+
}
|
|
3644
|
+
)
|
|
3645
|
+
] })
|
|
3646
|
+
)
|
|
3449
3647
|
}
|
|
3450
3648
|
),
|
|
3451
|
-
|
|
3452
|
-
"
|
|
3649
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3650
|
+
"div",
|
|
3453
3651
|
{
|
|
3454
|
-
"
|
|
3455
|
-
className: "flex flex-col overflow-y-auto",
|
|
3652
|
+
className: "flex flex-row",
|
|
3456
3653
|
style: {
|
|
3457
|
-
width:
|
|
3654
|
+
width: bodyWidth,
|
|
3458
3655
|
alignSelf: "flex-start",
|
|
3459
3656
|
flexGrow: 1,
|
|
3460
3657
|
flexShrink: 1,
|
|
3461
3658
|
flexBasis: "auto",
|
|
3462
3659
|
minHeight: 0,
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
pointerEvents: "auto"
|
|
3660
|
+
transform: folded ? "translateX(-100%)" : "none",
|
|
3661
|
+
visibility: folded ? "hidden" : "visible",
|
|
3662
|
+
transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
|
|
3467
3663
|
},
|
|
3468
|
-
children:
|
|
3664
|
+
children: [
|
|
3665
|
+
useRail && // 레일도 내비게이션 landmark — nav 로 세워야 aria-label(레일)이 스크린리더에 노출된다
|
|
3666
|
+
// (role 없는 ul 의 aria-label 은 영역명으로 읽히지 않는다). 메뉴 nav 와는 레이블로 구분된다.
|
|
3667
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3668
|
+
"nav",
|
|
3669
|
+
{
|
|
3670
|
+
"aria-label": `${ariaLabel} rail`,
|
|
3671
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
3672
|
+
style: {
|
|
3673
|
+
width: GNB_RAIL_WIDTH,
|
|
3674
|
+
padding: GNB_RAIL.padding,
|
|
3675
|
+
borderRight: `1px solid ${C.topBorder}`,
|
|
3676
|
+
backgroundColor: C.panelBg,
|
|
3677
|
+
pointerEvents: "auto"
|
|
3678
|
+
},
|
|
3679
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col p-0", style: { gap: GNB_RAIL.gap }, children: railItems.map(renderRailItem) })
|
|
3680
|
+
}
|
|
3681
|
+
),
|
|
3682
|
+
showMenu && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3683
|
+
"nav",
|
|
3684
|
+
{
|
|
3685
|
+
"aria-label": ariaLabel,
|
|
3686
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
3687
|
+
style: {
|
|
3688
|
+
width: GNB_MENU_WIDTH,
|
|
3689
|
+
paddingTop: M.bodyPaddingY,
|
|
3690
|
+
paddingBottom: M.bodyPaddingY,
|
|
3691
|
+
backgroundColor: C.panelBg,
|
|
3692
|
+
pointerEvents: "auto"
|
|
3693
|
+
},
|
|
3694
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: menuItems.map((item) => renderItem(item, 1)) })
|
|
3695
|
+
}
|
|
3696
|
+
)
|
|
3697
|
+
]
|
|
3469
3698
|
}
|
|
3470
3699
|
)
|
|
3471
3700
|
]
|