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