sellmate-design-system-react 0.2.0 → 0.4.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.
Files changed (138) hide show
  1. package/README.md +84 -21
  2. package/dist/components/SActionModal/SActionModal.cjs +2 -0
  3. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  4. package/dist/components/SActionModal/SActionModal.js +2 -0
  5. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +2 -0
  7. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js +2 -0
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +2 -0
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +2 -0
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +2 -0
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +2 -0
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +2 -0
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.js +2 -0
  21. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  22. package/dist/components/SConfirmModal/SConfirmModal.cjs +2 -0
  23. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  24. package/dist/components/SConfirmModal/SConfirmModal.js +2 -0
  25. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  26. package/dist/components/SDatePicker/SDatePicker.cjs +2 -0
  27. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  28. package/dist/components/SDatePicker/SDatePicker.js +2 -0
  29. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  30. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +2 -0
  31. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  32. package/dist/components/SDateRangePicker/SDateRangePicker.js +2 -0
  33. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  34. package/dist/components/SField/SField.cjs +2 -0
  35. package/dist/components/SField/SField.cjs.map +1 -1
  36. package/dist/components/SField/SField.js +2 -0
  37. package/dist/components/SField/SField.js.map +1 -1
  38. package/dist/components/SFilePicker/SFilePicker.cjs +2 -0
  39. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  40. package/dist/components/SFilePicker/SFilePicker.js +2 -0
  41. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  42. package/dist/components/SGhostButton/SGhostButton.cjs +2 -0
  43. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  44. package/dist/components/SGhostButton/SGhostButton.d.ts +3 -1
  45. package/dist/components/SGhostButton/SGhostButton.js +2 -0
  46. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  47. package/dist/components/SGnb/SGnb.cjs +394 -213
  48. package/dist/components/SGnb/SGnb.cjs.map +1 -1
  49. package/dist/components/SGnb/SGnb.d.ts +16 -12
  50. package/dist/components/SGnb/SGnb.js +375 -214
  51. package/dist/components/SGnb/SGnb.js.map +1 -1
  52. package/dist/components/SGnb/gnb.config.d.ts +35 -11
  53. package/dist/components/SGnb/index.d.ts +1 -1
  54. package/dist/components/SGuide/SGuide.cjs +2 -0
  55. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  56. package/dist/components/SGuide/SGuide.js +2 -0
  57. package/dist/components/SGuide/SGuide.js.map +1 -1
  58. package/dist/components/SInput/SInput.cjs +2 -0
  59. package/dist/components/SInput/SInput.cjs.map +1 -1
  60. package/dist/components/SInput/SInput.js +2 -0
  61. package/dist/components/SInput/SInput.js.map +1 -1
  62. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +2 -0
  63. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  64. package/dist/components/SKeyValueTable/SKeyValueTable.js +2 -0
  65. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  66. package/dist/components/SLayout/SLayout.cjs +26 -8
  67. package/dist/components/SLayout/SLayout.cjs.map +1 -1
  68. package/dist/components/SLayout/SLayout.d.ts +15 -9
  69. package/dist/components/SLayout/SLayout.js +26 -8
  70. package/dist/components/SLayout/SLayout.js.map +1 -1
  71. package/dist/components/SLayout/index.d.ts +1 -1
  72. package/dist/components/SLoadingModal/SLoadingModal.cjs +2 -0
  73. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  74. package/dist/components/SLoadingModal/SLoadingModal.js +2 -0
  75. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  76. package/dist/components/SModalContainer/SModalContainer.cjs +2 -0
  77. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  78. package/dist/components/SModalContainer/SModalContainer.js +2 -0
  79. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  80. package/dist/components/SNumberInput/SNumberInput.cjs +2 -0
  81. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  82. package/dist/components/SNumberInput/SNumberInput.js +2 -0
  83. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  84. package/dist/components/SPage/SPage.cjs +27 -9
  85. package/dist/components/SPage/SPage.cjs.map +1 -1
  86. package/dist/components/SPage/SPage.d.ts +2 -2
  87. package/dist/components/SPage/SPage.js +27 -9
  88. package/dist/components/SPage/SPage.js.map +1 -1
  89. package/dist/components/SPopover/SPopover.cjs +2 -0
  90. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  91. package/dist/components/SPopover/SPopover.js +2 -0
  92. package/dist/components/SPopover/SPopover.js.map +1 -1
  93. package/dist/components/SSelect/SSelect.cjs +2 -0
  94. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  95. package/dist/components/SSelect/SSelect.js +2 -0
  96. package/dist/components/SSelect/SSelect.js.map +1 -1
  97. package/dist/components/STable/STable.cjs +3 -1
  98. package/dist/components/STable/STable.cjs.map +1 -1
  99. package/dist/components/STable/STable.js +3 -1
  100. package/dist/components/STable/STable.js.map +1 -1
  101. package/dist/components/STextarea/STextarea.cjs +2 -0
  102. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  103. package/dist/components/STextarea/STextarea.js +2 -0
  104. package/dist/components/STextarea/STextarea.js.map +1 -1
  105. package/dist/components/STimePicker/STimePicker.cjs +3834 -0
  106. package/dist/components/STimePicker/STimePicker.cjs.map +1 -0
  107. package/dist/components/STimePicker/STimePicker.d.ts +50 -0
  108. package/dist/components/STimePicker/STimePicker.js +3811 -0
  109. package/dist/components/STimePicker/STimePicker.js.map +1 -0
  110. package/dist/components/STimePicker/index.d.ts +2 -0
  111. package/dist/components/STimePicker/timepicker.config.d.ts +23 -0
  112. package/dist/components/STimeRangePicker/STimeRangePicker.cjs +3908 -0
  113. package/dist/components/STimeRangePicker/STimeRangePicker.cjs.map +1 -0
  114. package/dist/components/STimeRangePicker/STimeRangePicker.d.ts +53 -0
  115. package/dist/components/STimeRangePicker/STimeRangePicker.js +3885 -0
  116. package/dist/components/STimeRangePicker/STimeRangePicker.js.map +1 -0
  117. package/dist/components/STimeRangePicker/index.d.ts +1 -0
  118. package/dist/components/STimeRangePicker/time-range-picker.config.d.ts +9 -0
  119. package/dist/components/SToast/SToast.cjs +2 -0
  120. package/dist/components/SToast/SToast.cjs.map +1 -1
  121. package/dist/components/SToast/SToast.js +2 -0
  122. package/dist/components/SToast/SToast.js.map +1 -1
  123. package/dist/components/SToast/SToastContainer.cjs +2 -0
  124. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  125. package/dist/components/SToast/SToastContainer.js +2 -0
  126. package/dist/components/SToast/SToastContainer.js.map +1 -1
  127. package/dist/components/STooltip/STooltip.cjs +2 -0
  128. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  129. package/dist/components/STooltip/STooltip.js +2 -0
  130. package/dist/components/STooltip/STooltip.js.map +1 -1
  131. package/dist/index.cjs +1011 -136
  132. package/dist/index.cjs.map +1 -1
  133. package/dist/index.d.ts +3 -0
  134. package/dist/index.js +1006 -136
  135. package/dist/index.js.map +1 -1
  136. package/dist/styles.css +475 -74
  137. package/dist/theme.css +85 -70
  138. package/package.json +4 -1
