sellmate-design-system-react 0.5.0 → 0.6.1

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.
Files changed (38) hide show
  1. package/README.md +20 -0
  2. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +180 -37
  3. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  4. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +6 -1
  5. package/dist/components/SDateRangePicker/SDateRangePicker.js +180 -37
  6. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  7. package/dist/components/SGnb/SGnb.cjs +145 -57
  8. package/dist/components/SGnb/SGnb.cjs.map +1 -1
  9. package/dist/components/SGnb/SGnb.d.ts +7 -1
  10. package/dist/components/SGnb/SGnb.js +146 -58
  11. package/dist/components/SGnb/SGnb.js.map +1 -1
  12. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +180 -37
  13. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  14. package/dist/components/SKeyValueTable/SKeyValueTable.js +180 -37
  15. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  16. package/dist/components/SLayout/SLayout.cjs +21 -6
  17. package/dist/components/SLayout/SLayout.cjs.map +1 -1
  18. package/dist/components/SLayout/SLayout.d.ts +17 -2
  19. package/dist/components/SLayout/SLayout.js +21 -6
  20. package/dist/components/SLayout/SLayout.js.map +1 -1
  21. package/dist/components/SPage/SPage.cjs +21 -6
  22. package/dist/components/SPage/SPage.cjs.map +1 -1
  23. package/dist/components/SPage/SPage.js +21 -6
  24. package/dist/components/SPage/SPage.js.map +1 -1
  25. package/dist/components/SStepper/SStepper.cjs +3019 -0
  26. package/dist/components/SStepper/SStepper.cjs.map +1 -0
  27. package/dist/components/SStepper/SStepper.d.ts +16 -0
  28. package/dist/components/SStepper/SStepper.js +3017 -0
  29. package/dist/components/SStepper/SStepper.js.map +1 -0
  30. package/dist/components/SStepper/index.d.ts +2 -0
  31. package/dist/components/SStepper/stepper.config.d.ts +42 -0
  32. package/dist/index.cjs +500 -104
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +499 -105
  36. package/dist/index.js.map +1 -1
  37. package/dist/styles.css +61 -0
  38. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -5041,15 +5041,30 @@ var SLayout = forwardRef(function SLayout2({
5041
5041
  },
5042
5042
  [isControlled, onFoldedChange]
5043
5043
  );
5044
+ const [gnbState, setGnbState] = useState({ forceFull: false, menuVisible: true });
5045
+ const reportGnbState = useCallback(
5046
+ (next) => setGnbState(
5047
+ (prev) => prev.forceFull === next.forceFull && prev.menuVisible === next.menuVisible ? prev : next
5048
+ ),
5049
+ []
5050
+ );
5051
+ const effectiveHeader = header === "full" || gnbState.forceFull ? "full" : "fix";
5044
5052
  const ctx = useMemo(
5045
- () => ({ type, header, useRail, folded: currentFolded, setFolded }),
5046
- [type, header, useRail, currentFolded, setFolded]
5053
+ () => ({
5054
+ type,
5055
+ header: effectiveHeader,
5056
+ useRail,
5057
+ folded: currentFolded,
5058
+ setFolded,
5059
+ reportGnbState
5060
+ }),
5061
+ [type, effectiveHeader, useRail, currentFolded, setFolded, reportGnbState]
5047
5062
  );
5048
- const isFullHeader = header === "full";
5049
- const gnbColumnWidth = useRail ? GNB_RAIL_WIDTH + GNB_MENU_WIDTH : GNB_MENU_WIDTH;
5063
+ const isFullHeader = effectiveHeader === "full";
5064
+ const gnbColumnWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (gnbState.menuVisible ? GNB_MENU_WIDTH : 0);
5050
5065
  const frameStyle = isFullHeader ? {
5051
5066
  gridTemplateColumns: `${currentFolded ? 0 : gnbColumnWidth}px 1fr`,
5052
- gridTemplateRows: `${GNB_HEADER[header].topHeight} 1fr`,
5067
+ gridTemplateRows: `${GNB_HEADER[effectiveHeader].topHeight} 1fr`,
5053
5068
  // 메뉴가 미끄러지는 동안 페이지 열도 같은 길이로 따라와야 한 덩어리로 읽힌다.
5054
5069
  // (fix 는 SGnb 가 제 폭을 직접 전환하므로 프레임이 손댈 게 없다)
5055
5070
  transition: `grid-template-columns ${GNB_FOLD_MS}ms ease-out`
@@ -5059,7 +5074,7 @@ var SLayout = forwardRef(function SLayout2({
5059
5074
  {
5060
5075
  ref,
5061
5076
  "data-type": type,
5062
- "data-header": header,
5077
+ "data-header": effectiveHeader,
5063
5078
  className: cn(
5064
5079
  "box-border h-full overflow-hidden",
5065
5080
  isFullHeader ? "grid" : "flex flex-row",
@@ -5092,9 +5107,10 @@ var SGnb = forwardRef(function SGnb2({
5092
5107
  }, ref) {
5093
5108
  const layout = useContext(SLayoutContext);
5094
5109
  const type = typeProp ?? layout?.type ?? "box";
5095
- const header = headerProp ?? layout?.header ?? "fix";
5096
5110
  const folded = foldedProp ?? layout?.folded ?? false;
5097
5111
  const useRail = useRailProp ?? layout?.useRail ?? false;
5112
+ const railHasLeaf = useRail && items.some((item) => !item.children || item.children.length === 0);
5113
+ const header = railHasLeaf ? "full" : headerProp ?? layout?.header ?? "fix";
5098
5114
  const H = GNB_HEADER[header];
5099
5115
  const M = GNB_MENU[type];
5100
5116
  const C = GNB_COLORS[color];
@@ -5228,9 +5244,24 @@ var SGnb = forwardRef(function SGnb2({
5228
5244
  onMouseEnter: () => setHoveredKey(item.value),
5229
5245
  onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
5230
5246
  children: [
5231
- depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, color: C.itemIcon, className: "shrink-0" }),
5247
+ depth === 1 && item.icon && /* @__PURE__ */ jsx(
5248
+ SIcon,
5249
+ {
5250
+ name: item.icon,
5251
+ size: M.depth1Icon,
5252
+ color: isSelected ? C.itemColorActive : C.itemIcon,
5253
+ className: "shrink-0"
5254
+ }
5255
+ ),
5232
5256
  /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap flex flex-no-wrap items-center", children: [
5233
- depth > 1 && iconBranch ? /* @__PURE__ */ jsx("div", { className: "w-[20px] h-[24px] flex items-center justify-center mr-[8px]", children: "-" }) : null,
5257
+ depth > 1 && iconBranch ? /* @__PURE__ */ jsx(
5258
+ "div",
5259
+ {
5260
+ className: "w-[20px] flex items-center justify-center mr-[8px]",
5261
+ style: { height: M.itemLineHeight },
5262
+ children: "-"
5263
+ }
5264
+ ) : null,
5234
5265
  item.label
5235
5266
  ] }),
5236
5267
  item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsx(
@@ -5282,16 +5313,28 @@ var SGnb = forwardRef(function SGnb2({
5282
5313
  };
5283
5314
  const railItems = useRail ? items : [];
5284
5315
  const menuItems = useRail ? items.find((item) => item.value === activeRail)?.children ?? [] : items;
5285
- const bodyWidth = useRail ? GNB_RAIL_WIDTH + GNB_MENU_WIDTH : GNB_MENU_WIDTH;
5316
+ const showMenu = !useRail || menuItems.length > 0;
5317
+ const bodyWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (showMenu ? GNB_MENU_WIDTH : 0);
5318
+ useEffect(() => {
5319
+ layout?.reportGnbState({ forceFull: railHasLeaf, menuVisible: showMenu });
5320
+ }, [layout, railHasLeaf, showMenu]);
5286
5321
  const railFolded = folded && header === "fix";
5287
5322
  const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : bodyWidth;
5323
+ const rightBorder = color === "light" ? `1px solid ${C.topBorder}` : void 0;
5288
5324
  const rootStyle = {
5289
5325
  width: rootWidth,
5290
5326
  transition: `width ${GNB_FOLD_MS}ms ease-out`,
5291
- ...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
5327
+ ...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg, borderRight: rightBorder },
5292
5328
  ...style
5293
5329
  };
5294
5330
  const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
5331
+ const foldInline = !!logo;
5332
+ const sideFoldStyle = {
5333
+ opacity: railFolded ? 0 : 1,
5334
+ position: railFolded ? "absolute" : "static",
5335
+ visibility: railFolded ? "hidden" : "visible",
5336
+ transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}`
5337
+ };
5295
5338
  return /* @__PURE__ */ jsxs(
5296
5339
  "div",
5297
5340
  {
@@ -5303,12 +5346,14 @@ var SGnb = forwardRef(function SGnb2({
5303
5346
  "data-color": color,
5304
5347
  ...rest,
5305
5348
  children: [
5306
- /* @__PURE__ */ jsxs(
5349
+ /* @__PURE__ */ jsx(
5307
5350
  "div",
5308
5351
  {
5309
5352
  className: "relative box-border flex shrink-0 items-center overflow-hidden",
5310
5353
  style: {
5311
5354
  height: H.topHeight,
5355
+ // 인라인 간격은 컨테이너 gap 대신 각 요소 margin 으로 준다 — 런처↔폴드(8px)와
5356
+ // 폴드↔로고(top-gap)를 다르게 두기 위함. 접히면 런처·로고가 흐름에서 빠져 margin 도 무의미해진다.
5312
5357
  padding: `0 ${railFolded ? GNB_RAIL.padding : H.topPaddingX}`,
5313
5358
  borderBottom: `1px solid ${C.topBorder}`,
5314
5359
  backgroundColor: C.panelBg,
@@ -5317,49 +5362,90 @@ var SGnb = forwardRef(function SGnb2({
5317
5362
  // 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
5318
5363
  color: C.itemColorDefault
5319
5364
  },
5320
- children: [
5321
- (onLauncherClick || logo) && /* @__PURE__ */ jsxs(
5322
- "div",
5323
- {
5324
- className: "absolute inset-y-0 flex items-center",
5325
- style: {
5326
- left: H.topPaddingX,
5327
- gap: H.topGap,
5328
- opacity: railFolded ? 0 : 1,
5329
- transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
5330
- // 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
5331
- transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
5332
- visibility: railFolded ? "hidden" : "visible"
5333
- },
5334
- children: [
5335
- onLauncherClick && /* @__PURE__ */ jsx(
5336
- SGhostButton,
5337
- {
5338
- icon: "launcher",
5339
- size: "xs",
5340
- intent: color === "dark" ? "inverse" : "default",
5341
- ariaLabel: "app launcher",
5342
- onClick: onLauncherClick,
5343
- className: "shrink-0"
5344
- }
5345
- ),
5346
- logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
5347
- ]
5348
- }
5349
- ),
5350
- /* @__PURE__ */ jsx(
5351
- SGhostButton,
5352
- {
5353
- icon: "sidebar",
5354
- size: "xs",
5355
- intent: color === "dark" ? "inverse" : "default",
5356
- ariaLabel: "toggle navigation",
5357
- ariaPressed: folded,
5358
- onClick: handleFoldToggle,
5359
- className: "ml-auto shrink-0"
5360
- }
5361
- )
5362
- ]
5365
+ children: foldInline ? (
5366
+ // 런처(있을 )·폴드·로고를 흐름에 나란히 둔다 폴드 버튼이 (런처와) 로고 사이에 선다.
5367
+ // 접히면 런처·로고가 페이드하며 흐름에서 빠지고(sideFoldStyle), 폴드 버튼만 남아 레일 가운데에 선다.
5368
+ /* @__PURE__ */ jsxs(Fragment, { children: [
5369
+ onLauncherClick && /* @__PURE__ */ jsx(
5370
+ SGhostButton,
5371
+ {
5372
+ icon: "launcher",
5373
+ size: "xs",
5374
+ intent: color === "dark" ? "inverse" : "default",
5375
+ ariaLabel: "app launcher",
5376
+ onClick: onLauncherClick,
5377
+ className: "shrink-0",
5378
+ style: { ...sideFoldStyle, marginRight: "8px" }
5379
+ }
5380
+ ),
5381
+ /* @__PURE__ */ jsx(
5382
+ SGhostButton,
5383
+ {
5384
+ icon: "sidebar",
5385
+ size: "xs",
5386
+ intent: color === "dark" ? "inverse" : "default",
5387
+ ariaLabel: "toggle navigation",
5388
+ ariaPressed: folded,
5389
+ onClick: handleFoldToggle,
5390
+ className: "shrink-0"
5391
+ }
5392
+ ),
5393
+ /* @__PURE__ */ jsx(
5394
+ "div",
5395
+ {
5396
+ className: "flex shrink-0 items-center",
5397
+ style: { height: H.logoHeight, marginLeft: H.topGap, ...sideFoldStyle },
5398
+ children: logo
5399
+ }
5400
+ )
5401
+ ] })
5402
+ ) : (
5403
+ // 런처·로고는 좌측 그룹(흐름에서 빼 폭 축소 중 폴드 버튼을 밀어내지 않는다),
5404
+ // 폴드 버튼은 우측 끝(ml-auto)이라 폭·패딩이 줄면 저절로 레일 가운데에 선다.
5405
+ /* @__PURE__ */ jsxs(Fragment, { children: [
5406
+ (onLauncherClick || logo) && /* @__PURE__ */ jsxs(
5407
+ "div",
5408
+ {
5409
+ className: "absolute inset-y-0 flex items-center",
5410
+ style: {
5411
+ left: H.topPaddingX,
5412
+ gap: H.topGap,
5413
+ opacity: railFolded ? 0 : 1,
5414
+ transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
5415
+ // 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
5416
+ transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
5417
+ visibility: railFolded ? "hidden" : "visible"
5418
+ },
5419
+ children: [
5420
+ onLauncherClick && /* @__PURE__ */ jsx(
5421
+ SGhostButton,
5422
+ {
5423
+ icon: "launcher",
5424
+ size: "xs",
5425
+ intent: color === "dark" ? "inverse" : "default",
5426
+ ariaLabel: "app launcher",
5427
+ onClick: onLauncherClick,
5428
+ className: "shrink-0"
5429
+ }
5430
+ ),
5431
+ logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
5432
+ ]
5433
+ }
5434
+ ),
5435
+ /* @__PURE__ */ jsx(
5436
+ SGhostButton,
5437
+ {
5438
+ icon: "sidebar",
5439
+ size: "xs",
5440
+ intent: color === "dark" ? "inverse" : "default",
5441
+ ariaLabel: "toggle navigation",
5442
+ ariaPressed: folded,
5443
+ onClick: handleFoldToggle,
5444
+ className: "ml-auto shrink-0"
5445
+ }
5446
+ )
5447
+ ] })
5448
+ )
5363
5449
  }
5364
5450
  ),
5365
5451
  /* @__PURE__ */ jsxs(
@@ -5395,7 +5481,7 @@ var SGnb = forwardRef(function SGnb2({
5395
5481
  children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", style: { gap: GNB_RAIL.gap }, children: railItems.map(renderRailItem) })
5396
5482
  }
5397
5483
  ),
5398
- /* @__PURE__ */ jsx(
5484
+ showMenu && /* @__PURE__ */ jsx(
5399
5485
  "nav",
5400
5486
  {
5401
5487
  "aria-label": ariaLabel,
@@ -5405,6 +5491,8 @@ var SGnb = forwardRef(function SGnb2({
5405
5491
  paddingTop: M.bodyPaddingY,
5406
5492
  paddingBottom: M.bodyPaddingY,
5407
5493
  backgroundColor: C.panelBg,
5494
+ // full 은 상단바가 전폭이라 루트가 오른쪽 선을 못 그린다 → 메뉴 오른쪽(페이지 경계)이 대신 그린다.
5495
+ borderRight: header === "full" ? rightBorder : void 0,
5408
5496
  pointerEvents: "auto"
5409
5497
  },
5410
5498
  children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: menuItems.map((item) => renderItem(item, 1)) })
@@ -5661,6 +5749,169 @@ function SPagination({
5661
5749
  }
5662
5750
  );
5663
5751
  }
5752
+
5753
+ // src/components/SStepper/stepper.config.ts
5754
+ var STEPPER_SIZES = ["sm", "lg"];
5755
+ var V2 = (path) => `var(--cmp-stepper-${path})`;
5756
+ var STEPPER_SIZE_CONFIG = {
5757
+ sm: {
5758
+ sequenceSize: V2("sm-sequence-size"),
5759
+ fontSize: "12px",
5760
+ lineHeight: "20px",
5761
+ fontWeightActive: 700,
5762
+ fontWeightDefault: 500
5763
+ },
5764
+ lg: {
5765
+ sequenceSize: V2("lg-sequence-size"),
5766
+ fontSize: "14px",
5767
+ lineHeight: "24px",
5768
+ fontWeightActive: 700,
5769
+ fontWeightDefault: 500
5770
+ }
5771
+ };
5772
+ var STEPPER_LAYOUT = {
5773
+ containerGap: V2("container-gap"),
5774
+ itemGap: V2("item-gap"),
5775
+ dashWidth: V2("dash-width"),
5776
+ sequenceRadius: V2("sequence-radius"),
5777
+ sequenceBorderWidth: V2("sequence-border-witdh"),
5778
+ sequenceIcon: V2("sequence-icon")
5779
+ };
5780
+ var STEPPER_COLORS = {
5781
+ dash: V2("dash-color"),
5782
+ sequenceBorder: {
5783
+ active: V2("sequence-border-active"),
5784
+ default: V2("sequence-border-default"),
5785
+ completed: V2("sequence-border-completed")
5786
+ },
5787
+ sequenceBg: {
5788
+ active: V2("sequence-bg-active"),
5789
+ default: V2("sequence-bg-default"),
5790
+ completed: V2("sequence-bg-completed")
5791
+ },
5792
+ sequenceText: {
5793
+ active: V2("sequence-text-active"),
5794
+ default: V2("sequence-text-default")
5795
+ },
5796
+ text: {
5797
+ active: V2("text-active"),
5798
+ default: V2("text-default")
5799
+ }
5800
+ };
5801
+ function getItemValue(item, index) {
5802
+ return item.value ?? String(index + 1);
5803
+ }
5804
+ function SStepper({
5805
+ items,
5806
+ value,
5807
+ size = "sm",
5808
+ ariaLabel = "\uC9C4\uD589 \uB2E8\uACC4",
5809
+ className,
5810
+ style
5811
+ }) {
5812
+ const config = STEPPER_SIZE_CONFIG[size];
5813
+ const activeIndex = Math.max(
5814
+ 0,
5815
+ items.findIndex((item, index) => getItemValue(item, index) === value)
5816
+ );
5817
+ const getState = (index) => {
5818
+ if (index < activeIndex) return "completed";
5819
+ if (index === activeIndex) return "active";
5820
+ return "default";
5821
+ };
5822
+ return /* @__PURE__ */ jsx(
5823
+ "div",
5824
+ {
5825
+ role: "list",
5826
+ "aria-label": ariaLabel,
5827
+ className: cn("inline-flex items-center", className),
5828
+ style: {
5829
+ gap: STEPPER_LAYOUT.containerGap,
5830
+ fontSize: config.fontSize,
5831
+ lineHeight: config.lineHeight,
5832
+ ...style
5833
+ },
5834
+ children: items.map((item, index) => {
5835
+ const state2 = getState(index);
5836
+ const itemStyle = {
5837
+ gap: STEPPER_LAYOUT.itemGap,
5838
+ color: state2 === "active" ? STEPPER_COLORS.text.active : STEPPER_COLORS.text.default,
5839
+ fontWeight: state2 === "active" ? config.fontWeightActive : config.fontWeightDefault
5840
+ };
5841
+ const sequenceStyle = {
5842
+ width: config.sequenceSize,
5843
+ height: config.sequenceSize,
5844
+ flexBasis: config.sequenceSize,
5845
+ borderRadius: STEPPER_LAYOUT.sequenceRadius,
5846
+ borderWidth: STEPPER_LAYOUT.sequenceBorderWidth,
5847
+ borderColor: STEPPER_COLORS.sequenceBorder[state2],
5848
+ background: STEPPER_COLORS.sequenceBg[state2],
5849
+ color: state2 === "completed" ? STEPPER_COLORS.text.default : STEPPER_COLORS.sequenceText[state2]
5850
+ };
5851
+ const content = /* @__PURE__ */ jsxs(
5852
+ "span",
5853
+ {
5854
+ role: "listitem",
5855
+ "aria-current": state2 === "active" ? "step" : void 0,
5856
+ className: "inline-flex items-center whitespace-nowrap",
5857
+ style: itemStyle,
5858
+ children: [
5859
+ /* @__PURE__ */ jsx(
5860
+ "span",
5861
+ {
5862
+ "aria-hidden": true,
5863
+ className: "box-border inline-flex shrink-0 items-center justify-center border border-solid font-bold",
5864
+ style: sequenceStyle,
5865
+ children: state2 === "completed" ? /* @__PURE__ */ jsx(SIcon, { name: "check", size: STEPPER_LAYOUT.sequenceIcon, color: "currentColor" }) : index + 1
5866
+ }
5867
+ ),
5868
+ /* @__PURE__ */ jsx("span", { children: item.label })
5869
+ ]
5870
+ }
5871
+ );
5872
+ return /* @__PURE__ */ jsxs(
5873
+ "span",
5874
+ {
5875
+ className: "inline-flex items-center",
5876
+ style: { gap: STEPPER_LAYOUT.containerGap },
5877
+ children: [
5878
+ content,
5879
+ index < items.length - 1 && /* @__PURE__ */ jsx(
5880
+ "span",
5881
+ {
5882
+ "aria-hidden": true,
5883
+ className: "inline-flex h-px shrink-0 items-center",
5884
+ style: { width: STEPPER_LAYOUT.dashWidth, color: STEPPER_COLORS.dash },
5885
+ children: /* @__PURE__ */ jsx(
5886
+ "svg",
5887
+ {
5888
+ width: "20",
5889
+ height: "1",
5890
+ viewBox: "0 0 20 1",
5891
+ fill: "none",
5892
+ xmlns: "http://www.w3.org/2000/svg",
5893
+ children: /* @__PURE__ */ jsx(
5894
+ "line",
5895
+ {
5896
+ y1: "0.5",
5897
+ x2: "20",
5898
+ y2: "0.5",
5899
+ stroke: "#888888",
5900
+ strokeDasharray: "4 4"
5901
+ }
5902
+ )
5903
+ }
5904
+ )
5905
+ }
5906
+ )
5907
+ ]
5908
+ },
5909
+ getItemValue(item, index)
5910
+ );
5911
+ })
5912
+ }
5913
+ );
5914
+ }
5664
5915
  function SCheckbox({
5665
5916
  value = false,
5666
5917
  val,
@@ -8060,25 +8311,45 @@ function SDatePicker({
8060
8311
  }
8061
8312
  );
8062
8313
  }
8314
+ function splitDateTime(v) {
8315
+ if (!v) return { date: null, hour: "00", minute: "00" };
8316
+ const [date = "", time = ""] = v.split(" ");
8317
+ const [hour = "00", minute = "00"] = time.split(":");
8318
+ return { date: date || null, hour, minute };
8319
+ }
8063
8320
  function RangeCalendar({
8064
8321
  value,
8065
8322
  selectable,
8066
8323
  maxRange,
8324
+ useTimePicker = false,
8067
8325
  onSelect,
8068
8326
  onPendingStartChange,
8069
8327
  onViewChange
8070
8328
  }) {
8071
8329
  const today = todayStr();
8072
- const base = value?.[0] || today;
8330
+ const initial = splitDateTime(value?.[0]);
8331
+ const base = initial.date || today;
8073
8332
  const [y, setY] = useState(Number(base.split("-")[0]));
8074
8333
  const [m, setM] = useState(Number(base.split("-")[1]));
8075
- const [start, setStart] = useState(value?.[0] ?? null);
8076
- const [end, setEnd] = useState(value?.[1] ?? null);
8334
+ const [start, setStart] = useState(initial.date);
8335
+ const [end, setEnd] = useState(splitDateTime(value?.[1]).date);
8077
8336
  const [hover, setHover] = useState(null);
8337
+ const [sH, setSH] = useState(initial.hour);
8338
+ const [sM, setSM] = useState(initial.minute);
8339
+ const [eH, setEH] = useState(splitDateTime(value?.[1]).hour);
8340
+ const [eM, setEM] = useState(splitDateTime(value?.[1]).minute);
8341
+ const lastEmitted = useRef("");
8078
8342
  useEffect(() => {
8079
- setStart(value?.[0] ?? null);
8080
- setEnd(value?.[1] ?? null);
8081
- }, [value]);
8343
+ if (useTimePicker && JSON.stringify(value) === lastEmitted.current) return;
8344
+ const s = splitDateTime(value?.[0]);
8345
+ const e = splitDateTime(value?.[1]);
8346
+ setStart(s.date);
8347
+ setEnd(e.date);
8348
+ setSH(s.hour);
8349
+ setSM(s.minute);
8350
+ setEH(e.hour);
8351
+ setEM(e.minute);
8352
+ }, [value, useTimePicker]);
8082
8353
  const setBase = (ny, nm) => {
8083
8354
  setY(ny);
8084
8355
  setM(nm);
@@ -8122,18 +8393,53 @@ function RangeCalendar({
8122
8393
  const hasRange = !!(rStart && rEnd && rStart !== rEnd);
8123
8394
  const inRange = (d) => !!(rStart && rEnd && d > rStart && d < rEnd);
8124
8395
  const RANGE_BG = "var(--cmp-datepicker-calendar-range-bg)";
8396
+ const pad = (v) => String(Number(v) || 0).padStart(2, "0");
8397
+ const emit2 = (dateStart, dateEnd, time) => {
8398
+ const t = time ?? { sH, sM, eH, eM };
8399
+ const range = useTimePicker ? [`${dateStart} ${pad(t.sH)}:${pad(t.sM)}`, `${dateEnd} ${pad(t.eH)}:${pad(t.eM)}`] : [dateStart, dateEnd];
8400
+ lastEmitted.current = JSON.stringify(range);
8401
+ onSelect(range);
8402
+ };
8125
8403
  const click = (d) => {
8126
8404
  if (isDisabled(d)) return;
8127
8405
  if (!start || complete || d < start) {
8128
8406
  setStart(d);
8129
8407
  setEnd(null);
8130
8408
  setHover(null);
8409
+ if (useTimePicker) {
8410
+ setSH("00");
8411
+ setSM("00");
8412
+ setEH("23");
8413
+ setEM("59");
8414
+ }
8131
8415
  onPendingStartChange?.(d);
8132
8416
  return;
8133
8417
  }
8134
8418
  setEnd(d);
8135
8419
  onPendingStartChange?.(null);
8136
- onSelect([start, d]);
8420
+ emit2(start, d, useTimePicker ? { sH, sM, eH: "23", eM: "59" } : void 0);
8421
+ };
8422
+ const changeTime = (which, part, raw) => {
8423
+ const digits = raw.replace(/\D/g, "").slice(0, 2);
8424
+ const max = part === "h" ? 23 : 59;
8425
+ const next = digits !== "" && Number(digits) > max ? String(max) : digits;
8426
+ const nextTime = { sH, sM, eH, eM };
8427
+ if (which === "start") nextTime[part === "h" ? "sH" : "sM"] = next;
8428
+ else nextTime[part === "h" ? "eH" : "eM"] = next;
8429
+ setSH(nextTime.sH);
8430
+ setSM(nextTime.sM);
8431
+ setEH(nextTime.eH);
8432
+ setEM(nextTime.eM);
8433
+ if (start && end) emit2(start, end, nextTime);
8434
+ };
8435
+ const blurTime = (which, part) => {
8436
+ if (which === "start") {
8437
+ if (part === "h") setSH(pad(sH));
8438
+ else setSM(pad(sM));
8439
+ } else {
8440
+ if (part === "h") setEH(pad(eH));
8441
+ else setEM(pad(eM));
8442
+ }
8137
8443
  };
8138
8444
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
8139
8445
  const renderPanel = (gy, gm, isLeft) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
@@ -8208,35 +8514,120 @@ function RangeCalendar({
8208
8514
  );
8209
8515
  }) })
8210
8516
  ] });
8211
- return /* @__PURE__ */ jsxs("div", { className: "inline-flex h-[364px] 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", children: [
8212
- /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
8213
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-[var(--cmp-datepicker-calendar-gap)] text-[14px] font-medium leading-[24px]", children: [
8214
- /* @__PURE__ */ jsx(SGhostButton, { icon: "chevronLeft", size: "xxs", ariaLabel: "prevYear", onClick: goPrevYear }),
8215
- /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-[40px] items-center justify-center text-center", children: y }),
8216
- /* @__PURE__ */ jsx(SGhostButton, { icon: "chevronRight", size: "xxs", ariaLabel: "nextYear", onClick: goNextYear })
8217
- ] }),
8218
- /* @__PURE__ */ jsx(
8219
- "button",
8220
- {
8221
- type: "button",
8222
- onClick: goToday,
8223
- 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",
8224
- children: "\uC624\uB298"
8225
- }
8226
- )
8227
- ] }),
8228
- /* @__PURE__ */ jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-range-panelGap)]", children: [
8229
- renderPanel(y, m, true),
8230
- /* @__PURE__ */ jsx(
8231
- "span",
8232
- {
8233
- className: "w-px shrink-0 self-stretch bg-[var(--cmp-datepicker-calendar-range-divider)]",
8234
- "aria-hidden": true
8235
- }
8517
+ const renderTimeSection = (which, date, suffix) => {
8518
+ const active = !!date;
8519
+ const [yy = "", mm = "", dd = ""] = date ? date.split("-") : [];
8520
+ const hour = which === "start" ? sH : eH;
8521
+ const minute = which === "start" ? sM : eM;
8522
+ const num = (n, strip) => active && n !== "" ? strip ? String(Number(n)) : n : "-";
8523
+ 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";
8524
+ const LABEL = "text-[color:var(--cmp-datepicker-calendar-time-date-label)]";
8525
+ const UNIT = "text-[color:var(--cmp-datepicker-calendar-time-date-unit)]";
8526
+ return /* @__PURE__ */ jsxs(
8527
+ "div",
8528
+ {
8529
+ className: cn(
8530
+ "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]",
8531
+ active ? "bg-[var(--cmp-datepicker-calendar-time-bg-selected)]" : "bg-[var(--cmp-datepicker-calendar-time-bg-default)]"
8532
+ ),
8533
+ children: [
8534
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-[var(--cmp-datepicker-calendar-time-date-gap)]", children: [
8535
+ /* @__PURE__ */ jsx("span", { className: LABEL, children: num(yy, false) }),
8536
+ /* @__PURE__ */ jsx("span", { className: UNIT, children: "\uB144" }),
8537
+ /* @__PURE__ */ jsx("span", { className: LABEL, children: num(mm, true) }),
8538
+ /* @__PURE__ */ jsx("span", { className: UNIT, children: "\uC6D4" }),
8539
+ /* @__PURE__ */ jsx("span", { className: LABEL, children: num(dd, true) }),
8540
+ /* @__PURE__ */ jsx("span", { className: UNIT, children: "\uC77C" })
8541
+ ] }),
8542
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-[var(--cmp-datepicker-calendar-time-unit-gap)]", children: [
8543
+ /* @__PURE__ */ jsx(
8544
+ "input",
8545
+ {
8546
+ type: "text",
8547
+ inputMode: "numeric",
8548
+ maxLength: 2,
8549
+ "aria-label": `${suffix} \uC2DC`,
8550
+ disabled: !active,
8551
+ value: active ? hour : "",
8552
+ placeholder: "--",
8553
+ onChange: (e) => changeTime(which, "h", e.target.value),
8554
+ onBlur: () => blurTime(which, "h"),
8555
+ className: INPUT_CLS
8556
+ }
8557
+ ),
8558
+ /* @__PURE__ */ jsx("span", { className: LABEL, children: ":" }),
8559
+ /* @__PURE__ */ jsx(
8560
+ "input",
8561
+ {
8562
+ type: "text",
8563
+ inputMode: "numeric",
8564
+ maxLength: 2,
8565
+ "aria-label": `${suffix} \uBD84`,
8566
+ disabled: !active,
8567
+ value: active ? minute : "",
8568
+ placeholder: "--",
8569
+ onChange: (e) => changeTime(which, "m", e.target.value),
8570
+ onBlur: () => blurTime(which, "m"),
8571
+ className: INPUT_CLS
8572
+ }
8573
+ )
8574
+ ] }),
8575
+ /* @__PURE__ */ jsx("span", { className: UNIT, children: suffix })
8576
+ ]
8577
+ }
8578
+ );
8579
+ };
8580
+ return /* @__PURE__ */ jsxs(
8581
+ "div",
8582
+ {
8583
+ className: cn(
8584
+ "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",
8585
+ useTimePicker ? "h-auto" : "h-[364px]"
8236
8586
  ),
8237
- renderPanel(nextMonthYM.year, nextMonthYM.month, false)
8238
- ] })
8239
- ] });
8587
+ children: [
8588
+ /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
8589
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-[var(--cmp-datepicker-calendar-gap)] text-[14px] font-medium leading-[24px]", children: [
8590
+ /* @__PURE__ */ jsx(SGhostButton, { icon: "chevronLeft", size: "xxs", ariaLabel: "prevYear", onClick: goPrevYear }),
8591
+ /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-[40px] items-center justify-center text-center", children: y }),
8592
+ /* @__PURE__ */ jsx(SGhostButton, { icon: "chevronRight", size: "xxs", ariaLabel: "nextYear", onClick: goNextYear })
8593
+ ] }),
8594
+ /* @__PURE__ */ jsx(
8595
+ "button",
8596
+ {
8597
+ type: "button",
8598
+ onClick: goToday,
8599
+ 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",
8600
+ children: "\uC624\uB298"
8601
+ }
8602
+ )
8603
+ ] }),
8604
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-range-panelGap)]", children: [
8605
+ renderPanel(y, m, true),
8606
+ /* @__PURE__ */ jsx(
8607
+ "span",
8608
+ {
8609
+ className: "w-px shrink-0 self-stretch bg-[var(--cmp-datepicker-calendar-range-divider)]",
8610
+ "aria-hidden": true
8611
+ }
8612
+ ),
8613
+ renderPanel(nextMonthYM.year, nextMonthYM.month, false)
8614
+ ] }),
8615
+ useTimePicker && /* @__PURE__ */ jsxs(Fragment, { children: [
8616
+ /* @__PURE__ */ jsx(
8617
+ "span",
8618
+ {
8619
+ className: "h-px w-full bg-[var(--cmp-datepicker-calendar-range-divider)]",
8620
+ "aria-hidden": true
8621
+ }
8622
+ ),
8623
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-time-gap)]", children: [
8624
+ renderTimeSection("start", start, "\uBD80\uD130"),
8625
+ renderTimeSection("end", end, "\uAE4C\uC9C0")
8626
+ ] })
8627
+ ] })
8628
+ ]
8629
+ }
8630
+ );
8240
8631
  }
8241
8632
  function SDateRangePicker({
8242
8633
  value,
@@ -8246,6 +8637,7 @@ function SDateRangePicker({
8246
8637
  placeholder = "YYYY-MM-DD ~ YYYY-MM-DD",
8247
8638
  selectable,
8248
8639
  maxRange,
8640
+ useTimePicker = false,
8249
8641
  disabled = false,
8250
8642
  width,
8251
8643
  name,
@@ -8289,8 +8681,9 @@ function SDateRangePicker({
8289
8681
  setPendingStart(null);
8290
8682
  onValueChange?.(r);
8291
8683
  if (rules && rules.length > 0) setRuleError(runRules(r, rules));
8292
- setOpen(false);
8684
+ if (!useTimePicker) setOpen(false);
8293
8685
  };
8686
+ const resolvedPlaceholder = useTimePicker && placeholder === "YYYY-MM-DD ~ YYYY-MM-DD" ? "YYYY-MM-DD HH:mm ~ YYYY-MM-DD HH:mm" : placeholder;
8294
8687
  return /* @__PURE__ */ jsx(
8295
8688
  SField,
8296
8689
  {
@@ -8357,7 +8750,7 @@ function SDateRangePicker({
8357
8750
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-right", children: startText }),
8358
8751
  /* @__PURE__ */ jsx("span", { className: "px-[4px]", children: "~" }),
8359
8752
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: endText })
8360
- ] }) : placeholder
8753
+ ] }) : resolvedPlaceholder
8361
8754
  }
8362
8755
  )
8363
8756
  ]
@@ -8376,6 +8769,7 @@ function SDateRangePicker({
8376
8769
  value: value ?? null,
8377
8770
  selectable,
8378
8771
  maxRange,
8772
+ useTimePicker,
8379
8773
  onSelect: select,
8380
8774
  onPendingStartChange: setPendingStart,
8381
8775
  onViewChange
@@ -9955,15 +10349,15 @@ var SBarcodeInput = forwardRef(
9955
10349
  );
9956
10350
 
9957
10351
  // src/components/STimePicker/timepicker.config.ts
9958
- var V2 = (path) => `var(--cmp-timepicker-${path})`;
10352
+ var V3 = (path) => `var(--cmp-timepicker-${path})`;
9959
10353
  var TIMEPICKER_SIZES = ["sm", "md"];
9960
10354
  var TIMEPICKER_SIZE_CONFIG = {
9961
10355
  sm: {
9962
- height: V2("sm-height"),
9963
- paddingX: V2("sm-paddingX"),
9964
- gap: V2("sm-gap"),
9965
- icon: V2("sm-icon"),
9966
- radius: V2("sm-radius"),
10356
+ height: V3("sm-height"),
10357
+ paddingX: V3("sm-paddingX"),
10358
+ gap: V3("sm-gap"),
10359
+ icon: V3("sm-icon"),
10360
+ radius: V3("sm-radius"),
9967
10361
  // TODO: component.timepicker 토큰에 typography가 추가되면 CSS 변수로 교체한다.
9968
10362
  fontSize: 12,
9969
10363
  lineHeight: 20,
@@ -9971,11 +10365,11 @@ var TIMEPICKER_SIZE_CONFIG = {
9971
10365
  fieldMinWidth: 128
9972
10366
  },
9973
10367
  md: {
9974
- height: V2("md-height"),
9975
- paddingX: V2("md-paddingX"),
9976
- gap: V2("md-gap"),
9977
- icon: V2("md-icon"),
9978
- radius: V2("md-radius"),
10368
+ height: V3("md-height"),
10369
+ paddingX: V3("md-paddingX"),
10370
+ gap: V3("md-gap"),
10371
+ icon: V3("md-icon"),
10372
+ radius: V3("md-radius"),
9979
10373
  // TODO: component.timepicker 토큰에 typography가 추가되면 CSS 변수로 교체한다.
9980
10374
  fontSize: 14,
9981
10375
  lineHeight: 24,
@@ -10804,6 +11198,6 @@ function STimeRangePicker({
10804
11198
  );
10805
11199
  }
10806
11200
 
10807
- export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, SActionModal, SBadge, SBarcodeInput, SButton, SCalendar, SCallout, SCard, SCheckbox, SChip, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDateRangePicker, SDivider, SDropdownButton, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SInput, SKeyValueTable, SLayout, SLayoutContext, SLinearProgress, SLoadingContainer, SLoadingModal, SLoadingViewport, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSelect, SSwitch, STable, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
11201
+ export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, SActionModal, SBadge, SBarcodeInput, SButton, SCalendar, SCallout, SCard, SCheckbox, SChip, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDateRangePicker, SDivider, SDropdownButton, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SInput, SKeyValueTable, SLayout, SLayoutContext, SLinearProgress, SLoadingContainer, SLoadingModal, SLoadingViewport, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSelect, SStepper, SSwitch, STEPPER_SIZES, STable, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
10808
11202
  //# sourceMappingURL=index.js.map
10809
11203
  //# sourceMappingURL=index.js.map