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
package/dist/index.cjs
CHANGED
|
@@ -4904,73 +4904,70 @@ function SLoadingModal({
|
|
|
4904
4904
|
}
|
|
4905
4905
|
|
|
4906
4906
|
// src/components/SGnb/gnb.config.ts
|
|
4907
|
-
var
|
|
4908
|
-
var box = (p) => `var(--cmp-
|
|
4909
|
-
var
|
|
4910
|
-
var
|
|
4907
|
+
var gnb = (p) => `var(--cmp-gnb-${p})`;
|
|
4908
|
+
var box = (p) => `var(--cmp-gnb-box-${p})`;
|
|
4909
|
+
var belt = (p) => `var(--cmp-gnb-belt-${p})`;
|
|
4910
|
+
var light = (p) => `var(--cmp-gnb-light-${p})`;
|
|
4911
|
+
var dark = (p) => `var(--cmp-gnb-dark-${p})`;
|
|
4912
|
+
var HEADER_TOP = {
|
|
4913
|
+
topHeight: gnb("top-height"),
|
|
4914
|
+
topPaddingX: gnb("top-paddingX"),
|
|
4915
|
+
topGap: gnb("top-gap"),
|
|
4916
|
+
logoHeight: gnb("logo-height")
|
|
4917
|
+
};
|
|
4911
4918
|
var GNB_HEADER = {
|
|
4912
|
-
fix:
|
|
4913
|
-
|
|
4914
|
-
topPaddingX: belt("top-paddingX"),
|
|
4915
|
-
topGap: belt("top-gap"),
|
|
4916
|
-
logoHeight: belt("logo-height")
|
|
4917
|
-
},
|
|
4918
|
-
full: {
|
|
4919
|
-
topHeight: box("top-height"),
|
|
4920
|
-
topPaddingX: box("paddingX"),
|
|
4921
|
-
topGap: box("gap"),
|
|
4922
|
-
// 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)
|
|
4923
|
-
logoHeight: box("logo-height")
|
|
4924
|
-
}
|
|
4919
|
+
fix: HEADER_TOP,
|
|
4920
|
+
full: HEADER_TOP
|
|
4925
4921
|
};
|
|
4926
4922
|
var GNB_MENU = {
|
|
4927
4923
|
box: {
|
|
4928
|
-
//
|
|
4929
|
-
bodyPaddingY:
|
|
4930
|
-
itemMarginX:
|
|
4931
|
-
|
|
4924
|
+
// 바디 패딩·세로 패딩·chevron 은 gnb 로 통합된 공통 토큰
|
|
4925
|
+
bodyPaddingY: gnb("body-padding"),
|
|
4926
|
+
itemMarginX: gnb("body-padding"),
|
|
4927
|
+
// 전방향 토큰 → 아이템 좌우 여백에도 사용
|
|
4928
|
+
itemPaddingY: gnb("item-paddingY"),
|
|
4932
4929
|
itemPaddingRight: box("item-paddingRight"),
|
|
4933
4930
|
itemGap: box("item-gap"),
|
|
4934
4931
|
itemRadius: box("item-radius"),
|
|
4935
|
-
itemArrow:
|
|
4932
|
+
itemArrow: gnb("item-chevron-size"),
|
|
4936
4933
|
depth1PaddingLeft: box("item-depth1-paddingLeft"),
|
|
4937
4934
|
depth2PaddingLeft: box("item-depth2-paddingLeft"),
|
|
4938
4935
|
depth3PaddingLeft: box("item-depth3-paddingLeft"),
|
|
4939
4936
|
depth1Icon: box("item-depth1-icon"),
|
|
4940
4937
|
depth1FontSize: 14,
|
|
4941
|
-
// token:
|
|
4938
|
+
// token: gnb.box.item.typography.selected.fontSize
|
|
4942
4939
|
depth1LineHeight: 24,
|
|
4943
|
-
// token:
|
|
4940
|
+
// token: gnb.box.item.typography.selected.lineHeight
|
|
4944
4941
|
depth1FontWeight: 700,
|
|
4945
4942
|
itemFontSize: 14,
|
|
4946
|
-
// token:
|
|
4943
|
+
// token: gnb.box.item.typography.default.fontSize
|
|
4947
4944
|
itemLineHeight: 24
|
|
4948
|
-
// token:
|
|
4945
|
+
// token: gnb.box.item.typography.default.lineHeight
|
|
4949
4946
|
},
|
|
4950
4947
|
belt: {
|
|
4951
|
-
bodyPaddingY:
|
|
4948
|
+
bodyPaddingY: gnb("body-padding"),
|
|
4952
4949
|
itemMarginX: "0px",
|
|
4953
4950
|
// belt 토큰에 가로 여백 없음 → 풀폭 행 (들여쓰기는 depth*-paddingLeft 전담)
|
|
4954
|
-
itemPaddingY:
|
|
4951
|
+
itemPaddingY: gnb("item-paddingY"),
|
|
4955
4952
|
itemPaddingRight: belt("item-paddingRight"),
|
|
4956
4953
|
itemGap: belt("item-gap"),
|
|
4957
4954
|
itemRadius: "0px",
|
|
4958
4955
|
// belt 토큰에 radius 없음 → 풀폭 행
|
|
4959
|
-
itemArrow:
|
|
4956
|
+
itemArrow: gnb("item-chevron-size"),
|
|
4960
4957
|
depth1PaddingLeft: belt("item-depth1-paddingLeft"),
|
|
4961
4958
|
depth2PaddingLeft: belt("item-depth2-paddingLeft"),
|
|
4962
4959
|
depth3PaddingLeft: belt("item-depth3-paddingLeft"),
|
|
4963
|
-
depth1Icon:
|
|
4964
|
-
// belt
|
|
4960
|
+
depth1Icon: belt("item-depth1-icon"),
|
|
4961
|
+
// belt 전용 depth1 아이콘 토큰(16)
|
|
4965
4962
|
depth1FontSize: 13,
|
|
4966
|
-
// token:
|
|
4963
|
+
// token: gnb.belt.item.depth1.typography.fontSize
|
|
4967
4964
|
depth1LineHeight: 22,
|
|
4968
|
-
// token:
|
|
4965
|
+
// token: gnb.belt.item.depth1.typography.lineHeight
|
|
4969
4966
|
depth1FontWeight: 700,
|
|
4970
4967
|
itemFontSize: 12,
|
|
4971
|
-
// token:
|
|
4968
|
+
// token: gnb.belt.item.typography.default.fontSize
|
|
4972
4969
|
itemLineHeight: 20
|
|
4973
|
-
// token:
|
|
4970
|
+
// token: gnb.belt.item.typography.default.lineHeight
|
|
4974
4971
|
}
|
|
4975
4972
|
};
|
|
4976
4973
|
var GNB_COLORS = {
|
|
@@ -4983,7 +4980,9 @@ var GNB_COLORS = {
|
|
|
4983
4980
|
itemColorDefault: light("item-fg-default"),
|
|
4984
4981
|
itemColorHover: light("item-fg-hover"),
|
|
4985
4982
|
itemColorActive: light("item-fg-selected"),
|
|
4986
|
-
itemChevron: light("item-chevron")
|
|
4983
|
+
itemChevron: light("item-chevron"),
|
|
4984
|
+
itemIcon: "grey_65",
|
|
4985
|
+
railFgDefault: light("rail-fg-default")
|
|
4987
4986
|
},
|
|
4988
4987
|
dark: {
|
|
4989
4988
|
panelBg: dark("bg"),
|
|
@@ -4994,19 +4993,41 @@ var GNB_COLORS = {
|
|
|
4994
4993
|
itemColorDefault: dark("item-fg-default"),
|
|
4995
4994
|
itemColorHover: dark("item-fg-hover"),
|
|
4996
4995
|
itemColorActive: dark("item-fg-selected"),
|
|
4997
|
-
itemChevron: dark("item-chevron")
|
|
4996
|
+
itemChevron: dark("item-chevron"),
|
|
4997
|
+
itemIcon: "grey_55",
|
|
4998
|
+
railFgDefault: dark("rail-fg-default")
|
|
4998
4999
|
}
|
|
4999
5000
|
};
|
|
5000
5001
|
var GNB_COMMON = {
|
|
5001
5002
|
/** 접힘 레일 폭. header="full" 은 접으면 메뉴만 사라져 레일이 없으므로 fix 전용. */
|
|
5002
|
-
foldWidth:
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5003
|
+
foldWidth: gnb("fold-width")
|
|
5004
|
+
};
|
|
5005
|
+
var GNB_RAIL = {
|
|
5006
|
+
padding: gnb("rail-padding"),
|
|
5007
|
+
gap: gnb("rail-gap"),
|
|
5008
|
+
itemWidth: gnb("rail-item-width"),
|
|
5009
|
+
itemPaddingTop: gnb("rail-item-paddingTop"),
|
|
5010
|
+
itemPaddingBottom: gnb("rail-item-paddingBottom"),
|
|
5011
|
+
itemRadius: gnb("rail-item-radius"),
|
|
5012
|
+
itemIcon: gnb("rail-item-icon"),
|
|
5013
|
+
// 타이포 (theme.css 에 gnb 타이포 변수 없음 → 토큰 값 직접 명시)
|
|
5014
|
+
itemFontSize: 11,
|
|
5015
|
+
// token: gnb.rail.item.typography.default.fontSize
|
|
5016
|
+
itemLineHeight: 18,
|
|
5017
|
+
// token: gnb.rail.item.typography.default.lineHeight
|
|
5018
|
+
itemFontWeight: 500,
|
|
5019
|
+
// token: gnb.rail.item.typography.default.fontWeight
|
|
5020
|
+
itemFontWeightSelected: 700
|
|
5021
|
+
// token: gnb.rail.item.typography.selected.fontWeight
|
|
5022
|
+
};
|
|
5023
|
+
var GNB_RAIL_WIDTH = 68;
|
|
5024
|
+
var GNB_FOLD_MS = 200;
|
|
5025
|
+
var GNB_EXPAND_MS = 200;
|
|
5009
5026
|
var GNB_MENU_WIDTH = 240;
|
|
5027
|
+
function findGnbRailValue(items, value) {
|
|
5028
|
+
if (items.some((item) => item.value === value)) return value;
|
|
5029
|
+
return findGnbAncestors(items, value)[0];
|
|
5030
|
+
}
|
|
5010
5031
|
function findGnbAncestors(items, value) {
|
|
5011
5032
|
const walk = (list, trail) => {
|
|
5012
5033
|
for (const item of list) {
|
|
@@ -5029,6 +5050,7 @@ function useSLayout() {
|
|
|
5029
5050
|
var SLayout = react.forwardRef(function SLayout2({
|
|
5030
5051
|
type = "box",
|
|
5031
5052
|
header = "fix",
|
|
5053
|
+
useRail = false,
|
|
5032
5054
|
folded,
|
|
5033
5055
|
defaultFolded = false,
|
|
5034
5056
|
onFoldedChange,
|
|
@@ -5047,21 +5069,40 @@ var SLayout = react.forwardRef(function SLayout2({
|
|
|
5047
5069
|
},
|
|
5048
5070
|
[isControlled, onFoldedChange]
|
|
5049
5071
|
);
|
|
5072
|
+
const [gnbState, setGnbState] = react.useState({ forceFull: false, menuVisible: true });
|
|
5073
|
+
const reportGnbState = react.useCallback(
|
|
5074
|
+
(next) => setGnbState(
|
|
5075
|
+
(prev) => prev.forceFull === next.forceFull && prev.menuVisible === next.menuVisible ? prev : next
|
|
5076
|
+
),
|
|
5077
|
+
[]
|
|
5078
|
+
);
|
|
5079
|
+
const effectiveHeader = header === "full" || gnbState.forceFull ? "full" : "fix";
|
|
5050
5080
|
const ctx = react.useMemo(
|
|
5051
|
-
() => ({
|
|
5052
|
-
|
|
5081
|
+
() => ({
|
|
5082
|
+
type,
|
|
5083
|
+
header: effectiveHeader,
|
|
5084
|
+
useRail,
|
|
5085
|
+
folded: currentFolded,
|
|
5086
|
+
setFolded,
|
|
5087
|
+
reportGnbState
|
|
5088
|
+
}),
|
|
5089
|
+
[type, effectiveHeader, useRail, currentFolded, setFolded, reportGnbState]
|
|
5053
5090
|
);
|
|
5054
|
-
const isFullHeader =
|
|
5091
|
+
const isFullHeader = effectiveHeader === "full";
|
|
5092
|
+
const gnbColumnWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (gnbState.menuVisible ? GNB_MENU_WIDTH : 0);
|
|
5055
5093
|
const frameStyle = isFullHeader ? {
|
|
5056
|
-
gridTemplateColumns: `${currentFolded ? 0 :
|
|
5057
|
-
gridTemplateRows: `${GNB_HEADER[
|
|
5094
|
+
gridTemplateColumns: `${currentFolded ? 0 : gnbColumnWidth}px 1fr`,
|
|
5095
|
+
gridTemplateRows: `${GNB_HEADER[effectiveHeader].topHeight} 1fr`,
|
|
5096
|
+
// 메뉴가 미끄러지는 동안 페이지 열도 같은 길이로 따라와야 한 덩어리로 읽힌다.
|
|
5097
|
+
// (fix 는 SGnb 가 제 폭을 직접 전환하므로 프레임이 손댈 게 없다)
|
|
5098
|
+
transition: `grid-template-columns ${GNB_FOLD_MS}ms ease-out`
|
|
5058
5099
|
} : void 0;
|
|
5059
5100
|
return /* @__PURE__ */ jsxRuntime.jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5060
5101
|
"div",
|
|
5061
5102
|
{
|
|
5062
5103
|
ref,
|
|
5063
5104
|
"data-type": type,
|
|
5064
|
-
"data-header":
|
|
5105
|
+
"data-header": effectiveHeader,
|
|
5065
5106
|
className: cn(
|
|
5066
5107
|
"box-border h-full overflow-hidden",
|
|
5067
5108
|
isFullHeader ? "grid" : "flex flex-row",
|
|
@@ -5079,8 +5120,10 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5079
5120
|
header: headerProp,
|
|
5080
5121
|
color = "light",
|
|
5081
5122
|
items,
|
|
5123
|
+
useRail: useRailProp,
|
|
5082
5124
|
value = "",
|
|
5083
5125
|
onValueChange,
|
|
5126
|
+
onRailChange,
|
|
5084
5127
|
folded: foldedProp,
|
|
5085
5128
|
onFoldChange,
|
|
5086
5129
|
onLauncherClick,
|
|
@@ -5092,8 +5135,10 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5092
5135
|
}, ref) {
|
|
5093
5136
|
const layout = react.useContext(SLayoutContext);
|
|
5094
5137
|
const type = typeProp ?? layout?.type ?? "box";
|
|
5095
|
-
const header = headerProp ?? layout?.header ?? "fix";
|
|
5096
5138
|
const folded = foldedProp ?? layout?.folded ?? false;
|
|
5139
|
+
const useRail = useRailProp ?? layout?.useRail ?? false;
|
|
5140
|
+
const railHasLeaf = useRail && items.some((item) => !item.children || item.children.length === 0);
|
|
5141
|
+
const header = railHasLeaf ? "full" : headerProp ?? layout?.header ?? "fix";
|
|
5097
5142
|
const H = GNB_HEADER[header];
|
|
5098
5143
|
const M = GNB_MENU[type];
|
|
5099
5144
|
const C = GNB_COLORS[color];
|
|
@@ -5101,6 +5146,9 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5101
5146
|
() => new Set(findGnbAncestors(items, value))
|
|
5102
5147
|
);
|
|
5103
5148
|
const [hoveredKey, setHoveredKey] = react.useState(null);
|
|
5149
|
+
const [activeRail, setActiveRail] = react.useState(
|
|
5150
|
+
() => findGnbRailValue(items, value) ?? items[0]?.value
|
|
5151
|
+
);
|
|
5104
5152
|
react.useEffect(() => {
|
|
5105
5153
|
const ancestors = findGnbAncestors(items, value);
|
|
5106
5154
|
if (ancestors.length === 0) return;
|
|
@@ -5111,6 +5159,15 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5111
5159
|
return next;
|
|
5112
5160
|
});
|
|
5113
5161
|
}, [items, value]);
|
|
5162
|
+
react.useEffect(() => {
|
|
5163
|
+
const owner = findGnbRailValue(items, value);
|
|
5164
|
+
if (owner !== void 0) setActiveRail(owner);
|
|
5165
|
+
}, [items, value]);
|
|
5166
|
+
react.useEffect(() => {
|
|
5167
|
+
setActiveRail(
|
|
5168
|
+
(prev) => prev !== void 0 && items.some((item) => item.value === prev) ? prev : items[0]?.value
|
|
5169
|
+
);
|
|
5170
|
+
}, [items]);
|
|
5114
5171
|
const toggleExpand = (key) => {
|
|
5115
5172
|
setExpandedKeys((prev) => {
|
|
5116
5173
|
const next = new Set(prev);
|
|
@@ -5127,16 +5184,58 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5127
5184
|
}
|
|
5128
5185
|
onValueChange?.(item.value);
|
|
5129
5186
|
};
|
|
5187
|
+
const handleRailClick = (item) => {
|
|
5188
|
+
if (item.disabled) return;
|
|
5189
|
+
setActiveRail(item.value);
|
|
5190
|
+
onRailChange?.(item.value);
|
|
5191
|
+
if (!item.children || item.children.length === 0) onValueChange?.(item.value);
|
|
5192
|
+
};
|
|
5130
5193
|
const handleFoldToggle = () => {
|
|
5131
5194
|
const next = !folded;
|
|
5132
5195
|
onFoldChange?.(next);
|
|
5133
5196
|
if (foldedProp === void 0) layout?.setFolded(next);
|
|
5134
5197
|
};
|
|
5135
|
-
const
|
|
5198
|
+
const renderRailItem = (item) => {
|
|
5199
|
+
const isActive = item.value === activeRail;
|
|
5200
|
+
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
5201
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5202
|
+
"button",
|
|
5203
|
+
{
|
|
5204
|
+
type: "button",
|
|
5205
|
+
disabled: item.disabled,
|
|
5206
|
+
"aria-current": isActive ? "true" : void 0,
|
|
5207
|
+
className: "box-border flex select-none flex-col items-center border-none bg-transparent transition-colors duration-150",
|
|
5208
|
+
style: {
|
|
5209
|
+
width: GNB_RAIL.itemWidth,
|
|
5210
|
+
paddingTop: GNB_RAIL.itemPaddingTop,
|
|
5211
|
+
paddingBottom: GNB_RAIL.itemPaddingBottom,
|
|
5212
|
+
borderRadius: GNB_RAIL.itemRadius,
|
|
5213
|
+
backgroundColor: isActive ? C.itemBgSelected : isHovered ? C.itemBgHover : C.itemBgDefault,
|
|
5214
|
+
color: isActive ? C.itemColorActive : C.railFgDefault,
|
|
5215
|
+
// <button> 의 UA 폰트를 끊고 앱 폰트를 잇는다 (메뉴 아이템과 같은 이유)
|
|
5216
|
+
fontFamily: "inherit",
|
|
5217
|
+
fontSize: GNB_RAIL.itemFontSize,
|
|
5218
|
+
lineHeight: `${GNB_RAIL.itemLineHeight}px`,
|
|
5219
|
+
fontWeight: isActive ? GNB_RAIL.itemFontWeightSelected : GNB_RAIL.itemFontWeight,
|
|
5220
|
+
opacity: item.disabled ? 0.4 : 1,
|
|
5221
|
+
cursor: item.disabled ? "not-allowed" : "pointer"
|
|
5222
|
+
},
|
|
5223
|
+
onClick: () => handleRailClick(item),
|
|
5224
|
+
onMouseEnter: () => setHoveredKey(item.value),
|
|
5225
|
+
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
5226
|
+
children: [
|
|
5227
|
+
item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: GNB_RAIL.itemIcon, className: "shrink-0" }),
|
|
5228
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap text-center", children: item.label })
|
|
5229
|
+
]
|
|
5230
|
+
}
|
|
5231
|
+
) }, item.value);
|
|
5232
|
+
};
|
|
5233
|
+
const renderItem = (item, depth, branchHasIcon = false) => {
|
|
5136
5234
|
const hasChildren = !!(item.children && item.children.length > 0);
|
|
5137
5235
|
const isSelected = !hasChildren && item.value === value;
|
|
5138
5236
|
const isExpanded = expandedKeys.has(item.value);
|
|
5139
5237
|
const isHovered = hoveredKey === item.value && !item.disabled;
|
|
5238
|
+
const iconBranch = depth === 1 ? !!item.icon : branchHasIcon;
|
|
5140
5239
|
const itemStyle = {
|
|
5141
5240
|
gap: M.itemGap,
|
|
5142
5241
|
paddingTop: M.itemPaddingY,
|
|
@@ -5173,8 +5272,11 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5173
5272
|
onMouseEnter: () => setHoveredKey(item.value),
|
|
5174
5273
|
onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
|
|
5175
5274
|
children: [
|
|
5176
|
-
depth === 1 && item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: M.depth1Icon, className: "shrink-0" }),
|
|
5177
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5275
|
+
depth === 1 && item.icon && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: item.icon, size: M.depth1Icon, color: C.itemIcon, className: "shrink-0" }),
|
|
5276
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap flex flex-no-wrap items-center", children: [
|
|
5277
|
+
depth > 1 && iconBranch ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[20px] h-[24px] flex items-center justify-center mr-[8px]", children: "-" }) : null,
|
|
5278
|
+
item.label
|
|
5279
|
+
] }),
|
|
5178
5280
|
item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5179
5281
|
STag,
|
|
5180
5282
|
{
|
|
@@ -5192,94 +5294,221 @@ var SGnb = react.forwardRef(function SGnb2({
|
|
|
5192
5294
|
size: M.itemArrow,
|
|
5193
5295
|
color: C.itemChevron,
|
|
5194
5296
|
rotate: isExpanded ? 180 : 0,
|
|
5195
|
-
className: "shrink-0 transition-transform duration-
|
|
5297
|
+
className: "shrink-0 transition-transform duration-200"
|
|
5196
5298
|
}
|
|
5197
5299
|
)
|
|
5198
5300
|
]
|
|
5199
5301
|
}
|
|
5200
5302
|
),
|
|
5201
|
-
hasChildren &&
|
|
5303
|
+
hasChildren && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5304
|
+
"div",
|
|
5305
|
+
{
|
|
5306
|
+
className: "grid",
|
|
5307
|
+
style: {
|
|
5308
|
+
gridTemplateRows: isExpanded ? "1fr" : "0fr",
|
|
5309
|
+
transition: `grid-template-rows ${GNB_EXPAND_MS}ms ease-out`
|
|
5310
|
+
},
|
|
5311
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5312
|
+
"ul",
|
|
5313
|
+
{
|
|
5314
|
+
className: "m-0 flex min-h-0 list-none flex-col overflow-hidden p-0",
|
|
5315
|
+
style: {
|
|
5316
|
+
visibility: isExpanded ? "visible" : "hidden",
|
|
5317
|
+
transition: `visibility 0s linear ${isExpanded ? "0s" : `${GNB_EXPAND_MS}ms`}`
|
|
5318
|
+
},
|
|
5319
|
+
children: item.children.map((child) => renderItem(child, depth + 1, iconBranch))
|
|
5320
|
+
}
|
|
5321
|
+
)
|
|
5322
|
+
}
|
|
5323
|
+
)
|
|
5202
5324
|
] }, item.value)
|
|
5203
5325
|
);
|
|
5204
5326
|
};
|
|
5327
|
+
const railItems = useRail ? items : [];
|
|
5328
|
+
const menuItems = useRail ? items.find((item) => item.value === activeRail)?.children ?? [] : items;
|
|
5329
|
+
const showMenu = !useRail || menuItems.length > 0;
|
|
5330
|
+
const bodyWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (showMenu ? GNB_MENU_WIDTH : 0);
|
|
5331
|
+
react.useEffect(() => {
|
|
5332
|
+
layout?.reportGnbState({ forceFull: railHasLeaf, menuVisible: showMenu });
|
|
5333
|
+
}, [layout, railHasLeaf, showMenu]);
|
|
5205
5334
|
const railFolded = folded && header === "fix";
|
|
5206
|
-
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth :
|
|
5335
|
+
const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : bodyWidth;
|
|
5207
5336
|
const rootStyle = {
|
|
5208
5337
|
width: rootWidth,
|
|
5338
|
+
transition: `width ${GNB_FOLD_MS}ms ease-out`,
|
|
5209
5339
|
...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
|
|
5210
5340
|
...style
|
|
5211
5341
|
};
|
|
5342
|
+
const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
|
|
5343
|
+
const foldInline = !!logo;
|
|
5344
|
+
const sideFoldStyle = {
|
|
5345
|
+
opacity: railFolded ? 0 : 1,
|
|
5346
|
+
position: railFolded ? "absolute" : "static",
|
|
5347
|
+
visibility: railFolded ? "hidden" : "visible",
|
|
5348
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}`
|
|
5349
|
+
};
|
|
5212
5350
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5213
5351
|
"div",
|
|
5214
5352
|
{
|
|
5215
5353
|
ref,
|
|
5216
|
-
className: cn("box-border flex h-full shrink-0 flex-col", className),
|
|
5354
|
+
className: cn("box-border flex h-full shrink-0 flex-col overflow-hidden", className),
|
|
5217
5355
|
style: rootStyle,
|
|
5218
5356
|
"data-type": type,
|
|
5219
5357
|
"data-header": header,
|
|
5220
5358
|
"data-color": color,
|
|
5221
5359
|
...rest,
|
|
5222
5360
|
children: [
|
|
5223
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5361
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5224
5362
|
"div",
|
|
5225
5363
|
{
|
|
5226
|
-
className:
|
|
5364
|
+
className: "relative box-border flex shrink-0 items-center overflow-hidden",
|
|
5227
5365
|
style: {
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5366
|
+
height: H.topHeight,
|
|
5367
|
+
// 인라인 간격은 컨테이너 gap 대신 각 요소 margin 으로 준다 — 런처↔폴드(8px)와
|
|
5368
|
+
// 폴드↔로고(top-gap)를 다르게 두기 위함. 접히면 런처·로고가 흐름에서 빠져 margin 도 무의미해진다.
|
|
5369
|
+
padding: `0 ${railFolded ? GNB_RAIL.padding : H.topPaddingX}`,
|
|
5231
5370
|
borderBottom: `1px solid ${C.topBorder}`,
|
|
5232
5371
|
backgroundColor: C.panelBg,
|
|
5233
5372
|
pointerEvents: "auto",
|
|
5373
|
+
transition: `padding ${GNB_FOLD_MS}ms ease-out`,
|
|
5234
5374
|
// 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
|
|
5235
5375
|
color: C.itemColorDefault
|
|
5236
5376
|
},
|
|
5237
|
-
children:
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5377
|
+
children: foldInline ? (
|
|
5378
|
+
// 런처(있을 때)·폴드·로고를 흐름에 나란히 둔다 → 폴드 버튼이 (런처와) 로고 사이에 선다.
|
|
5379
|
+
// 접히면 런처·로고가 페이드하며 흐름에서 빠지고(sideFoldStyle), 폴드 버튼만 남아 레일 가운데에 선다.
|
|
5380
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5381
|
+
onLauncherClick && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5382
|
+
SGhostButton,
|
|
5383
|
+
{
|
|
5384
|
+
icon: "launcher",
|
|
5385
|
+
size: "xs",
|
|
5386
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
5387
|
+
ariaLabel: "app launcher",
|
|
5388
|
+
onClick: onLauncherClick,
|
|
5389
|
+
className: "shrink-0",
|
|
5390
|
+
style: { ...sideFoldStyle, marginRight: "8px" }
|
|
5391
|
+
}
|
|
5392
|
+
),
|
|
5393
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5394
|
+
SGhostButton,
|
|
5395
|
+
{
|
|
5396
|
+
icon: "sidebar",
|
|
5397
|
+
size: "xs",
|
|
5398
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
5399
|
+
ariaLabel: "toggle navigation",
|
|
5400
|
+
ariaPressed: folded,
|
|
5401
|
+
onClick: handleFoldToggle,
|
|
5402
|
+
className: "shrink-0"
|
|
5403
|
+
}
|
|
5404
|
+
),
|
|
5405
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5406
|
+
"div",
|
|
5407
|
+
{
|
|
5408
|
+
className: "flex shrink-0 items-center",
|
|
5409
|
+
style: { height: H.logoHeight, marginLeft: H.topGap, ...sideFoldStyle },
|
|
5410
|
+
children: logo
|
|
5411
|
+
}
|
|
5412
|
+
)
|
|
5413
|
+
] })
|
|
5414
|
+
) : (
|
|
5415
|
+
// 런처·로고는 좌측 그룹(흐름에서 빼 폭 축소 중 폴드 버튼을 밀어내지 않는다),
|
|
5416
|
+
// 폴드 버튼은 우측 끝(ml-auto)이라 폭·패딩이 줄면 저절로 레일 가운데에 선다.
|
|
5417
|
+
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5418
|
+
(onLauncherClick || logo) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5419
|
+
"div",
|
|
5420
|
+
{
|
|
5421
|
+
className: "absolute inset-y-0 flex items-center",
|
|
5422
|
+
style: {
|
|
5423
|
+
left: H.topPaddingX,
|
|
5424
|
+
gap: H.topGap,
|
|
5425
|
+
opacity: railFolded ? 0 : 1,
|
|
5426
|
+
transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
|
|
5427
|
+
// 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
|
|
5428
|
+
transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
|
|
5429
|
+
visibility: railFolded ? "hidden" : "visible"
|
|
5430
|
+
},
|
|
5431
|
+
children: [
|
|
5432
|
+
onLauncherClick && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5433
|
+
SGhostButton,
|
|
5434
|
+
{
|
|
5435
|
+
icon: "launcher",
|
|
5436
|
+
size: "xs",
|
|
5437
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
5438
|
+
ariaLabel: "app launcher",
|
|
5439
|
+
onClick: onLauncherClick,
|
|
5440
|
+
className: "shrink-0"
|
|
5441
|
+
}
|
|
5442
|
+
),
|
|
5443
|
+
logo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
|
|
5444
|
+
]
|
|
5445
|
+
}
|
|
5446
|
+
),
|
|
5447
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5448
|
+
SGhostButton,
|
|
5449
|
+
{
|
|
5450
|
+
icon: "sidebar",
|
|
5451
|
+
size: "xs",
|
|
5452
|
+
intent: color === "dark" ? "inverse" : "default",
|
|
5453
|
+
ariaLabel: "toggle navigation",
|
|
5454
|
+
ariaPressed: folded,
|
|
5455
|
+
onClick: handleFoldToggle,
|
|
5456
|
+
className: "ml-auto shrink-0"
|
|
5457
|
+
}
|
|
5458
|
+
)
|
|
5459
|
+
] })
|
|
5460
|
+
)
|
|
5263
5461
|
}
|
|
5264
5462
|
),
|
|
5265
|
-
|
|
5266
|
-
"
|
|
5463
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5464
|
+
"div",
|
|
5267
5465
|
{
|
|
5268
|
-
"
|
|
5269
|
-
className: "flex flex-col overflow-y-auto",
|
|
5466
|
+
className: "flex flex-row",
|
|
5270
5467
|
style: {
|
|
5271
|
-
width:
|
|
5468
|
+
width: bodyWidth,
|
|
5272
5469
|
alignSelf: "flex-start",
|
|
5273
5470
|
flexGrow: 1,
|
|
5274
5471
|
flexShrink: 1,
|
|
5275
5472
|
flexBasis: "auto",
|
|
5276
5473
|
minHeight: 0,
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
pointerEvents: "auto"
|
|
5474
|
+
transform: folded ? "translateX(-100%)" : "none",
|
|
5475
|
+
visibility: folded ? "hidden" : "visible",
|
|
5476
|
+
transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
|
|
5281
5477
|
},
|
|
5282
|
-
children:
|
|
5478
|
+
children: [
|
|
5479
|
+
useRail && // 레일도 내비게이션 landmark — nav 로 세워야 aria-label(레일)이 스크린리더에 노출된다
|
|
5480
|
+
// (role 없는 ul 의 aria-label 은 영역명으로 읽히지 않는다). 메뉴 nav 와는 레이블로 구분된다.
|
|
5481
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5482
|
+
"nav",
|
|
5483
|
+
{
|
|
5484
|
+
"aria-label": `${ariaLabel} rail`,
|
|
5485
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
5486
|
+
style: {
|
|
5487
|
+
width: GNB_RAIL_WIDTH,
|
|
5488
|
+
padding: GNB_RAIL.padding,
|
|
5489
|
+
borderRight: `1px solid ${C.topBorder}`,
|
|
5490
|
+
backgroundColor: C.panelBg,
|
|
5491
|
+
pointerEvents: "auto"
|
|
5492
|
+
},
|
|
5493
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col p-0", style: { gap: GNB_RAIL.gap }, children: railItems.map(renderRailItem) })
|
|
5494
|
+
}
|
|
5495
|
+
),
|
|
5496
|
+
showMenu && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5497
|
+
"nav",
|
|
5498
|
+
{
|
|
5499
|
+
"aria-label": ariaLabel,
|
|
5500
|
+
className: "flex shrink-0 flex-col overflow-y-auto",
|
|
5501
|
+
style: {
|
|
5502
|
+
width: GNB_MENU_WIDTH,
|
|
5503
|
+
paddingTop: M.bodyPaddingY,
|
|
5504
|
+
paddingBottom: M.bodyPaddingY,
|
|
5505
|
+
backgroundColor: C.panelBg,
|
|
5506
|
+
pointerEvents: "auto"
|
|
5507
|
+
},
|
|
5508
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: menuItems.map((item) => renderItem(item, 1)) })
|
|
5509
|
+
}
|
|
5510
|
+
)
|
|
5511
|
+
]
|
|
5283
5512
|
}
|
|
5284
5513
|
)
|
|
5285
5514
|
]
|
|
@@ -5293,7 +5522,7 @@ var SPage = react.forwardRef(function SPage2({ className, style, children, ...re
|
|
|
5293
5522
|
"main",
|
|
5294
5523
|
{
|
|
5295
5524
|
ref,
|
|
5296
|
-
className: cn("box-border min-h-0 min-w-0 flex-1 overflow-auto p-
|
|
5525
|
+
className: cn("box-border min-h-0 min-w-0 flex-1 overflow-auto p-20", className),
|
|
5297
5526
|
style: { ...gridStyle, ...style },
|
|
5298
5527
|
...rest,
|
|
5299
5528
|
children
|
|
@@ -5335,6 +5564,7 @@ function STabs({
|
|
|
5335
5564
|
}) {
|
|
5336
5565
|
const s = TAB_MAIN_SIZE[size];
|
|
5337
5566
|
const blue = TAB_MAIN_COLORS.selected.border;
|
|
5567
|
+
const isVertical = isSub && vertical;
|
|
5338
5568
|
const tabRefs = react.useRef([]);
|
|
5339
5569
|
const moveTo = (target) => {
|
|
5340
5570
|
if (target < 0 || target >= tabs.length) return;
|
|
@@ -5342,19 +5572,17 @@ function STabs({
|
|
|
5342
5572
|
tabRefs.current[target]?.focus();
|
|
5343
5573
|
};
|
|
5344
5574
|
const containerStyle = { ...style };
|
|
5345
|
-
if (!isSub) {
|
|
5346
|
-
if (vertical) containerStyle.borderRight = `1px solid ${blue}`;
|
|
5347
|
-
else containerStyle.borderBottom = `1px solid ${blue}`;
|
|
5348
|
-
}
|
|
5575
|
+
if (!isSub) containerStyle.borderBottom = `1px solid ${blue}`;
|
|
5349
5576
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5350
5577
|
"div",
|
|
5351
5578
|
{
|
|
5352
5579
|
role: "tablist",
|
|
5353
|
-
"aria-orientation":
|
|
5580
|
+
"aria-orientation": isVertical ? "vertical" : "horizontal",
|
|
5354
5581
|
className: cn(
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5582
|
+
// main(가로): baseline(하단 divider)이 100% 너비로 채워지도록 w-full
|
|
5583
|
+
isSub ? "inline-flex w-fit" : "flex w-full",
|
|
5584
|
+
isVertical ? "flex-col items-stretch" : "flex-row",
|
|
5585
|
+
isSub ? isVertical ? "gap-[16px]" : "gap-[24px]" : "gap-[4px]",
|
|
5358
5586
|
className
|
|
5359
5587
|
),
|
|
5360
5588
|
style: containerStyle,
|
|
@@ -5383,22 +5611,12 @@ function STabs({
|
|
|
5383
5611
|
color: selected ? TAB_MAIN_COLORS.selected.text : TAB_MAIN_COLORS.default.text,
|
|
5384
5612
|
border: `1px solid ${border}`,
|
|
5385
5613
|
// 폴더형: 미선택 탭은 baseline 위에, 선택 탭은 baseline(파란선)을 1px 덮어 연결된 것처럼 보이게
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
borderRight: selected ? `1px solid ${selectedBg}` : "none",
|
|
5393
|
-
marginRight: selected ? -1 : 0
|
|
5394
|
-
} : {
|
|
5395
|
-
borderTopLeftRadius: 6,
|
|
5396
|
-
borderTopRightRadius: 6,
|
|
5397
|
-
borderBottomLeftRadius: 0,
|
|
5398
|
-
borderBottomRightRadius: 0,
|
|
5399
|
-
borderBottom: selected ? `1px solid ${selectedBg}` : "none",
|
|
5400
|
-
marginBottom: selected ? -1 : 0
|
|
5401
|
-
}
|
|
5614
|
+
borderTopLeftRadius: 6,
|
|
5615
|
+
borderTopRightRadius: 6,
|
|
5616
|
+
borderBottomLeftRadius: 0,
|
|
5617
|
+
borderBottomRightRadius: 0,
|
|
5618
|
+
borderBottom: selected ? `1px solid ${selectedBg}` : "none",
|
|
5619
|
+
marginBottom: selected ? -1 : 0
|
|
5402
5620
|
};
|
|
5403
5621
|
}
|
|
5404
5622
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -5418,8 +5636,8 @@ function STabs({
|
|
|
5418
5636
|
select();
|
|
5419
5637
|
return;
|
|
5420
5638
|
}
|
|
5421
|
-
const nextKey =
|
|
5422
|
-
const prevKey =
|
|
5639
|
+
const nextKey = isVertical ? "ArrowDown" : "ArrowRight";
|
|
5640
|
+
const prevKey = isVertical ? "ArrowUp" : "ArrowLeft";
|
|
5423
5641
|
if (e.key === nextKey) {
|
|
5424
5642
|
e.preventDefault();
|
|
5425
5643
|
moveTo((i + 1) % tabs.length);
|
|
@@ -7940,25 +8158,45 @@ function SDatePicker({
|
|
|
7940
8158
|
}
|
|
7941
8159
|
);
|
|
7942
8160
|
}
|
|
8161
|
+
function splitDateTime(v) {
|
|
8162
|
+
if (!v) return { date: null, hour: "00", minute: "00" };
|
|
8163
|
+
const [date = "", time = ""] = v.split(" ");
|
|
8164
|
+
const [hour = "00", minute = "00"] = time.split(":");
|
|
8165
|
+
return { date: date || null, hour, minute };
|
|
8166
|
+
}
|
|
7943
8167
|
function RangeCalendar({
|
|
7944
8168
|
value,
|
|
7945
8169
|
selectable,
|
|
7946
8170
|
maxRange,
|
|
8171
|
+
useTimePicker = false,
|
|
7947
8172
|
onSelect,
|
|
7948
8173
|
onPendingStartChange,
|
|
7949
8174
|
onViewChange
|
|
7950
8175
|
}) {
|
|
7951
8176
|
const today = todayStr();
|
|
7952
|
-
const
|
|
8177
|
+
const initial = splitDateTime(value?.[0]);
|
|
8178
|
+
const base = initial.date || today;
|
|
7953
8179
|
const [y, setY] = react.useState(Number(base.split("-")[0]));
|
|
7954
8180
|
const [m, setM] = react.useState(Number(base.split("-")[1]));
|
|
7955
|
-
const [start, setStart] = react.useState(
|
|
7956
|
-
const [end, setEnd] = react.useState(value?.[1]
|
|
8181
|
+
const [start, setStart] = react.useState(initial.date);
|
|
8182
|
+
const [end, setEnd] = react.useState(splitDateTime(value?.[1]).date);
|
|
7957
8183
|
const [hover, setHover] = react.useState(null);
|
|
8184
|
+
const [sH, setSH] = react.useState(initial.hour);
|
|
8185
|
+
const [sM, setSM] = react.useState(initial.minute);
|
|
8186
|
+
const [eH, setEH] = react.useState(splitDateTime(value?.[1]).hour);
|
|
8187
|
+
const [eM, setEM] = react.useState(splitDateTime(value?.[1]).minute);
|
|
8188
|
+
const lastEmitted = react.useRef("");
|
|
7958
8189
|
react.useEffect(() => {
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
8190
|
+
if (useTimePicker && JSON.stringify(value) === lastEmitted.current) return;
|
|
8191
|
+
const s = splitDateTime(value?.[0]);
|
|
8192
|
+
const e = splitDateTime(value?.[1]);
|
|
8193
|
+
setStart(s.date);
|
|
8194
|
+
setEnd(e.date);
|
|
8195
|
+
setSH(s.hour);
|
|
8196
|
+
setSM(s.minute);
|
|
8197
|
+
setEH(e.hour);
|
|
8198
|
+
setEM(e.minute);
|
|
8199
|
+
}, [value, useTimePicker]);
|
|
7962
8200
|
const setBase = (ny, nm) => {
|
|
7963
8201
|
setY(ny);
|
|
7964
8202
|
setM(nm);
|
|
@@ -8002,18 +8240,53 @@ function RangeCalendar({
|
|
|
8002
8240
|
const hasRange = !!(rStart && rEnd && rStart !== rEnd);
|
|
8003
8241
|
const inRange = (d) => !!(rStart && rEnd && d > rStart && d < rEnd);
|
|
8004
8242
|
const RANGE_BG = "var(--cmp-datepicker-calendar-range-bg)";
|
|
8243
|
+
const pad = (v) => String(Number(v) || 0).padStart(2, "0");
|
|
8244
|
+
const emit2 = (dateStart, dateEnd, time) => {
|
|
8245
|
+
const t = time ?? { sH, sM, eH, eM };
|
|
8246
|
+
const range = useTimePicker ? [`${dateStart} ${pad(t.sH)}:${pad(t.sM)}`, `${dateEnd} ${pad(t.eH)}:${pad(t.eM)}`] : [dateStart, dateEnd];
|
|
8247
|
+
lastEmitted.current = JSON.stringify(range);
|
|
8248
|
+
onSelect(range);
|
|
8249
|
+
};
|
|
8005
8250
|
const click = (d) => {
|
|
8006
8251
|
if (isDisabled(d)) return;
|
|
8007
8252
|
if (!start || complete || d < start) {
|
|
8008
8253
|
setStart(d);
|
|
8009
8254
|
setEnd(null);
|
|
8010
8255
|
setHover(null);
|
|
8256
|
+
if (useTimePicker) {
|
|
8257
|
+
setSH("00");
|
|
8258
|
+
setSM("00");
|
|
8259
|
+
setEH("23");
|
|
8260
|
+
setEM("59");
|
|
8261
|
+
}
|
|
8011
8262
|
onPendingStartChange?.(d);
|
|
8012
8263
|
return;
|
|
8013
8264
|
}
|
|
8014
8265
|
setEnd(d);
|
|
8015
8266
|
onPendingStartChange?.(null);
|
|
8016
|
-
|
|
8267
|
+
emit2(start, d, useTimePicker ? { sH, sM, eH: "23", eM: "59" } : void 0);
|
|
8268
|
+
};
|
|
8269
|
+
const changeTime = (which, part, raw) => {
|
|
8270
|
+
const digits = raw.replace(/\D/g, "").slice(0, 2);
|
|
8271
|
+
const max = part === "h" ? 23 : 59;
|
|
8272
|
+
const next = digits !== "" && Number(digits) > max ? String(max) : digits;
|
|
8273
|
+
const nextTime = { sH, sM, eH, eM };
|
|
8274
|
+
if (which === "start") nextTime[part === "h" ? "sH" : "sM"] = next;
|
|
8275
|
+
else nextTime[part === "h" ? "eH" : "eM"] = next;
|
|
8276
|
+
setSH(nextTime.sH);
|
|
8277
|
+
setSM(nextTime.sM);
|
|
8278
|
+
setEH(nextTime.eH);
|
|
8279
|
+
setEM(nextTime.eM);
|
|
8280
|
+
if (start && end) emit2(start, end, nextTime);
|
|
8281
|
+
};
|
|
8282
|
+
const blurTime = (which, part) => {
|
|
8283
|
+
if (which === "start") {
|
|
8284
|
+
if (part === "h") setSH(pad(sH));
|
|
8285
|
+
else setSM(pad(sM));
|
|
8286
|
+
} else {
|
|
8287
|
+
if (part === "h") setEH(pad(eH));
|
|
8288
|
+
else setEM(pad(eM));
|
|
8289
|
+
}
|
|
8017
8290
|
};
|
|
8018
8291
|
const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
|
|
8019
8292
|
const renderPanel = (gy, gm, isLeft) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
|
|
@@ -8088,35 +8361,120 @@ function RangeCalendar({
|
|
|
8088
8361
|
);
|
|
8089
8362
|
}) })
|
|
8090
8363
|
] });
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8364
|
+
const renderTimeSection = (which, date, suffix) => {
|
|
8365
|
+
const active = !!date;
|
|
8366
|
+
const [yy = "", mm = "", dd = ""] = date ? date.split("-") : [];
|
|
8367
|
+
const hour = which === "start" ? sH : eH;
|
|
8368
|
+
const minute = which === "start" ? sM : eM;
|
|
8369
|
+
const num = (n, strip) => active && n !== "" ? strip ? String(Number(n)) : n : "-";
|
|
8370
|
+
const INPUT_CLS = "h-[var(--cmp-datepicker-sm-height)] w-[40px] rounded-[var(--cmp-datepicker-sm-radius)] border border-solid border-[color:var(--cmp-datepicker-border-default)] bg-[var(--cmp-datepicker-bg-default)] text-center text-[12px] font-medium leading-[20px] text-[color:var(--cmp-datepicker-text-default)] outline-none placeholder:text-[color:var(--cmp-datepicker-text-disabled)] focus:border-[color:var(--cmp-datepicker-border-focus)] disabled:cursor-not-allowed";
|
|
8371
|
+
const LABEL = "text-[color:var(--cmp-datepicker-calendar-time-date-label)]";
|
|
8372
|
+
const UNIT = "text-[color:var(--cmp-datepicker-calendar-time-date-unit)]";
|
|
8373
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8374
|
+
"div",
|
|
8375
|
+
{
|
|
8376
|
+
className: cn(
|
|
8377
|
+
"flex flex-1 items-center justify-center gap-[var(--cmp-datepicker-calendar-time-section-gap)] rounded-[var(--cmp-datepicker-calendar-time-radius)] px-[var(--cmp-datepicker-calendar-time-paddingX)] py-[var(--cmp-datepicker-calendar-time-paddingY)] text-[12px] font-medium leading-[20px]",
|
|
8378
|
+
active ? "bg-[var(--cmp-datepicker-calendar-time-bg-selected)]" : "bg-[var(--cmp-datepicker-calendar-time-bg-default)]"
|
|
8379
|
+
),
|
|
8380
|
+
children: [
|
|
8381
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-[var(--cmp-datepicker-calendar-time-date-gap)]", children: [
|
|
8382
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: LABEL, children: num(yy, false) }),
|
|
8383
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: UNIT, children: "\uB144" }),
|
|
8384
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: LABEL, children: num(mm, true) }),
|
|
8385
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: UNIT, children: "\uC6D4" }),
|
|
8386
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: LABEL, children: num(dd, true) }),
|
|
8387
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: UNIT, children: "\uC77C" })
|
|
8388
|
+
] }),
|
|
8389
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-[var(--cmp-datepicker-calendar-time-unit-gap)]", children: [
|
|
8390
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8391
|
+
"input",
|
|
8392
|
+
{
|
|
8393
|
+
type: "text",
|
|
8394
|
+
inputMode: "numeric",
|
|
8395
|
+
maxLength: 2,
|
|
8396
|
+
"aria-label": `${suffix} \uC2DC`,
|
|
8397
|
+
disabled: !active,
|
|
8398
|
+
value: active ? hour : "",
|
|
8399
|
+
placeholder: "--",
|
|
8400
|
+
onChange: (e) => changeTime(which, "h", e.target.value),
|
|
8401
|
+
onBlur: () => blurTime(which, "h"),
|
|
8402
|
+
className: INPUT_CLS
|
|
8403
|
+
}
|
|
8404
|
+
),
|
|
8405
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: LABEL, children: ":" }),
|
|
8406
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8407
|
+
"input",
|
|
8408
|
+
{
|
|
8409
|
+
type: "text",
|
|
8410
|
+
inputMode: "numeric",
|
|
8411
|
+
maxLength: 2,
|
|
8412
|
+
"aria-label": `${suffix} \uBD84`,
|
|
8413
|
+
disabled: !active,
|
|
8414
|
+
value: active ? minute : "",
|
|
8415
|
+
placeholder: "--",
|
|
8416
|
+
onChange: (e) => changeTime(which, "m", e.target.value),
|
|
8417
|
+
onBlur: () => blurTime(which, "m"),
|
|
8418
|
+
className: INPUT_CLS
|
|
8419
|
+
}
|
|
8420
|
+
)
|
|
8421
|
+
] }),
|
|
8422
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: UNIT, children: suffix })
|
|
8423
|
+
]
|
|
8424
|
+
}
|
|
8425
|
+
);
|
|
8426
|
+
};
|
|
8427
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8428
|
+
"div",
|
|
8429
|
+
{
|
|
8430
|
+
className: cn(
|
|
8431
|
+
"inline-flex flex-col gap-[var(--cmp-datepicker-calendar-gap)] rounded-[var(--cmp-datepicker-calendar-radius)] bg-[var(--cmp-datepicker-calendar-bg)] p-[var(--cmp-datepicker-calendar-paddingXY)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.2)] select-none",
|
|
8432
|
+
useTimePicker ? "h-auto" : "h-[364px]"
|
|
8116
8433
|
),
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8434
|
+
children: [
|
|
8435
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
8436
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-[var(--cmp-datepicker-calendar-gap)] text-[14px] font-medium leading-[24px]", children: [
|
|
8437
|
+
/* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "chevronLeft", size: "xxs", ariaLabel: "prevYear", onClick: goPrevYear }),
|
|
8438
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex min-w-[40px] items-center justify-center text-center", children: y }),
|
|
8439
|
+
/* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { icon: "chevronRight", size: "xxs", ariaLabel: "nextYear", onClick: goNextYear })
|
|
8440
|
+
] }),
|
|
8441
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8442
|
+
"button",
|
|
8443
|
+
{
|
|
8444
|
+
type: "button",
|
|
8445
|
+
onClick: goToday,
|
|
8446
|
+
className: "absolute right-0 h-[24px] min-w-[37px] cursor-pointer bg-transparent text-center text-[12px] font-medium leading-[24px] text-[color:var(--cmp-datepicker-calendar-day-default-text)] hover:underline",
|
|
8447
|
+
children: "\uC624\uB298"
|
|
8448
|
+
}
|
|
8449
|
+
)
|
|
8450
|
+
] }),
|
|
8451
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-range-panelGap)]", children: [
|
|
8452
|
+
renderPanel(y, m, true),
|
|
8453
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8454
|
+
"span",
|
|
8455
|
+
{
|
|
8456
|
+
className: "w-px shrink-0 self-stretch bg-[var(--cmp-datepicker-calendar-range-divider)]",
|
|
8457
|
+
"aria-hidden": true
|
|
8458
|
+
}
|
|
8459
|
+
),
|
|
8460
|
+
renderPanel(nextMonthYM.year, nextMonthYM.month, false)
|
|
8461
|
+
] }),
|
|
8462
|
+
useTimePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
8463
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8464
|
+
"span",
|
|
8465
|
+
{
|
|
8466
|
+
className: "h-px w-full bg-[var(--cmp-datepicker-calendar-range-divider)]",
|
|
8467
|
+
"aria-hidden": true
|
|
8468
|
+
}
|
|
8469
|
+
),
|
|
8470
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-time-gap)]", children: [
|
|
8471
|
+
renderTimeSection("start", start, "\uBD80\uD130"),
|
|
8472
|
+
renderTimeSection("end", end, "\uAE4C\uC9C0")
|
|
8473
|
+
] })
|
|
8474
|
+
] })
|
|
8475
|
+
]
|
|
8476
|
+
}
|
|
8477
|
+
);
|
|
8120
8478
|
}
|
|
8121
8479
|
function SDateRangePicker({
|
|
8122
8480
|
value,
|
|
@@ -8126,6 +8484,7 @@ function SDateRangePicker({
|
|
|
8126
8484
|
placeholder = "YYYY-MM-DD ~ YYYY-MM-DD",
|
|
8127
8485
|
selectable,
|
|
8128
8486
|
maxRange,
|
|
8487
|
+
useTimePicker = false,
|
|
8129
8488
|
disabled = false,
|
|
8130
8489
|
width,
|
|
8131
8490
|
name,
|
|
@@ -8169,8 +8528,9 @@ function SDateRangePicker({
|
|
|
8169
8528
|
setPendingStart(null);
|
|
8170
8529
|
onValueChange?.(r);
|
|
8171
8530
|
if (rules && rules.length > 0) setRuleError(runRules(r, rules));
|
|
8172
|
-
setOpen(false);
|
|
8531
|
+
if (!useTimePicker) setOpen(false);
|
|
8173
8532
|
};
|
|
8533
|
+
const resolvedPlaceholder = useTimePicker && placeholder === "YYYY-MM-DD ~ YYYY-MM-DD" ? "YYYY-MM-DD HH:mm ~ YYYY-MM-DD HH:mm" : placeholder;
|
|
8174
8534
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
8175
8535
|
SField,
|
|
8176
8536
|
{
|
|
@@ -8237,7 +8597,7 @@ function SDateRangePicker({
|
|
|
8237
8597
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-right", children: startText }),
|
|
8238
8598
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-[4px]", children: "~" }),
|
|
8239
8599
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
|
|
8240
|
-
] }) :
|
|
8600
|
+
] }) : resolvedPlaceholder
|
|
8241
8601
|
}
|
|
8242
8602
|
)
|
|
8243
8603
|
]
|
|
@@ -8256,6 +8616,7 @@ function SDateRangePicker({
|
|
|
8256
8616
|
value: value ?? null,
|
|
8257
8617
|
selectable,
|
|
8258
8618
|
maxRange,
|
|
8619
|
+
useTimePicker,
|
|
8259
8620
|
onSelect: select,
|
|
8260
8621
|
onPendingStartChange: setPendingStart,
|
|
8261
8622
|
onViewChange
|
|
@@ -10290,7 +10651,9 @@ function RangeTimeSelector({
|
|
|
10290
10651
|
};
|
|
10291
10652
|
const commit = (side, hour, minute, direction = "unknown") => {
|
|
10292
10653
|
const next = formatTimeValue(hour, minute);
|
|
10293
|
-
onChange(
|
|
10654
|
+
onChange(
|
|
10655
|
+
normalizeNextRange(side === "start" ? [next, range[1]] : [range[0], next], side, direction)
|
|
10656
|
+
);
|
|
10294
10657
|
};
|
|
10295
10658
|
const getTimeMinutes2 = (time) => {
|
|
10296
10659
|
const { hour, minute } = parseTimeValue(time);
|
|
@@ -10688,9 +11051,12 @@ exports.BUTTON_SIZES = BUTTON_SIZES;
|
|
|
10688
11051
|
exports.COLOR_KEYS = COLOR_KEYS;
|
|
10689
11052
|
exports.GNB_COLORS = GNB_COLORS;
|
|
10690
11053
|
exports.GNB_COMMON = GNB_COMMON;
|
|
11054
|
+
exports.GNB_FOLD_MS = GNB_FOLD_MS;
|
|
10691
11055
|
exports.GNB_HEADER = GNB_HEADER;
|
|
10692
11056
|
exports.GNB_MENU = GNB_MENU;
|
|
10693
11057
|
exports.GNB_MENU_WIDTH = GNB_MENU_WIDTH;
|
|
11058
|
+
exports.GNB_RAIL = GNB_RAIL;
|
|
11059
|
+
exports.GNB_RAIL_WIDTH = GNB_RAIL_WIDTH;
|
|
10694
11060
|
exports.ICONS = ICONS;
|
|
10695
11061
|
exports.SActionModal = SActionModal;
|
|
10696
11062
|
exports.SBadge = SBadge;
|
|
@@ -10753,6 +11119,7 @@ exports.TIMEPICKER_SIZE_CONFIG = TIMEPICKER_SIZE_CONFIG;
|
|
|
10753
11119
|
exports.buttonPreset = buttonPreset;
|
|
10754
11120
|
exports.cn = cn;
|
|
10755
11121
|
exports.findGnbAncestors = findGnbAncestors;
|
|
11122
|
+
exports.findGnbRailValue = findGnbRailValue;
|
|
10756
11123
|
exports.isColorKey = isColorKey;
|
|
10757
11124
|
exports.loading = loading;
|
|
10758
11125
|
exports.resolveColor = resolveColor;
|