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
@@ -3,7 +3,10 @@ import { type SGnbType, type SGnbHeader, type SGnbColor, type SGnbMenuItem } fro
3
3
  export interface SGnbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'> {
4
4
  /** 메뉴 스타일: box(라운드) / belt(풀폭 행). 미지정 시 SLayout 의 type 을 따른다. */
5
5
  type?: SGnbType;
6
- /** 상단바 구조: fix(GNB 폭 고정) / full(레이아웃 전폭). 미지정 시 SLayout 의 header 를 따른다. */
6
+ /**
7
+ * 상단바 구조: fix(GNB 폭 고정) / full(레이아웃 전폭). 미지정 시 SLayout 의 header 를 따른다.
8
+ * 단, useRail 이고 레일에 children 없는 아이템이 있으면 메뉴 폭이 출렁이므로 full 로 강제된다.
9
+ */
7
10
  header?: SGnbHeader;
8
11
  /** 색상 테마: light / dark */
9
12
  color?: SGnbColor;
@@ -12,6 +15,7 @@ export interface SGnbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'>
12
15
  /**
13
16
  * 좌측 레일 사용 여부. 켜면 items 의 첫 depth 가 아이콘+라벨 버튼의 세로 레일로 서고,
14
17
  * 선택된 레일 아이템의 children 이 오른쪽 메뉴 패널에 깔린다. 미지정 시 SLayout 의 useRail 을 따른다.
18
+ * children 없는 레일 아이템이 활성이면 메뉴 패널은 렌더되지 않고 레일만 남는다.
15
19
  */
16
20
  useRail?: boolean;
17
21
  /** 현재 선택된 아이템 value */
@@ -42,6 +46,8 @@ export interface SGnbProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'>
42
46
  *
43
47
  * useRail 이면 items 의 첫 depth 가 좌측 레일(아이콘+라벨 68px)로 서고, 선택된 레일 아이템의
44
48
  * children 이 메뉴 패널의 depth1 이 된다 — 트리는 그대로 두고 어디부터 그릴지만 바뀐다.
49
+ * children 없는 레일 아이템은 자기 자신이 선택(리프)이라 메뉴 패널을 렌더하지 않는다. 이런 리프가
50
+ * 하나라도 있으면 활성에 따라 메뉴 폭이 출렁이므로 header 는 full 로 강제해 상단바를 전폭에 고정한다.
45
51
  *
46
52
  * 접힘/펼침은 GNB_FOLD_MS 동안의 슬라이드다 — 레일·메뉴가 함께 왼쪽으로 빠져나가고 펼치면 오른쪽에서 들어온다.
47
53
  * 페이지 쪽 폭은 fix 면 이 컴포넌트의 폭 전환이, full 이면 SLayout 의 메뉴 열 전환이 같은 길이로 맞춘다.