@@ -1,7 +1,8 @@
1
1
  "use client";
2
- import { forwardRef, createContext, useState, useCallback, useMemo, useContext, useEffect } from 'react';
2
+ import { createContext, forwardRef, useState, useCallback, useMemo, useContext, useEffect, useRef } from 'react';
3
3
  import { clsx } from 'clsx';
4
4
  import { extendTailwindMerge } from 'tailwind-merge';
5
+ import * as RPopover from '@radix-ui/react-popover';
5
6
  import { jsx, jsxs } from 'react/jsx-runtime';
6
7
 
7
8
  var twMerge = extendTailwindMerge({
@@ -15,6 +16,33 @@ function cn(...inputs) {
15
16
  return twMerge(clsx(inputs));
16
17
  }
17
18
 
19
+ // src/lib/floating-anim.ts
20
+ var FLOATING_SLIDE_ANIM = [
21
+ "data-[state=open]:data-[side=top]:animate-floating-in-top",
22
+ "data-[state=delayed-open]:data-[side=top]:animate-floating-in-top",
23
+ "data-[state=instant-open]:data-[side=top]:animate-floating-in-top",
24
+ "data-[state=closed]:data-[side=top]:animate-floating-out-top",
25
+ "data-[state=open]:data-[side=bottom]:animate-floating-in-bottom",
26
+ "data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom",
27
+ "data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom",
28
+ "data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom",
29
+ "data-[state=open]:data-[side=left]:animate-floating-in-left",
30
+ "data-[state=delayed-open]:data-[side=left]:animate-floating-in-left",
31
+ "data-[state=instant-open]:data-[side=left]:animate-floating-in-left",
32
+ "data-[state=closed]:data-[side=left]:animate-floating-out-left",
33
+ "data-[state=open]:data-[side=right]:animate-floating-in-right",
34
+ "data-[state=delayed-open]:data-[side=right]:animate-floating-in-right",
35
+ "data-[state=instant-open]:data-[side=right]:animate-floating-in-right",
36
+ "data-[state=closed]:data-[side=right]:animate-floating-out-right"
37
+ ].join(" ");
38
+ var PortalContainerContext = createContext(null);
39
+ var InsideModalContext = createContext(false);
40
+ PortalContainerContext.Provider;
41
+ InsideModalContext.Provider;
42
+ function usePortalContainer() {
43
+ return useContext(PortalContainerContext) ?? void 0;
44
+ }
45
+
18
46
  // src/lib/color.ts
19
47
  var COLOR_KEYS = [
20
48
  "white",
@@ -2850,18 +2878,226 @@ var SIcon = forwardRef(function SIcon2({ name, size = 24, color, rotate, label,
2850
2878
  );
2851
2879
  });
2852
2880
 
2881
+ // src/components/STag/tag.config.ts
2882
+ var TAG_SIZE_CONFIG = {
2883
+ xs: {
2884
+ height: 20,
2885
+ paddingX: 8,
2886
+ gap: 4,
2887
+ radius: 6,
2888
+ iconSize: 12,
2889
+ fontSize: 11,
2890
+ fontWeight: 500,
2891
+ lineHeight: 18
2892
+ },
2893
+ sm: {
2894
+ height: 24,
2895
+ paddingX: 8,
2896
+ gap: 4,
2897
+ radius: 6,
2898
+ iconSize: 16,
2899
+ fontSize: 12,
2900
+ fontWeight: 700,
2901
+ lineHeight: 20
2902
+ },
2903
+ md: {
2904
+ height: 28,
2905
+ paddingX: 12,
2906
+ gap: 6,
2907
+ radius: 8,
2908
+ iconSize: 16,
2909
+ fontSize: 14,
2910
+ fontWeight: 700,
2911
+ lineHeight: 24
2912
+ }
2913
+ };
2914
+ var STag = forwardRef(function STag2({
2915
+ shape = "square",
2916
+ size = "sm",
2917
+ color = "grey",
2918
+ label = "",
2919
+ icon,
2920
+ iconLeft = true,
2921
+ width,
2922
+ className,
2923
+ style,
2924
+ ...rest
2925
+ }, ref) {
2926
+ const s = TAG_SIZE_CONFIG[size];
2927
+ const contentColor = `var(--cmp-tag-${color}-content)`;
2928
+ const iconNode = icon ? /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
2929
+ const resolvedWidth = typeof width === "number" ? `${width}px` : width;
2930
+ return /* @__PURE__ */ jsxs(
2931
+ "span",
2932
+ {
2933
+ ref,
2934
+ "aria-label": label || "tag",
2935
+ className: cn(
2936
+ "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
2937
+ className
2938
+ ),
2939
+ style: {
2940
+ minHeight: s.height,
2941
+ padding: `0 ${s.paddingX}px`,
2942
+ gap: s.gap,
2943
+ borderRadius: shape === "pill" ? 999 : s.radius,
2944
+ fontSize: s.fontSize,
2945
+ fontWeight: s.fontWeight,
2946
+ lineHeight: `${s.lineHeight}px`,
2947
+ background: `var(--cmp-tag-${color}-bg)`,
2948
+ color: contentColor,
2949
+ ...resolvedWidth != null ? { width: resolvedWidth } : {},
2950
+ ...style
2951
+ },
2952
+ ...rest,
2953
+ children: [
2954
+ icon && iconLeft && iconNode,
2955
+ label && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
2956
+ icon && !iconLeft && iconNode
2957
+ ]
2958
+ }
2959
+ );
2960
+ });
2961
+ var SIZE = {
2962
+ xxs: { icon: 12, radius: "sm" },
2963
+ xs: { icon: 16, radius: "sm" },
2964
+ sm: { icon: 20, radius: "sm" },
2965
+ md: { icon: 24, radius: "md" },
2966
+ lg: { icon: 32, radius: "md" }
2967
+ };
2968
+ var TOOLTIP_HIDE_DELAY_MS = 100;
2969
+ function SGhostButton({
2970
+ icon,
2971
+ size = "sm",
2972
+ intent = "default",
2973
+ ariaLabel = "",
2974
+ ariaPressed,
2975
+ tooltipText,
2976
+ disabled = false,
2977
+ onClick,
2978
+ anchorClassName,
2979
+ anchorStyle,
2980
+ className,
2981
+ style
2982
+ }) {
2983
+ const portalContainer = usePortalContainer();
2984
+ const [open, setOpen] = useState(false);
2985
+ const hideTimer = useRef(void 0);
2986
+ const cancelHide = () => {
2987
+ if (hideTimer.current) {
2988
+ clearTimeout(hideTimer.current);
2989
+ hideTimer.current = void 0;
2990
+ }
2991
+ };
2992
+ const openNow = () => {
2993
+ cancelHide();
2994
+ setOpen(true);
2995
+ };
2996
+ const startHide = () => {
2997
+ cancelHide();
2998
+ hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
2999
+ };
3000
+ const s = SIZE[size];
3001
+ const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3002
+ const text = (tooltipText ?? "").trim();
3003
+ const hasTooltip = text !== "";
3004
+ const btn = /* @__PURE__ */ jsxs(
3005
+ "button",
3006
+ {
3007
+ type: "button",
3008
+ disabled,
3009
+ "aria-disabled": disabled ? "true" : void 0,
3010
+ "aria-label": ariaLabel.trim() || void 0,
3011
+ "aria-pressed": ariaPressed,
3012
+ tabIndex: disabled ? -1 : void 0,
3013
+ onClick: (e) => {
3014
+ if (disabled) {
3015
+ e.preventDefault();
3016
+ return;
3017
+ }
3018
+ onClick?.(e);
3019
+ },
3020
+ className: cn(
3021
+ "group relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3022
+ disabled ? "cursor-not-allowed" : "cursor-pointer",
3023
+ "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
3024
+ className
3025
+ ),
3026
+ style: {
3027
+ width: `var(--cmp-ghostButton-${size}-size)`,
3028
+ height: `var(--cmp-ghostButton-${size}-size)`,
3029
+ borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
3030
+ ...style
3031
+ },
3032
+ children: [
3033
+ !disabled && /* @__PURE__ */ jsx(
3034
+ "span",
3035
+ {
3036
+ "aria-hidden": true,
3037
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover:opacity-5",
3038
+ style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
3039
+ }
3040
+ ),
3041
+ /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
3042
+ ]
3043
+ }
3044
+ );
3045
+ if (!hasTooltip) return btn;
3046
+ return /* @__PURE__ */ jsxs(RPopover.Root, { open, onOpenChange: setOpen, children: [
3047
+ /* @__PURE__ */ jsx(RPopover.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
3048
+ "span",
3049
+ {
3050
+ className: cn("inline-flex", anchorClassName),
3051
+ style: anchorStyle,
3052
+ onMouseEnter: openNow,
3053
+ onMouseLeave: startHide,
3054
+ onFocus: openNow,
3055
+ onBlur: startHide,
3056
+ children: btn
3057
+ }
3058
+ ) }),
3059
+ /* @__PURE__ */ jsx(RPopover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
3060
+ RPopover.Content,
3061
+ {
3062
+ side: "top",
3063
+ sideOffset: 4,
3064
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
3065
+ "aria-hidden": true,
3066
+ onOpenAutoFocus: (e) => e.preventDefault(),
3067
+ onMouseEnter: cancelHide,
3068
+ onMouseLeave: startHide,
3069
+ children: /* @__PURE__ */ jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
3070
+ }
3071
+ ) })
3072
+ ] });
3073
+ }
3074
+
2853
3075
  // src/components/SGnb/gnb.config.ts