@@ -3,7 +3,7 @@ import { createContext, forwardRef, useState, useCallback, useMemo, useContext,
3
3
  import { clsx } from 'clsx';
4
4
  import { extendTailwindMerge } from 'tailwind-merge';
5
5
  import * as RPopover from '@radix-ui/react-popover';
6
- import { jsx, jsxs } from 'react/jsx-runtime';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
 
8
8
  var twMerge = extendTailwindMerge({
9
9
  extend: {
@@ -3233,15 +3233,30 @@ forwardRef(function SLayout2({
3233
3233
  },
3234
3234
  [isControlled, onFoldedChange]
3235
3235
  );
3236
- const ctx = useMemo(
3237
- () => ({ type, header, useRail, folded: currentFolded, setFolded }),
3238
- [type, header, useRail, currentFolded, setFolded]
3236
+ const [gnbState, setGnbState] = useState({ forceFull: false, menuVisible: true });
3237
+ const reportGnbState = useCallback(
3238
+ (next) => setGnbState(
3239
+ (prev) => prev.forceFull === next.forceFull && prev.menuVisible === next.menuVisible ? prev : next
3240
+ ),
3241
+ []
3239
3242
  );
3240
- const isFullHeader = header === "full";
3241
- const gnbColumnWidth = useRail ? GNB_RAIL_WIDTH + GNB_MENU_WIDTH : GNB_MENU_WIDTH;
3243
+ const effectiveHeader = header === "full" || gnbState.forceFull ? "full" : "fix";
3244
+ const ctx = useMemo(
3245
+ () => ({
3246
+ type,
3247
+ header: effectiveHeader,
3248
+ useRail,
3249
+ folded: currentFolded,
3250
+ setFolded,
3251
+ reportGnbState
3252
+ }),
3253
+ [type, effectiveHeader, useRail, currentFolded, setFolded, reportGnbState]
3254
+ );
3255
+ const isFullHeader = effectiveHeader === "full";
3256
+ const gnbColumnWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (gnbState.menuVisible ? GNB_MENU_WIDTH : 0);
3242
3257
  const frameStyle = isFullHeader ? {
3243
3258
  gridTemplateColumns: `${currentFolded ? 0 : gnbColumnWidth}px 1fr`,
3244
- gridTemplateRows: `${GNB_HEADER[header].topHeight} 1fr`,
3259
+ gridTemplateRows: `${GNB_HEADER[effectiveHeader].topHeight} 1fr`,
3245
3260
  // 메뉴가 미끄러지는 동안 페이지 열도 같은 길이로 따라와야 한 덩어리로 읽힌다.
3246
3261
  // (fix 는 SGnb 가 제 폭을 직접 전환하므로 프레임이 손댈 게 없다)
3247
3262
  transition: `grid-template-columns ${GNB_FOLD_MS}ms ease-out`
@@ -3251,7 +3266,7 @@ forwardRef(function SLayout2({
3251
3266
  {
3252
3267
  ref,
3253
3268
  "data-type": type,
3254
- "data-header": header,
3269
+ "data-header": effectiveHeader,
3255
3270
  className: cn(
3256
3271
  "box-border h-full overflow-hidden",
3257
3272
  isFullHeader ? "grid" : "flex flex-row",
@@ -3284,9 +3299,10 @@ var SGnb = forwardRef(function SGnb2({
3284
3299
  }, ref) {
3285
3300
  const layout = useContext(SLayoutContext);
3286
3301
  const type = typeProp ?? layout?.type ?? "box";
3287
- const header = headerProp ?? layout?.header ?? "fix";
3288
3302
  const folded = foldedProp ?? layout?.folded ?? false;
3289
3303
  const useRail = useRailProp ?? layout?.useRail ?? false;
3304
+ const railHasLeaf = useRail && items.some((item) => !item.children || item.children.length === 0);
3305
+ const header = railHasLeaf ? "full" : headerProp ?? layout?.header ?? "fix";
3290
3306
  const H = GNB_HEADER[header];
3291
3307
  const M = GNB_MENU[type];
3292
3308
  const C = GNB_COLORS[color];
@@ -3420,9 +3436,24 @@ var SGnb = forwardRef(function SGnb2({
3420
3436
  onMouseEnter: () => setHoveredKey(item.value),
3421
3437
  onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
3422
3438
  children: [
3423
- depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, color: C.itemIcon, className: "shrink-0" }),
3439
+ depth === 1 && item.icon && /* @__PURE__ */ jsx(
3440
+ SIcon,
3441
+ {
3442
+ name: item.icon,
3443
+ size: M.depth1Icon,
3444
+ color: isSelected ? C.itemColorActive : C.itemIcon,
3445
+ className: "shrink-0"
3446
+ }
3447
+ ),
3424
3448
  /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap flex flex-no-wrap items-center", children: [
3425
- depth > 1 && iconBranch ? /* @__PURE__ */ jsx("div", { className: "w-[20px] h-[24px] flex items-center justify-center mr-[8px]", children: "-" }) : null,
3449
+ depth > 1 && iconBranch ? /* @__PURE__ */ jsx(
3450
+ "div",
3451
+ {
3452
+ className: "w-[20px] flex items-center justify-center mr-[8px]",
3453
+ style: { height: M.itemLineHeight },
3454
+ children: "-"
3455
+ }
3456
+ ) : null,
3426
3457
  item.label
3427
3458
  ] }),
3428
3459
  item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsx(
@@ -3474,16 +3505,28 @@ var SGnb = forwardRef(function SGnb2({
3474
3505
  };
3475
3506
  const railItems = useRail ? items : [];
3476
3507
  const menuItems = useRail ? items.find((item) => item.value === activeRail)?.children ?? [] : items;
3477
- const bodyWidth = useRail ? GNB_RAIL_WIDTH + GNB_MENU_WIDTH : GNB_MENU_WIDTH;
3508
+ const showMenu = !useRail || menuItems.length > 0;
3509
+ const bodyWidth = (useRail ? GNB_RAIL_WIDTH : 0) + (showMenu ? GNB_MENU_WIDTH : 0);
3510
+ useEffect(() => {
3511
+ layout?.reportGnbState({ forceFull: railHasLeaf, menuVisible: showMenu });
3512
+ }, [layout, railHasLeaf, showMenu]);
3478
3513
  const railFolded = folded && header === "fix";
3479
3514
  const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : bodyWidth;
3515
+ const rightBorder = color === "light" ? `1px solid ${C.topBorder}` : void 0;
3480
3516
  const rootStyle = {
3481
3517
  width: rootWidth,
3482
3518
  transition: `width ${GNB_FOLD_MS}ms ease-out`,
3483
- ...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
3519
+ ...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg, borderRight: rightBorder },
3484
3520
  ...style
3485
3521
  };
3486
3522
  const hideAfterSlide = (hidden) => `visibility 0s linear ${hidden ? `${GNB_FOLD_MS}ms` : "0s"}`;
3523
+ const foldInline = !!logo;
3524
+ const sideFoldStyle = {
3525
+ opacity: railFolded ? 0 : 1,
3526
+ position: railFolded ? "absolute" : "static",
3527
+ visibility: railFolded ? "hidden" : "visible",
3528
+ transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}`
3529
+ };
3487
3530
  return /* @__PURE__ */ jsxs(
3488
3531
  "div",
3489
3532
  {
@@ -3495,12 +3538,14 @@ var SGnb = forwardRef(function SGnb2({
3495
3538
  "data-color": color,
3496
3539
  ...rest,
3497
3540
  children: [
3498
- /* @__PURE__ */ jsxs(
3541
+ /* @__PURE__ */ jsx(
3499
3542
  "div",
3500
3543
  {
3501
3544
  className: "relative box-border flex shrink-0 items-center overflow-hidden",
3502
3545
  style: {
3503
3546
  height: H.topHeight,
3547
+ // 인라인 간격은 컨테이너 gap 대신 각 요소 margin 으로 준다 — 런처↔폴드(8px)와
3548
+ // 폴드↔로고(top-gap)를 다르게 두기 위함. 접히면 런처·로고가 흐름에서 빠져 margin 도 무의미해진다.
3504
3549
  padding: `0 ${railFolded ? GNB_RAIL.padding : H.topPaddingX}`,
3505
3550
  borderBottom: `1px solid ${C.topBorder}`,
3506
3551
  backgroundColor: C.panelBg,
@@ -3509,49 +3554,90 @@ var SGnb = forwardRef(function SGnb2({
3509
3554
  // 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
3510
3555
  color: C.itemColorDefault
3511
3556
  },
3512
- children: [
3513
- (onLauncherClick || logo) && /* @__PURE__ */ jsxs(
3514
- "div",
3515
- {
3516
- className: "absolute inset-y-0 flex items-center",
3517
- style: {
3518
- left: H.topPaddingX,
3519
- gap: H.topGap,
3520
- opacity: railFolded ? 0 : 1,
3521
- transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
3522
- // 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
3523
- transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
3524
- visibility: railFolded ? "hidden" : "visible"
3525
- },
3526
- children: [
3527
- onLauncherClick && /* @__PURE__ */ jsx(
3528
- SGhostButton,
3529
- {
3530
- icon: "launcher",
3531
- size: "xs",
3532
- intent: color === "dark" ? "inverse" : "default",
3533
- ariaLabel: "app launcher",
3534
- onClick: onLauncherClick,
3535
- className: "shrink-0"
3536
- }
3537
- ),
3538
- logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
3539
- ]
3540
- }
3541
- ),
3542
- /* @__PURE__ */ jsx(
3543
- SGhostButton,
3544
- {
3545
- icon: "sidebar",
3546
- size: "xs",
3547
- intent: color === "dark" ? "inverse" : "default",
3548
- ariaLabel: "toggle navigation",
3549
- ariaPressed: folded,
3550
- onClick: handleFoldToggle,
3551
- className: "ml-auto shrink-0"
3552
- }
3553
- )
3554
- ]
3557
+ children: foldInline ? (
3558
+ // 런처(있을 )·폴드·로고를 흐름에 나란히 둔다 폴드 버튼이 (런처와) 로고 사이에 선다.
3559
+ // 접히면 런처·로고가 페이드하며 흐름에서 빠지고(sideFoldStyle), 폴드 버튼만 남아 레일 가운데에 선다.
3560
+ /* @__PURE__ */ jsxs(Fragment, { children: [
3561
+ onLauncherClick && /* @__PURE__ */ jsx(
3562
+ SGhostButton,
3563
+ {
3564
+ icon: "launcher",
3565
+ size: "xs",
3566
+ intent: color === "dark" ? "inverse" : "default",
3567
+ ariaLabel: "app launcher",
3568
+ onClick: onLauncherClick,
3569
+ className: "shrink-0",
3570
+ style: { ...sideFoldStyle, marginRight: "8px" }
3571
+ }
3572
+ ),
3573
+ /* @__PURE__ */ jsx(
3574
+ SGhostButton,
3575
+ {
3576
+ icon: "sidebar",
3577
+ size: "xs",
3578
+ intent: color === "dark" ? "inverse" : "default",
3579
+ ariaLabel: "toggle navigation",
3580
+ ariaPressed: folded,
3581
+ onClick: handleFoldToggle,
3582
+ className: "shrink-0"
3583
+ }
3584
+ ),
3585
+ /* @__PURE__ */ jsx(
3586
+ "div",
3587
+ {
3588
+ className: "flex shrink-0 items-center",
3589
+ style: { height: H.logoHeight, marginLeft: H.topGap, ...sideFoldStyle },
3590
+ children: logo
3591
+ }
3592
+ )
3593
+ ] })
3594
+ ) : (
3595
+ // 런처·로고는 좌측 그룹(흐름에서 빼 폭 축소 중 폴드 버튼을 밀어내지 않는다),
3596
+ // 폴드 버튼은 우측 끝(ml-auto)이라 폭·패딩이 줄면 저절로 레일 가운데에 선다.
3597
+ /* @__PURE__ */ jsxs(Fragment, { children: [
3598
+ (onLauncherClick || logo) && /* @__PURE__ */ jsxs(
3599
+ "div",
3600
+ {
3601
+ className: "absolute inset-y-0 flex items-center",
3602
+ style: {
3603
+ left: H.topPaddingX,
3604
+ gap: H.topGap,
3605
+ opacity: railFolded ? 0 : 1,
3606
+ transition: `opacity ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(railFolded)}, transform ${GNB_FOLD_MS}ms ease-out`,
3607
+ // 메뉴와 같은 방향으로 빠지되, 좁은 레일 안에서 꼬리가 비치지 않게 페이드가 거리를 대신한다
3608
+ transform: railFolded ? `translateX(-${H.topPaddingX})` : "none",
3609
+ visibility: railFolded ? "hidden" : "visible"
3610
+ },
3611
+ children: [
3612
+ onLauncherClick && /* @__PURE__ */ jsx(
3613
+ SGhostButton,
3614
+ {
3615
+ icon: "launcher",
3616
+ size: "xs",
3617
+ intent: color === "dark" ? "inverse" : "default",
3618
+ ariaLabel: "app launcher",
3619
+ onClick: onLauncherClick,
3620
+ className: "shrink-0"
3621
+ }
3622
+ ),
3623
+ logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo })
3624
+ ]
3625
+ }
3626
+ ),
3627
+ /* @__PURE__ */ jsx(
3628
+ SGhostButton,
3629
+ {
3630
+ icon: "sidebar",
3631
+ size: "xs",
3632
+ intent: color === "dark" ? "inverse" : "default",
3633
+ ariaLabel: "toggle navigation",
3634
+ ariaPressed: folded,
3635
+ onClick: handleFoldToggle,
3636
+ className: "ml-auto shrink-0"
3637
+ }
3638
+ )
3639
+ ] })
3640
+ )
3555
3641
  }
3556
3642
  ),
3557
3643
  /* @__PURE__ */ jsxs(
@@ -3587,7 +3673,7 @@ var SGnb = forwardRef(function SGnb2({
3587
3673
  children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", style: { gap: GNB_RAIL.gap }, children: railItems.map(renderRailItem) })
3588
3674
  }
3589
3675
  ),
3590
- /* @__PURE__ */ jsx(
3676
+ showMenu && /* @__PURE__ */ jsx(
3591
3677
  "nav",
3592
3678
  {
3593
3679
  "aria-label": ariaLabel,
@@ -3597,6 +3683,8 @@ var SGnb = forwardRef(function SGnb2({
3597
3683
  paddingTop: M.bodyPaddingY,
3598
3684
  paddingBottom: M.bodyPaddingY,
3599
3685
  backgroundColor: C.panelBg,
3686
+ // full 은 상단바가 전폭이라 루트가 오른쪽 선을 못 그린다 → 메뉴 오른쪽(페이지 경계)이 대신 그린다.
3687
+ borderRight: header === "full" ? rightBorder : void 0,
3600
3688
  pointerEvents: "auto"
3601
3689
  },
3602
3690
  children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: menuItems.map((item) => renderItem(item, 1)) })