2854
3076
  var belt = (p) => `var(--cmp-gnbBelt-${p})`;
2855
3077
  var box = (p) => `var(--cmp-gnbBox-${p})`;
2856
3078
  var light = (p) => `var(--cmp-light-${p})`;
2857
3079
  var dark = (p) => `var(--cmp-dark-${p})`;
2858
- var GNB_LAYOUT = {
2859
- // 사용자 명명: type="box" → gnbBox 토큰(둥금), type="belt" → gnbBelt 토큰(각짐)
2860
- box: {
3080
+ var GNB_HEADER = {
3081
+ fix: {
3082
+ topHeight: belt("top-height"),
3083
+ topPaddingX: belt("top-paddingX"),
3084
+ topGap: belt("top-gap"),
3085
+ logoHeight: belt("logo-height")
3086
+ },
3087
+ full: {
3088
+ topHeight: box("top-height"),
2861
3089
  topPaddingX: box("paddingX"),
2862
3090
  topGap: box("gap"),
2863
3091
  // 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)
2864
- bodyPadding: box("menu-body-padding"),
3092
+ logoHeight: box("logo-height")
3093
+ }
3094
+ };
3095
+ var GNB_MENU = {
3096
+ box: {
3097
+ // menu.body.padding 은 전방향 토큰 → 세로 패딩과 아이템 좌우 여백 양쪽에 쓴다
3098
+ bodyPaddingY: box("menu-body-padding"),
3099
+ itemMarginX: box("menu-body-padding"),
3100
+ itemPaddingY: box("item-paddingY"),
2865
3101
  itemPaddingRight: box("item-paddingRight"),
2866
3102
  itemGap: box("item-gap"),
2867
3103
  itemRadius: box("item-radius"),
@@ -2881,9 +3117,10 @@ var GNB_LAYOUT = {
2881
3117
  // token: gnbBox.item.typography.default.lineHeight
2882
3118
  },
2883
3119
  belt: {
2884
- topPaddingX: belt("top-paddingX"),
2885
- topGap: belt("top-gap"),
2886
- bodyPadding: belt("body-paddingY"),
3120
+ bodyPaddingY: belt("body-paddingY"),
3121
+ itemMarginX: "0px",
3122
+ // belt 토큰에 가로 여백 없음 → 풀폭 행 (들여쓰기는 depth*-paddingLeft 전담)
3123
+ itemPaddingY: belt("item-paddingY"),
2887
3124
  itemPaddingRight: belt("item-paddingRight"),
2888
3125
  itemGap: belt("item-gap"),
2889
3126
  itemRadius: "0px",
@@ -2913,7 +3150,9 @@ var GNB_COLORS = {
2913
3150
  itemBgHover: light("item-bg-hover"),
2914
3151
  itemBgSelected: light("item-bg-selected"),
2915
3152
  itemColorDefault: light("item-fg-default"),
2916
- itemColorActive: light("item-fg-selected")
3153
+ itemColorHover: light("item-fg-hover"),
3154
+ itemColorActive: light("item-fg-selected"),
3155
+ itemChevron: light("item-chevron")
2917
3156
  },
2918
3157
  dark: {
2919
3158
  panelBg: dark("bg"),
@@ -2922,18 +3161,19 @@ var GNB_COLORS = {
2922
3161
  itemBgHover: dark("item-bg-hover"),
2923
3162
  itemBgSelected: dark("item-bg-selected"),
2924
3163
  itemColorDefault: dark("item-fg-default"),
2925
- itemColorActive: dark("item-fg-selected")
3164
+ itemColorHover: dark("item-fg-hover"),
3165
+ itemColorActive: dark("item-fg-selected"),
3166
+ itemChevron: dark("item-chevron")
2926
3167
  }
2927
3168
  };
2928
3169
  var GNB_COMMON = {
2929
- topHeight: box("top-height"),
2930
- // 56 (box=belt 동일)
2931
- logoHeight: box("logo-height"),
2932
- // 32
2933
- itemPaddingY: box("item-paddingY"),
2934
- // 6
2935
- foldWidth: belt("fold-width")
2936
- // 48 (belt 토큰 공유)
3170
+ /** 접힘 레일 폭. header="full" 은 접으면 메뉴만 사라져 레일이 없으므로 fix 전용. */
3171
+ foldWidth: belt("fold-width"),
3172
+ /**
3173
+ * 상단바 아이콘 버튼 사이 간격. fix 레일(세로 스택)의 gap·상하 여백에 쓴다.
3174
+ * 레일 전용 여백 토큰이 따로 없어 같은 값(8px)인 이 토큰으로 재해석했다.
3175
+ */
3176
+ buttonGap: box("button-gap")
2937
3177
  };
2938
3178
  var GNB_MENU_WIDTH = 240;
2939
3179
  function findGnbAncestors(items, value) {
@@ -2952,6 +3192,7 @@ function findGnbAncestors(items, value) {
2952
3192
  var SLayoutContext = createContext(null);
2953
3193
  forwardRef(function SLayout2({
2954
3194
  type = "box",
3195
+ header = "fix",
2955
3196
  folded,
2956
3197
  defaultFolded = false,
2957
3198
  onFoldedChange,
@@ -2971,22 +3212,23 @@ forwardRef(function SLayout2({
2971
3212
  [isControlled, onFoldedChange]
2972
3213
  );
2973
3214
  const ctx = useMemo(
2974
- () => ({ type, folded: currentFolded, setFolded }),
2975
- [type, currentFolded, setFolded]
3215
+ () => ({ type, header, folded: currentFolded, setFolded }),
3216
+ [type, header, currentFolded, setFolded]
2976
3217
  );
2977
- const isBox = type === "box";
2978
- const frameStyle = isBox ? {
3218
+ const isFullHeader = header === "full";
3219
+ const frameStyle = isFullHeader ? {
2979
3220
  gridTemplateColumns: `${currentFolded ? 0 : GNB_MENU_WIDTH}px 1fr`,
2980
- gridTemplateRows: `${GNB_COMMON.topHeight} 1fr`
3221
+ gridTemplateRows: `${GNB_HEADER[header].topHeight} 1fr`
2981
3222
  } : void 0;
2982
3223
  return /* @__PURE__ */ jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
2983
3224
  "div",
2984
3225
  {
2985
3226
  ref,
2986
3227
  "data-type": type,
3228
+ "data-header": header,
2987
3229
  className: cn(
2988
3230
  "box-border h-full overflow-hidden",
2989
- isBox ? "grid" : "flex flex-row",
3231
+ isFullHeader ? "grid" : "flex flex-row",
2990
3232
  className
2991
3233
  ),
2992
3234
  style: { ...frameStyle, ...style },
@@ -2995,90 +3237,10 @@ forwardRef(function SLayout2({
2995
3237
  }
2996
3238
  ) });
2997
3239
  });
2998
-
2999
- // src/components/STag/tag.config.ts
3000
- var TAG_SIZE_CONFIG = {
3001
- xs: {
3002
- height: 20,
3003
- paddingX: 8,
3004
- gap: 4,
3005
- radius: 6,
3006
- iconSize: 12,
3007
- fontSize: 11,
3008
- fontWeight: 500,
3009
- lineHeight: 18
3010
- },
3011
- sm: {
3012
- height: 24,
3013
- paddingX: 8,
3014
- gap: 4,
3015
- radius: 6,
3016
- iconSize: 16,
3017
- fontSize: 12,
3018
- fontWeight: 700,
3019
- lineHeight: 20
3020
- },
3021
- md: {
3022
- height: 28,
3023
- paddingX: 12,
3024
- gap: 6,
3025
- radius: 8,
3026
- iconSize: 16,
3027
- fontSize: 14,
3028
- fontWeight: 700,
3029
- lineHeight: 24
3030
- }
3031
- };
3032
- var STag = forwardRef(function STag2({
3033
- shape = "square",
3034
- size = "sm",
3035
- color = "grey",
3036
- label = "",
3037
- icon,
3038
- iconLeft = true,
3039
- width,
3040
- className,
3041
- style,
3042
- ...rest
3043
- }, ref) {
3044
- const s = TAG_SIZE_CONFIG[size];
3045
- const contentColor = `var(--cmp-tag-${color}-content)`;
3046
- const iconNode = icon ? /* @__PURE__ */ jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
3047
- const resolvedWidth = typeof width === "number" ? `${width}px` : width;
3048
- return /* @__PURE__ */ jsxs(
3049
- "span",
3050
- {
3051
- ref,
3052
- "aria-label": label || "tag",
3053
- className: cn(
3054
- "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
3055
- className
3056
- ),
3057
- style: {
3058
- minHeight: s.height,
3059
- padding: `0 ${s.paddingX}px`,
3060
- gap: s.gap,
3061
- borderRadius: shape === "pill" ? 999 : s.radius,
3062
- fontSize: s.fontSize,
3063
- fontWeight: s.fontWeight,
3064
- lineHeight: `${s.lineHeight}px`,
3065
- background: `var(--cmp-tag-${color}-bg)`,
3066
- color: contentColor,
3067
- ...resolvedWidth != null ? { width: resolvedWidth } : {},
3068
- ...style
3069
- },
3070
- ...rest,
3071
- children: [
3072
- icon && iconLeft && iconNode,
3073
- label && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
3074
- icon && !iconLeft && iconNode
3075
- ]
3076
- }
3077
- );
3078
- });
3079
- var DEPTH_PADDING_LEFT = (L, depth) => depth === 1 ? L.depth1PaddingLeft : depth === 2 ? L.depth2PaddingLeft : L.depth3PaddingLeft;
3240
+ var DEPTH_PADDING_LEFT = (M, depth) => depth === 1 ? M.depth1PaddingLeft : depth === 2 ? M.depth2PaddingLeft : M.depth3PaddingLeft;
3080
3241
  var SGnb = forwardRef(function SGnb2({
3081
3242
  type: typeProp,
3243
+ header: headerProp,
3082
3244
  color = "light",
3083
3245
  items,
3084
3246
  value = "",
@@ -3087,14 +3249,17 @@ var SGnb = forwardRef(function SGnb2({
3087
3249
  onFoldChange,
3088
3250
  onLauncherClick,
3089
3251
  logo,
3090
- search,
3252
+ ariaLabel = "global navigation",
3091
3253
  className,
3092
- style
3254
+ style,
3255
+ ...rest
3093
3256
  }, ref) {
3094
3257
  const layout = useContext(SLayoutContext);
3095
3258
  const type = typeProp ?? layout?.type ?? "box";
3259
+ const header = headerProp ?? layout?.header ?? "fix";
3096
3260
  const folded = foldedProp ?? layout?.folded ?? false;
3097
- const L = GNB_LAYOUT[type];
3261
+ const H = GNB_HEADER[header];
3262
+ const M = GNB_MENU[type];
3098
3263
  const C = GNB_COLORS[color];
3099
3264
  const [expandedKeys, setExpandedKeys] = useState(
3100
3265
  () => new Set(findGnbAncestors(items, value))
@@ -3104,6 +3269,7 @@ var SGnb = forwardRef(function SGnb2({
3104
3269
  const ancestors = findGnbAncestors(items, value);
3105
3270
  if (ancestors.length === 0) return;
3106
3271
  setExpandedKeys((prev) => {
3272
+ if (ancestors.every((a) => prev.has(a))) return prev;
3107
3273
  const next = new Set(prev);
3108
3274
  ancestors.forEach((a) => next.add(a));
3109
3275
  return next;
@@ -3128,148 +3294,142 @@ var SGnb = forwardRef(function SGnb2({
3128
3294
  const handleFoldToggle = () => {
3129
3295
  const next = !folded;
3130
3296
  onFoldChange?.(next);
3131
- layout?.setFolded(next);
3297
+ if (foldedProp === void 0) layout?.setFolded(next);
3132
3298
  };
3133
- const iconBtnClass = "inline-flex shrink-0 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-1.5";
3134
3299
  const renderItem = (item, depth) => {
3135
3300
  const hasChildren = !!(item.children && item.children.length > 0);
3136
3301
  const isSelected = !hasChildren && item.value === value;
3137
3302
  const isExpanded = expandedKeys.has(item.value);
3138
- const isHovered = hoveredKey === item.value;
3139
- const bg = isSelected ? C.itemBgSelected : isHovered && !item.disabled ? C.itemBgHover : C.itemBgDefault;
3303
+ const isHovered = hoveredKey === item.value && !item.disabled;
3140
3304
  const itemStyle = {
3141
- gap: L.itemGap,
3142
- paddingTop: GNB_COMMON.itemPaddingY,
3143
- paddingBottom: GNB_COMMON.itemPaddingY,
3144
- paddingRight: L.itemPaddingRight,
3145
- paddingLeft: DEPTH_PADDING_LEFT(L, depth),
3146
- marginLeft: L.bodyPadding,
3147
- marginRight: L.bodyPadding,
3148
- borderRadius: L.itemRadius,
3149
- backgroundColor: bg,
3150
- color: isSelected ? C.itemColorActive : C.itemColorDefault,
3151
- fontSize: depth === 1 ? L.depth1FontSize : L.itemFontSize,
3152
- lineHeight: `${depth === 1 ? L.depth1LineHeight : L.itemLineHeight}px`,
3153
- fontWeight: depth === 1 || isSelected ? L.depth1FontWeight : 400,
3305
+ gap: M.itemGap,
3306
+ paddingTop: M.itemPaddingY,
3307
+ paddingBottom: M.itemPaddingY,
3308
+ paddingRight: M.itemPaddingRight,
3309
+ paddingLeft: DEPTH_PADDING_LEFT(M, depth),
3310
+ marginLeft: M.itemMarginX,
3311
+ marginRight: M.itemMarginX,
3312
+ borderRadius: M.itemRadius,
3313
+ backgroundColor: isSelected ? C.itemBgSelected : isHovered ? C.itemBgHover : C.itemBgDefault,
3314
+ color: isSelected ? C.itemColorActive : isHovered ? C.itemColorHover : C.itemColorDefault,
3315
+ // <button> UA 폰트(Arial 13.3px)를 끊고 앱 폰트를 잇는다
3316
+ fontFamily: "inherit",
3317
+ fontSize: depth === 1 ? M.depth1FontSize : M.itemFontSize,
3318
+ lineHeight: `${depth === 1 ? M.depth1LineHeight : M.itemLineHeight}px`,
3319
+ fontWeight: depth === 1 || isSelected ? M.depth1FontWeight : 400,
3154
3320
  opacity: item.disabled ? 0.4 : 1,
3155
3321
  cursor: item.disabled ? "not-allowed" : "pointer"
3156
3322
  };
3157
- return /* @__PURE__ */ jsxs("div", { children: [
3158
- /* @__PURE__ */ jsxs(
3159
- "div",
3160
- {
3161
- role: hasChildren ? "button" : "menuitem",
3162
- tabIndex: item.disabled ? -1 : 0,
3163
- "aria-disabled": item.disabled || void 0,
3164
- "aria-expanded": hasChildren ? isExpanded : void 0,
3165
- "aria-current": isSelected ? "page" : void 0,
3166
- className: "box-border flex select-none items-center transition-colors duration-150",
3167
- style: itemStyle,
3168
- onClick: () => handleItemClick(item),
3169
- onMouseEnter: () => setHoveredKey(item.value),
3170
- onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
3171
- onKeyDown: (e) => {
3172
- if (e.key === "Enter" || e.key === " ") {
3173
- e.preventDefault();
3174
- handleItemClick(item);
3175
- }
3176
- },
3177
- children: [
3178
- depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: L.depth1Icon, className: "shrink-0" }),
3179
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: item.label }),
3180
- item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsx(
3181
- STag,
3182
- {
3183
- shape: "square",
3184
- size: "xs",
3185
- color: isSelected ? "blue" : "grey",
3186
- label: item.tag,
3187
- className: "shrink-0"
3188
- }
3189
- ),
3190
- hasChildren && /* @__PURE__ */ jsx(
3191
- SIcon,
3192
- {
3193
- name: "chevronDown",
3194
- size: L.itemArrow,
3195
- rotate: isExpanded ? 180 : 0,
3196
- className: "shrink-0 transition-transform duration-150"
3197
- }
3198
- )
3199
- ]
3200
- }
3201
- ),
3202
- hasChildren && isExpanded && item.children.map((child) => renderItem(child, depth + 1))
3203
- ] }, item.value);
3323
+ return (
3324
+ // li flex-col 로 둬야 버튼이 좌우 마진만큼 안쪽으로 들어가고,
3325
+ // 하위 ul 은 마진 없이 전폭을 써서 depth*-paddingLeft 가 들여쓰기를 단독으로 책임진다.
3326
+ /* @__PURE__ */ jsxs("li", { className: "flex flex-col", children: [
3327
+ /* @__PURE__ */ jsxs(
3328
+ "button",
3329
+ {
3330
+ type: "button",
3331
+ disabled: item.disabled,
3332
+ "aria-expanded": hasChildren ? isExpanded : void 0,
3333
+ "aria-current": isSelected ? "page" : void 0,
3334
+ className: "box-border flex select-none items-center border-none bg-transparent text-left transition-colors duration-150",
3335
+ style: itemStyle,
3336
+ onClick: () => handleItemClick(item),
3337
+ onMouseEnter: () => setHoveredKey(item.value),
3338
+ onMouseLeave: () => setHoveredKey((prev) => prev === item.value ? null : prev),
3339
+ children: [
3340
+ depth === 1 && item.icon && /* @__PURE__ */ jsx(SIcon, { name: item.icon, size: M.depth1Icon, className: "shrink-0" }),
3341
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: item.label }),
3342
+ item.tag !== void 0 && item.tag !== "" && /* @__PURE__ */ jsx(
3343
+ STag,
3344
+ {
3345
+ shape: "square",
3346
+ size: "xs",
3347
+ color: item.tagColor ?? "blue",
3348
+ label: item.tag,
3349
+ className: "shrink-0"
3350
+ }
3351
+ ),
3352
+ hasChildren && /* @__PURE__ */ jsx(
3353
+ SIcon,
3354
+ {
3355
+ name: "chevronDown",
3356
+ size: M.itemArrow,
3357
+ color: C.itemChevron,
3358
+ rotate: isExpanded ? 180 : 0,
3359
+ className: "shrink-0 transition-transform duration-150"
3360
+ }
3361
+ )
3362
+ ]
3363
+ }
3364
+ ),
3365
+ hasChildren && isExpanded && /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: item.children.map((child) => renderItem(child, depth + 1)) })
3366
+ ] }, item.value)
3367
+ );
3368
+ };
3369
+ const railFolded = folded && header === "fix";
3370
+ const rootWidth = header === "full" ? "100%" : folded ? GNB_COMMON.foldWidth : GNB_MENU_WIDTH;
3371
+ const rootStyle = {
3372
+ width: rootWidth,
3373
+ ...header === "full" ? { gridArea: "1 / 1 / -1 / -1", pointerEvents: "none" } : { backgroundColor: C.panelBg },
3374
+ ...style
3204
3375
  };
3205
- const railFolded = folded && type === "belt";
3206
- const rootWidth = type === "box" ? "100%" : folded ? GNB_COMMON.foldWidth : GNB_MENU_WIDTH;
3207
- const inBoxGrid = !!layout && type === "box";
3208
- const rootStyle = useMemo(
3209
- () => ({
3210
- width: rootWidth,
3211
- ...inBoxGrid ? { gridArea: "1 / 1 / -1 / -1" } : null,
3212
- ...style
3213
- }),
3214
- [rootWidth, inBoxGrid, style]
3215
- );
3216
- const iconColor = C.itemColorDefault;
3217
3376
  return /* @__PURE__ */ jsxs(
3218
3377
  "div",
3219
3378
  {
3220
3379
  ref,
3221
- className: cn(
3222
- "box-border flex flex-col",
3223
- type === "box" && !inBoxGrid ? "h-auto" : "h-full",
3224
- className
3225
- ),
3380
+ className: cn("box-border flex h-full shrink-0 flex-col", className),
3226
3381
  style: rootStyle,
3227
3382
  "data-type": type,
3383
+ "data-header": header,
3228
3384
  "data-color": color,
3385
+ ...rest,
3229
3386
  children: [
3230
3387
  /* @__PURE__ */ jsxs(
3231
3388
  "div",
3232
3389
  {
3233
3390
  className: cn("box-border flex items-center", railFolded && "flex-col"),
3234
3391
  style: {
3235
- gap: railFolded ? 8 : L.topGap,
3236
- height: railFolded ? "auto" : GNB_COMMON.topHeight,
3237
- padding: railFolded ? "8px 0" : `0 ${L.topPaddingX}`,
3392
+ gap: railFolded ? GNB_COMMON.buttonGap : H.topGap,
3393
+ height: railFolded ? "auto" : H.topHeight,
3394
+ padding: railFolded ? `${GNB_COMMON.buttonGap} 0` : `0 ${H.topPaddingX}`,
3238
3395
  borderBottom: `1px solid ${C.topBorder}`,
3239
3396
  backgroundColor: C.panelBg,
3240
- color: iconColor
3397
+ pointerEvents: "auto",
3398
+ // 아이콘 버튼은 자체 ghostButton 토큰을 쓰므로, 이 색은 logo 슬롯 상속용
3399
+ color: C.itemColorDefault
3241
3400
  },
3242
3401
  children: [
3243
- /* @__PURE__ */ jsx(
3244
- "button",
3402
+ onLauncherClick && /* @__PURE__ */ jsx(
3403
+ SGhostButton,
3245
3404
  {
3246
- type: "button",
3247
- "aria-label": "app launcher",
3248
- className: iconBtnClass,
3405
+ icon: "launcher",
3406
+ size: "xs",
3407
+ intent: color === "dark" ? "inverse" : "default",
3408
+ ariaLabel: "app launcher",
3249
3409
  onClick: onLauncherClick,
3250
- children: /* @__PURE__ */ jsx(SIcon, { name: "launcher", size: 20 })
3410
+ className: "shrink-0"
3251
3411
  }
3252
3412
  ),
3253
- !railFolded && logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: GNB_COMMON.logoHeight }, children: logo }),
3254
- !railFolded && /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: search }),
3413
+ !railFolded && logo && /* @__PURE__ */ jsx("div", { className: "flex items-center", style: { height: H.logoHeight }, children: logo }),
3255
3414
  /* @__PURE__ */ jsx(
3256
- "button",
3415
+ SGhostButton,
3257
3416
  {
3258
- type: "button",
3259
- "aria-label": "toggle navigation",
3260
- "aria-pressed": folded,
3261
- className: cn(iconBtnClass, !railFolded && "ml-auto"),
3417
+ icon: "sidebar",
3418
+ size: "xs",
3419
+ intent: color === "dark" ? "inverse" : "default",
3420
+ ariaLabel: "toggle navigation",
3421
+ ariaPressed: folded,
3262
3422
  onClick: handleFoldToggle,
3263
- children: /* @__PURE__ */ jsx(SIcon, { name: "sidebar", size: 20 })
3423
+ className: cn("shrink-0", !railFolded && "ml-auto")
3264
3424
  }
3265
3425
  )
3266
3426
  ]
3267
3427
  }
3268
3428
  ),
3269
3429
  !folded && /* @__PURE__ */ jsx(
3270
- "div",
3430
+ "nav",
3271
3431
  {
3272
- role: "menu",
3432
+ "aria-label": ariaLabel,
3273
3433
  className: "flex flex-col overflow-y-auto",
3274
3434
  style: {
3275
3435
  width: GNB_MENU_WIDTH,
@@ -3278,11 +3438,12 @@ var SGnb = forwardRef(function SGnb2({
3278
3438
  flexShrink: 1,
3279
3439
  flexBasis: "auto",
3280
3440
  minHeight: 0,
3281
- paddingTop: L.bodyPadding,
3282
- paddingBottom: L.bodyPadding,
3283
- backgroundColor: C.panelBg
3441
+ paddingTop: M.bodyPaddingY,
3442
+ paddingBottom: M.bodyPaddingY,
3443
+ backgroundColor: C.panelBg,
3444
+ pointerEvents: "auto"
3284
3445
  },
3285
- children: items.map((item) => renderItem(item, 1))
3446
+ children: /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col p-0", children: items.map((item) => renderItem(item, 1)) })
3286
3447
  }
3287
3448
  )
3288
3449
  ]