sellmate-design-system-react 8.0.0 → 9.0.0-beta.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 (51) hide show
  1. package/AGENTS.md +492 -116
  2. package/README.md +38 -8
  3. package/dist/components/SChipFilter/README.md +69 -0
  4. package/dist/components/SChipFilter/SChipFilter.d.ts +160 -0
  5. package/dist/components/SChipFilter/index.d.ts +1 -0
  6. package/dist/components/SDatePicker/README.md +2 -0
  7. package/dist/components/SDateRangePicker/README.md +21 -0
  8. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +16 -0
  9. package/dist/components/SDateRangePicker/index.d.ts +1 -1
  10. package/dist/components/SDraggableItem/README.md +1 -0
  11. package/dist/components/SDraggableItem/SDraggableItem.d.ts +3 -0
  12. package/dist/components/SDrawer/README.md +5 -4
  13. package/dist/components/SDrawer/SDrawer.d.ts +16 -5
  14. package/dist/components/SExpansionItem/README.md +1 -0
  15. package/dist/components/SExpansionItem/SExpansionItem.d.ts +3 -0
  16. package/dist/components/SGhostButton/README.md +4 -0
  17. package/dist/components/SGnb/README.md +3 -0
  18. package/dist/components/SGnb/SGnb.d.ts +15 -0
  19. package/dist/components/SGuide/README.md +1 -1
  20. package/dist/components/SGuide/SGuide.d.ts +1 -1
  21. package/dist/components/SIcon/README.md +2 -0
  22. package/dist/components/SIcon/icons.gen.d.ts +1 -0
  23. package/dist/components/SList/README.md +1 -0
  24. package/dist/components/SList/SList.d.ts +2 -0
  25. package/dist/components/SListItem/README.md +3 -2
  26. package/dist/components/SListItem/SListItem.d.ts +5 -2
  27. package/dist/components/SPage/README.md +21 -0
  28. package/dist/components/SPage/SPage.d.ts +10 -0
  29. package/dist/components/SPage/SPageHeader.d.ts +32 -0
  30. package/dist/components/SPage/index.d.ts +1 -0
  31. package/dist/components/SPortal/README.md +1 -1
  32. package/dist/components/SPortal/SPortal.d.ts +7 -1
  33. package/dist/components/SRadio/README.md +2 -0
  34. package/dist/components/SRadioButton/README.md +13 -0
  35. package/dist/components/SSelect/README.md +2 -2
  36. package/dist/components/SSelect/SSelect.d.ts +2 -2
  37. package/dist/components/STag/README.md +2 -0
  38. package/dist/components/STextLink/README.md +2 -0
  39. package/dist/components/STooltip/README.md +2 -0
  40. package/dist/index.cjs +1750 -232
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +1761 -245
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/floating-width.d.ts +22 -0
  46. package/dist/llms-full.txt +628 -126
  47. package/dist/llms.txt +498 -118
  48. package/dist/styles.css +249 -22
  49. package/dist/theme.css +36 -10
  50. package/eslint/scale.gen.mjs +1 -1
  51. package/package.json +8 -4
package/dist/index.cjs CHANGED
@@ -7,7 +7,7 @@ var react = require('react');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var reactDom = require('react-dom');
9
9
  var RCheckbox = require('@radix-ui/react-checkbox');
10
- var RPopover2 = require('@radix-ui/react-popover');
10
+ var RPopover9 = require('@radix-ui/react-popover');
11
11
  var client = require('react-dom/client');
12
12
  var RTooltip = require('@radix-ui/react-tooltip');
13
13
  var RDropdown = require('@radix-ui/react-dropdown-menu');
@@ -34,7 +34,7 @@ function _interopNamespace(e) {
34
34
  }
35
35
 
36
36
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
37
- var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
37
+ var RPopover9__namespace = /*#__PURE__*/_interopNamespace(RPopover9);
38
38
  var RTooltip__namespace = /*#__PURE__*/_interopNamespace(RTooltip);
39
39
  var RDropdown__namespace = /*#__PURE__*/_interopNamespace(RDropdown);
40
40
  var RSwitch__namespace = /*#__PURE__*/_interopNamespace(RSwitch);
@@ -2593,6 +2593,27 @@ var Icon_sidebar = (p) => {
2593
2593
  }
2594
2594
  );
2595
2595
  };
2596
+ var Icon_star = (p) => {
2597
+ const { size = 24, ...rest } = p;
2598
+ return /* @__PURE__ */ jsxRuntime.jsx(
2599
+ "svg",
2600
+ {
2601
+ width: size,
2602
+ height: size,
2603
+ viewBox: "0 0 24 24",
2604
+ fill: "none",
2605
+ xmlns: "http://www.w3.org/2000/svg",
2606
+ ...rest,
2607
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2608
+ "path",
2609
+ {
2610
+ d: "M12.3358 3.45335C12.0755 2.84888 11.2185 2.84888 10.9582 3.45335L8.78136 8.50811C8.67278 8.76024 8.43512 8.93291 8.16178 8.95826L2.68175 9.46652C2.02642 9.5273 1.76157 10.3424 2.25602 10.7768L6.39071 14.409C6.59695 14.5902 6.68773 14.8696 6.62737 15.1374L5.41733 20.5063C5.27263 21.1483 5.966 21.6521 6.5319 21.3161L11.2641 18.5062C11.5001 18.366 11.7939 18.366 12.0299 18.5062L16.7621 21.3161C17.328 21.6521 18.0214 21.1483 17.8767 20.5063L16.6666 15.1374C16.6063 14.8696 16.6971 14.5902 16.9033 14.409L21.038 10.7768C21.5324 10.3424 21.2676 9.5273 20.6123 9.46652L15.1322 8.95826C14.8589 8.93291 14.6212 8.76024 14.5126 8.50811L12.3358 3.45335Z",
2611
+ fill: "currentColor"
2612
+ }
2613
+ )
2614
+ }
2615
+ );
2616
+ };
2596
2617
  var Icon_store = (p) => {
2597
2618
  const { size = 24, ...rest } = p;
2598
2619
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3087,6 +3108,7 @@ var ICONS = {
3087
3108
  settingOutline: Icon_settingOutline,
3088
3109
  shield: Icon_shield,
3089
3110
  sidebar: Icon_sidebar,
3111
+ star: Icon_star,
3090
3112
  store: Icon_store,
3091
3113
  synchronize: Icon_synchronize,
3092
3114
  thumbsDown: Icon_thumbsDown,
@@ -3191,6 +3213,7 @@ var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
3191
3213
  bordered,
3192
3214
  selected,
3193
3215
  dense,
3216
+ size,
3194
3217
  dragging,
3195
3218
  disabled,
3196
3219
  hovered,
@@ -3204,6 +3227,8 @@ var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
3204
3227
  const supportingTextNode = renderSlot(supportingText, renderState);
3205
3228
  const hasSupportingText = supportingTextNode != null && supportingTextNode !== "";
3206
3229
  const trailingNode = renderSlot(trailing, renderState);
3230
+ const titleTypo = size === "sm" ? selected ? "typo-item-sm-selected" : "typo-item-sm-default" : selected ? "typo-item-md-selected" : "typo-item-md-default";
3231
+ const paddingYClass = dense ? size === "sm" ? "py-(--cmp-listDraggableItem-paddingY-sm-dense)" : "py-(--cmp-listDraggableItem-paddingY-md-dense)" : size === "sm" ? "py-(--cmp-listDraggableItem-paddingY-sm-normal)" : "py-(--cmp-listDraggableItem-paddingY-md-normal)";
3207
3232
  return /* @__PURE__ */ jsxRuntime.jsxs(
3208
3233
  "div",
3209
3234
  {
@@ -3211,16 +3236,19 @@ var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
3211
3236
  "aria-disabled": disabled || void 0,
3212
3237
  "data-dragging": dragging || void 0,
3213
3238
  className: cn(
3214
- "relative flex w-full items-center gap-(--cmp-listDraggableItem-gap) overflow-hidden px-(--cmp-listDraggableItem-paddingX) text-[12px] leading-[20px]",
3215
- disabled ? "bg-(--cmp-listDraggableItem-bg-default) font-medium text-(--sys-color-disabled-text)" : selected ? "bg-(--cmp-listDraggableItem-bg-selected) font-bold text-(--cmp-listDraggableItem-text-selected)" : "bg-(--cmp-listDraggableItem-bg-default) font-medium text-(--cmp-listDraggableItem-text-default)",
3239
+ "relative flex w-full items-center gap-(--cmp-listDraggableItem-gap) overflow-hidden px-(--cmp-listDraggableItem-paddingX)",
3240
+ titleTypo,
3241
+ disabled ? "bg-(--cmp-listDraggableItem-bg-default) text-(--sys-color-disabled-text)" : selected ? "bg-(--cmp-listDraggableItem-bg-selected) text-(--cmp-listDraggableItem-text-selected)" : "bg-(--cmp-listDraggableItem-bg-default) text-(--cmp-listDraggableItem-text-default)",
3216
3242
  !disabled && !selected && !dragging && "hover:bg-(--cmp-listDraggableItem-bg-hover)",
3217
- hasSupportingText && supportingTextPosition === "bottom" ? dense ? "min-h-[52px] py-(--cmp-listDraggableItem-paddingY-dense)" : "min-h-[60px] py-(--cmp-listDraggableItem-paddingY-normal)" : dense ? "h-[32px] py-(--cmp-listDraggableItem-paddingY-dense)" : "h-[40px] py-(--cmp-listDraggableItem-paddingY-normal)",
3218
- bordered ? cn(
3243
+ hasSupportingText && supportingTextPosition === "bottom" ? dense ? "min-h-[52px]" : "min-h-[60px]" : dense ? "h-[32px]" : "h-[40px]",
3244
+ paddingYClass,
3245
+ // 항목 사이 구분선은 리스트가 그린다 — SDraggableList 의 separator (SList.tsx)
3246
+ bordered && cn(
3219
3247
  "rounded-(--cmp-listDraggableItem-radius-bordered) border border-(--cmp-listDraggableItem-border-default)",
3220
3248
  !disabled && !selected && !dragging && "hover:border-(--cmp-listDraggableItem-bordered-border-hover)",
3221
3249
  !disabled && selected && !dragging && "border-(--cmp-listDraggableItem-bordered-border-selected) bg-(--cmp-listDraggableItem-bordered-bg-selected)",
3222
3250
  !disabled && selected && dragging && "border-[length:var(--cmp-listDraggableItem-bordered-dragging-border-width)] border-(--cmp-listDraggableItem-bordered-dragging-border-color) bg-(--cmp-listDraggableItem-bordered-dragging-bg)"
3223
- ) : "border-b border-(--cmp-listDraggableItem-border-default)",
3251
+ ),
3224
3252
  disabled && "cursor-not-allowed",
3225
3253
  className
3226
3254
  ),
@@ -3272,7 +3300,7 @@ var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
3272
3300
  "span",
3273
3301
  {
3274
3302
  className: cn(
3275
- "min-w-0 truncate text-[12px] font-normal leading-[20px] text-(--sys-color-fg-secondary)",
3303
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3276
3304
  supportingTextPosition === "right" && "flex-1"
3277
3305
  ),
3278
3306
  children: supportingTextNode
@@ -3300,6 +3328,7 @@ var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableIte
3300
3328
  selected = false,
3301
3329
  dragging = false,
3302
3330
  dense = false,
3331
+ size = "sm",
3303
3332
  disabled = false,
3304
3333
  dragOverlay = true,
3305
3334
  dragOverlayOpacity = 0.75,
@@ -3371,6 +3400,7 @@ var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableIte
3371
3400
  bordered,
3372
3401
  selected,
3373
3402
  dense,
3403
+ size,
3374
3404
  dragging: dragging || overlayVisible,
3375
3405
  disabled,
3376
3406
  hovered,
@@ -3405,6 +3435,7 @@ var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableIte
3405
3435
  bordered,
3406
3436
  selected,
3407
3437
  dense,
3438
+ size,
3408
3439
  dragging: true,
3409
3440
  disabled,
3410
3441
  hovered: true,
@@ -3419,7 +3450,7 @@ var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableIte
3419
3450
  ] });
3420
3451
  });
3421
3452
  var SDraggableItem2 = /* @__PURE__ */ withDisplayName(SDraggableItemImpl, "SDraggableItem");
3422
- var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, children, useGap = false, usePadding = false, ...rest }, ref) {
3453
+ var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, children, useGap = false, usePadding = false, separator = false, ...rest }, ref) {
3423
3454
  return /* @__PURE__ */ jsxRuntime.jsx(
3424
3455
  "div",
3425
3456
  {
@@ -3427,6 +3458,7 @@ var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, chi
3427
3458
  className: cn(
3428
3459
  useGap && "flex flex-col gap-(--cmp-list-group-gap)",
3429
3460
  usePadding && "p-(--cmp-list-group-padding)",
3461
+ separator && "[&>*]:border-b [&>*]:border-(--cmp-listItem-border-default)",
3430
3462
  className
3431
3463
  ),
3432
3464
  ...rest,
@@ -3756,6 +3788,7 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
3756
3788
  interaction,
3757
3789
  accentStripe = false,
3758
3790
  dense = false,
3791
+ size = "sm",
3759
3792
  disabled = false,
3760
3793
  onToggle,
3761
3794
  className,
@@ -3771,6 +3804,7 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
3771
3804
  const hasChevronInteraction = interaction === "chevron";
3772
3805
  const showInteractionChevron = hasChevronInteraction && !disabled && (isSelected || hovered);
3773
3806
  const interactionChevronColor = isSelected ? "var(--sys-color-fg-brand)" : "var(--sys-color-fg-quaternary)";
3807
+ const titleTypo = size === "sm" ? isExpanded ? "typo-item-sm-selected" : "typo-item-sm-default" : isExpanded ? "typo-item-md-selected" : "typo-item-md-default";
3774
3808
  const reportExpandedDescendant = react.useCallback(
3775
3809
  (delta) => {
3776
3810
  setExpandedDescendantCount((current) => Math.max(0, current + delta));
@@ -3796,7 +3830,7 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
3796
3830
  ref,
3797
3831
  className: cn(
3798
3832
  "group/expansion-item relative overflow-hidden bg-(--cmp-listExpansionItem-title-bg-default)",
3799
- !bordered && "border-b border-(--cmp-listExpansionItem-border-default)",
3833
+ // 항목 사이 구분선은 리스트가 그린다 — SList/SExpansionList 의 separator (SList.tsx)
3800
3834
  // 보더 색은 시맨틱 토큰(border-default → hover strong → 펼침 accentLight)을 쓴다.
3801
3835
  // TODO: expansionItem/bordered/radius 토큰 생성 후 rounded 고정값도 토큰으로 교체한다.
3802
3836
  bordered && "rounded-[8px] border border-border-default hover:border-border-strong [&:has(>button:hover)]:border-border-strong",
@@ -3828,7 +3862,8 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
3828
3862
  onToggle?.(next, event);
3829
3863
  },
3830
3864
  className: cn(
3831
- "group/expansion-item-button flex w-full cursor-pointer items-center justify-between border-0 text-left text-[14px] font-bold leading-[24px] text-(--cmp-listExpansionItem-title-text-default) focus-visible:outline focus-visible:outline-2 focus-visible:outline-(--sys-color-border-accent) disabled:cursor-not-allowed disabled:text-(--sys-color-disabled-text)",
3865
+ "group/expansion-item-button flex w-full cursor-pointer items-center justify-between border-0 text-left text-(--cmp-listExpansionItem-title-text-default) focus-visible:outline focus-visible:outline-2 focus-visible:outline-(--sys-color-border-accent) disabled:cursor-not-allowed disabled:text-(--sys-color-disabled-text)",
3866
+ titleTypo,
3832
3867
  isExpanded && "border-b border-(--cmp-listExpansionItem-title-border)",
3833
3868
  hasChevronInteraction ? "hover:bg-(--sys-color-bg-neutralBright)" : !isExpanded && "hover:bg-(--cmp-listExpansionItem-title-bg-hover)",
3834
3869
  hasSupportingText && supportingTextPosition === "bottom" ? dense ? "min-h-[52px] py-(--cmp-listExpansionItem-title-paddingY-dense)" : "min-h-[60px] py-(--cmp-listExpansionItem-title-paddingY-normal)" : dense ? "h-[32px] py-(--cmp-listExpansionItem-title-paddingY-dense)" : "h-[40px] py-(--cmp-listExpansionItem-title-paddingY-normal)",
@@ -3865,7 +3900,7 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
3865
3900
  "span",
3866
3901
  {
3867
3902
  className: cn(
3868
- "min-w-0 truncate text-[12px] font-normal leading-[20px] text-(--sys-color-fg-secondary)",
3903
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3869
3904
  supportingTextPosition === "right" && "flex-1"
3870
3905
  ),
3871
3906
  children: supportingText
@@ -3905,10 +3940,11 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3905
3940
  trailing,
3906
3941
  bordered = false,
3907
3942
  selected = false,
3908
- clickable = true,
3943
+ clickable = false,
3909
3944
  interaction,
3910
3945
  accentStripe = false,
3911
3946
  dense = false,
3947
+ size = "sm",
3912
3948
  disabled = false,
3913
3949
  className,
3914
3950
  onMouseEnter,
@@ -3927,6 +3963,7 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3927
3963
  const hasChevronInteraction = interaction === "chevron";
3928
3964
  const showInteractionChevron = hasChevronInteraction && isInteractive && (isSelected || hovered);
3929
3965
  const interactionChevronColor = isSelected ? "var(--sys-color-fg-brand)" : "var(--sys-color-fg-quaternary)";
3966
+ const titleTypo = size === "sm" ? isSelected ? "typo-item-sm-selected" : "typo-item-sm-default" : isSelected ? "typo-item-md-selected" : "typo-item-md-default";
3930
3967
  const handleMouseEnter = (event) => {
3931
3968
  if (isInteractive) setHovered(true);
3932
3969
  onMouseEnter?.(event);
@@ -3943,7 +3980,8 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3943
3980
  onMouseEnter: handleMouseEnter,
3944
3981
  onMouseLeave: handleMouseLeave,
3945
3982
  className: cn(
3946
- "group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-[14px] font-bold leading-[24px] text-(--cmp-listItem-text-default)",
3983
+ "group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-(--cmp-listItem-text-default)",
3984
+ titleTypo,
3947
3985
  disabled ? "pointer-events-none cursor-not-allowed text-(--sys-color-disabled-text)" : isSelected ? cn(
3948
3986
  "cursor-pointer text-(--cmp-listItem-text-selected)",
3949
3987
  "bg-(--cmp-listItem-bg-selected)"
@@ -3955,10 +3993,10 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3955
3993
  "gap-(--cmp-listItem-gap)",
3956
3994
  "pr-(--cmp-listItem-paddingX)",
3957
3995
  depth === 2 ? "pl-[36px]" : depth === 3 ? "pl-[56px]" : "pl-[16px]",
3958
- bordered ? cn(
3996
+ bordered && cn(
3959
3997
  "overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default)",
3960
3998
  isSelected ? "border-(--cmp-listItem-bordered-border-selected)" : isInteractive && "hover:border-(--cmp-listItem-bordered-border-hover)"
3961
- ) : "border-b border-(--cmp-listItem-border-default)",
3999
+ ),
3962
4000
  className
3963
4001
  ),
3964
4002
  ...rest,
@@ -3984,7 +4022,7 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3984
4022
  "span",
3985
4023
  {
3986
4024
  className: cn(
3987
- "min-w-0 truncate text-[12px] font-normal leading-[20px] text-(--sys-color-fg-secondary)",
4025
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3988
4026
  supportingTextPosition === "right" && "flex-1"
3989
4027
  ),
3990
4028
  children: supportingTextNode
@@ -4037,6 +4075,8 @@ var SExpansionListImpl = /* @__PURE__ */ react.forwardRef(function SExpansionLis
4037
4075
  const depth2 = child.props.depth ?? 1;
4038
4076
  const itemKey2 = String(child.key ?? index);
4039
4077
  return react.cloneElement(child, {
4078
+ // 선택을 리스트가 관리하는 이상 아이템은 클릭 가능해야 한다 — SListItem 의 기본값(false)을 뒤집는다
4079
+ clickable: child.props.clickable ?? true,
4040
4080
  selected: selectedKeys[depth2] === itemKey2,
4041
4081
  onClick: (event) => {
4042
4082
  setSelectedKeys((current) => ({ ...current, [depth2]: itemKey2 }));
@@ -4342,8 +4382,8 @@ function SGhostButton({
4342
4382
  }
4343
4383
  );
4344
4384
  if (!hasTooltip) return btn;
4345
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
4346
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4385
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: setOpen, children: [
4386
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4347
4387
  "span",
4348
4388
  {
4349
4389
  className: cn("inline-flex", anchorClassName),
@@ -4355,8 +4395,8 @@ function SGhostButton({
4355
4395
  children: btn
4356
4396
  }
4357
4397
  ) }),
4358
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4359
- RPopover2__namespace.Content,
4398
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4399
+ RPopover9__namespace.Content,
4360
4400
  {
4361
4401
  side: "top",
4362
4402
  sideOffset: 4,
@@ -4827,7 +4867,7 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4827
4867
  ...restContentProps
4828
4868
  } = contentProps ?? {};
4829
4869
  const contentClass = cn(
4830
- "z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
4870
+ "z-50 w-fit max-w-[var(--radix-popover-content-available-width)] rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
4831
4871
  FLOATING_SLIDE_ANIM,
4832
4872
  className,
4833
4873
  contentPropsClassName
@@ -4839,14 +4879,14 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4839
4879
  ...style,
4840
4880
  ...contentPropsStyle
4841
4881
  };
4842
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpenState, children: [
4882
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: setOpenState, children: [
4843
4883
  isNone ? (
4844
4884
  // 트리거 없음: children 을 래퍼 없이 그대로 앵커로 쓴다 (소비자 레이아웃 보존).
4845
4885
  // mouse/focus 핸들러가 없으므로 useClose 없이도 자동 닫힘이 발생하지 않는다.
4846
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: triggerNode })
4886
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { asChild: true, children: triggerNode })
4847
4887
  ) : isHover ? (
4848
4888
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
4849
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4889
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4850
4890
  "span",
4851
4891
  {
4852
4892
  className: "inline-flex",
@@ -4857,9 +4897,9 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4857
4897
  children: triggerNode
4858
4898
  }
4859
4899
  ) })
4860
- ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: triggerNode }),
4861
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
4862
- RPopover2__namespace.Content,
4900
+ ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: triggerNode }),
4901
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
4902
+ RPopover9__namespace.Content,
4863
4903
  {
4864
4904
  side: placement,
4865
4905
  sideOffset,
@@ -4893,7 +4933,7 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4893
4933
  }
4894
4934
  )
4895
4935
  ] }),
4896
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
4936
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
4897
4937
  ]
4898
4938
  }
4899
4939
  ) })
@@ -5901,6 +5941,15 @@ var STextLink = /* @__PURE__ */ react.forwardRef(function STextLink2({
5901
5941
  /* @__PURE__ */ jsxRuntime.jsx(RTooltip__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(RTooltip__namespace.Content, { side: "top", sideOffset: 4, className: cn("z-50", FLOATING_SLIDE_ANIM), children: /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: "square", size: "xs", color: "grey", label: text }) }) })
5902
5942
  ] }) });
5903
5943
  });
5944
+
5945
+ // src/lib/floating-width.ts
5946
+ var POPOVER_AVAILABLE_WIDTH = "var(--radix-popover-content-available-width)";
5947
+ var DROPDOWN_AVAILABLE_WIDTH = "var(--radix-dropdown-menu-content-available-width)";
5948
+ function clampToAvailableWidth(value, availableWidth) {
5949
+ if (value == null || value === "") return availableWidth;
5950
+ const css = typeof value === "number" ? `${value}px` : value;
5951
+ return `min(${css}, ${availableWidth})`;
5952
+ }
5904
5953
  var SIZE2 = {
5905
5954
  xs: { height: 24, paddingX: 8, gap: 6, arrow: 12, fontSize: 12, minWidth: 64, splitMinWidth: 72 },
5906
5955
  sm: {
@@ -5937,6 +5986,7 @@ function splitDivider(color, outline) {
5937
5986
  };
5938
5987
  return (outline ? outlined : solid)[color];
5939
5988
  }
5989
+ var VIEWPORT_PADDING = 12;
5940
5990
  var itemClass = cn(
5941
5991
  "flex min-h-[28px] cursor-pointer select-none items-center gap-[8px] rounded-[2px] px-[12px] py-[4px] text-[12px] leading-[20px] outline-none",
5942
5992
  "text-[color:var(--sys-color-fg-secondary)]",
@@ -6018,12 +6068,16 @@ var SDropdownButtonImpl = /* @__PURE__ */ react.forwardRef(
6018
6068
  sideOffset: 4,
6019
6069
  align: "start",
6020
6070
  alignOffset: split ? splitAlignOffset : 0,
6071
+ collisionPadding: VIEWPORT_PADDING,
6021
6072
  className: cn(
6022
6073
  "z-50 overflow-hidden rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-normal-sm",
6023
- !split && "min-w-[var(--radix-dropdown-menu-trigger-width)]",
6074
+ // 창보다 넓은 메뉴는 창 폭에 맞춰 줄인다. 트리거 너비를 따르는 하한도 같은 상한으로
6075
+ // 감싸야 한다 — min-width 가 max-width 를 이기기 때문이다.
6076
+ "max-w-[var(--radix-dropdown-menu-content-available-width)]",
6077
+ !split && "min-w-[min(var(--radix-dropdown-menu-trigger-width),var(--radix-dropdown-menu-content-available-width))]",
6024
6078
  FLOATING_SLIDE_ANIM
6025
6079
  ),
6026
- style: split ? { minWidth: splitWidth } : void 0,
6080
+ style: split ? { minWidth: clampToAvailableWidth(splitWidth, DROPDOWN_AVAILABLE_WIDTH) } : void 0,
6027
6081
  children: items.map((it) => /* @__PURE__ */ jsxRuntime.jsxs(
6028
6082
  RDropdown__namespace.Item,
6029
6083
  {
@@ -6035,7 +6089,7 @@ var SDropdownButtonImpl = /* @__PURE__ */ react.forwardRef(
6035
6089
  /* @__PURE__ */ jsxRuntime.jsx(
6036
6090
  "span",
6037
6091
  {
6038
- className: "min-w-0 whitespace-nowrap",
6092
+ className: "min-w-0 break-words",
6039
6093
  dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(it.label) }
6040
6094
  }
6041
6095
  )
@@ -6247,16 +6301,20 @@ function SPopover({
6247
6301
  const descColor = type === "default" ? "var(--cmp-popover-default-description)" : "var(--cmp-popover-light-description)";
6248
6302
  const leftLinkLabelClass = type === "default" ? "text-[color:var(--cmp-popover-default-subAction)]" : "text-[color:var(--cmp-popover-light-subAction)]";
6249
6303
  const triggerNode = trigger ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex cursor-pointer", "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: iconSize, color }) });
6250
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6251
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: triggerNode }),
6252
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6253
- RPopover2__namespace.Content,
6304
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6305
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: triggerNode }),
6306
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6307
+ RPopover9__namespace.Content,
6254
6308
  {
6255
6309
  side: placement,
6256
6310
  sideOffset: 8,
6257
6311
  className: cn(
6258
6312
  // 닫기 버튼이 항상 있으므로 우측은 close clearance(36px) 로 고정
6259
- "relative z-50 flex min-w-[160px] flex-col gap-[var(--cmp-popover-gap)] rounded-[var(--cmp-popover-radius)] py-[var(--cmp-popover-paddingY)] px-[var(--cmp-popover-paddingX)] text-[12px] leading-[20px] shadow-normal-sm",
6313
+ "relative z-50 flex flex-col gap-[var(--cmp-popover-gap)] rounded-[var(--cmp-popover-radius)] py-[var(--cmp-popover-paddingY)] px-[var(--cmp-popover-paddingX)] text-[12px] leading-[20px] shadow-normal-sm",
6314
+ // 창보다 넓어지면 창 폭에 맞춘다. 최소 너비도 같은 상한으로 감싼다 —
6315
+ // min-width 가 max-width 를 이기므로 감싸지 않으면 상한이 무력화된다.
6316
+ "max-w-[var(--radix-popover-content-available-width)]",
6317
+ "min-w-[min(160px,var(--radix-popover-content-available-width))]",
6260
6318
  FLOATING_SLIDE_ANIM,
6261
6319
  menuClassName
6262
6320
  ),
@@ -6312,7 +6370,7 @@ function SPopover({
6312
6370
  ]
6313
6371
  }
6314
6372
  ),
6315
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
6373
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
6316
6374
  ]
6317
6375
  }
6318
6376
  ) })
@@ -6341,8 +6399,9 @@ function SPortal({
6341
6399
  ariaLabel
6342
6400
  }) {
6343
6401
  const portalContainer = usePortalContainer();
6402
+ const { minWidth: styleMinWidth, maxWidth: styleMaxWidth, ...restStyle } = style ?? {};
6344
6403
  return /* @__PURE__ */ jsxRuntime.jsxs(
6345
- RPopover2__namespace.Root,
6404
+ RPopover9__namespace.Root,
6346
6405
  {
6347
6406
  open,
6348
6407
  onOpenChange: (next) => {
@@ -6350,9 +6409,9 @@ function SPortal({
6350
6409
  if (!next) onClose?.();
6351
6410
  },
6352
6411
  children: [
6353
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
6354
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
6355
- RPopover2__namespace.Content,
6412
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { virtualRef: anchorRef }),
6413
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
6414
+ RPopover9__namespace.Content,
6356
6415
  {
6357
6416
  side: placement,
6358
6417
  align,
@@ -6379,11 +6438,18 @@ function SPortal({
6379
6438
  className: cn(
6380
6439
  "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
6381
6440
  maxHeight != null && "overflow-y-auto",
6382
- fit && "min-w-(--radix-popover-trigger-width)",
6441
+ // 앵커가 창보다 넓을 수 있는 자리(가로 스크롤 안의 트리거 등)에서도 하한이 상한을
6442
+ // 이기지 않도록, 앵커 너비 역시 가용 폭으로 가둔다.
6443
+ fit && "min-w-[min(var(--radix-popover-trigger-width),var(--radix-popover-content-available-width))]",
6383
6444
  FLOATING_SLIDE_ANIM,
6384
6445
  className
6385
6446
  ),
6386
- style: { maxHeight, ...style },
6447
+ style: {
6448
+ maxHeight,
6449
+ ...restStyle,
6450
+ ...styleMinWidth != null ? { minWidth: clampToAvailableWidth(styleMinWidth, POPOVER_AVAILABLE_WIDTH) } : {},
6451
+ maxWidth: clampToAvailableWidth(styleMaxWidth, POPOVER_AVAILABLE_WIDTH)
6452
+ },
6387
6453
  children
6388
6454
  }
6389
6455
  ) })
@@ -6543,16 +6609,18 @@ function SGuide({
6543
6609
  );
6544
6610
  if (isNotion)
6545
6611
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style, children: trigger });
6546
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
6547
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: trigger }),
6548
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6549
- RPopover2__namespace.Content,
6612
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: setOpen, children: [
6613
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: trigger }),
6614
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6615
+ RPopover9__namespace.Content,
6550
6616
  {
6551
6617
  side: "bottom",
6552
6618
  align: "start",
6553
6619
  sideOffset: 4,
6554
6620
  className: cn(
6555
6621
  "relative z-50 rounded-[var(--cmp-guide-contents-radius)] bg-[var(--color-white)]",
6622
+ // popupWidth(기본 426px)가 창보다 넓으면 창 폭에 맞춘다 — max-width 가 width 를 이긴다.
6623
+ "max-w-[var(--radix-popover-content-available-width)]",
6556
6624
  FLOATING_SLIDE_ANIM
6557
6625
  ),
6558
6626
  style: {
@@ -7057,6 +7125,8 @@ function SLoadingModal({
7057
7125
  );
7058
7126
  }
7059
7127
  var CLOSE_ANIMATION_DURATION2 = 300;
7128
+ var RESIZE_KEY_STEP = { normal: 10, large: 50 };
7129
+ var RESIZE_HIT_WIDTH = "calc(var(--cmp-splitter-border-width) + var(--cmp-splitter-border-padding) * 2)";
7060
7130
  function toPx(value, percentBase) {
7061
7131
  if (typeof value === "number") return value;
7062
7132
  if (typeof value !== "string") return void 0;
@@ -7094,6 +7164,7 @@ function SDrawer({
7094
7164
  resizable = false,
7095
7165
  minWidth,
7096
7166
  maxWidth,
7167
+ onWidthChange,
7097
7168
  footerLeft,
7098
7169
  button,
7099
7170
  children,
@@ -7106,11 +7177,16 @@ function SDrawer({
7106
7177
  const [portalContainer, setPortalContainer] = react.useState(null);
7107
7178
  const backdropPointerDownRef = react.useRef(false);
7108
7179
  const contentRef = react.useRef(null);
7180
+ const panelRef = react.useRef(null);
7109
7181
  const stackRegistrationRef = react.useRef(null);
7110
7182
  const resizeCleanupRef = react.useRef(null);
7111
- const resizeStateRef = react.useRef(null);
7112
7183
  const [layerIndex, setLayerIndex] = react.useState(null);
7113
7184
  const [currentWidth, setCurrentWidth] = react.useState(width);
7185
+ const [resizing, setResizing] = react.useState(false);
7186
+ const [measuredWidth, setMeasuredWidth] = react.useState(null);
7187
+ const [viewportWidth, setViewportWidth] = react.useState(
7188
+ () => typeof window === "undefined" ? void 0 : window.innerWidth
7189
+ );
7114
7190
  react.useEffect(() => {
7115
7191
  setCurrentWidth(width);
7116
7192
  }, [width]);
@@ -7126,6 +7202,22 @@ function SDrawer({
7126
7202
  react.useEffect(() => {
7127
7203
  return () => resizeCleanupRef.current?.();
7128
7204
  }, []);
7205
+ react.useEffect(() => {
7206
+ if (!rendered) return;
7207
+ const sync = () => setViewportWidth(window.innerWidth);
7208
+ sync();
7209
+ window.addEventListener("resize", sync);
7210
+ return () => window.removeEventListener("resize", sync);
7211
+ }, [rendered]);
7212
+ react.useEffect(() => {
7213
+ const panel = panelRef.current;
7214
+ if (!resizable || !rendered || !panel || typeof ResizeObserver === "undefined") return;
7215
+ const observer = new ResizeObserver(
7216
+ () => setMeasuredWidth(Math.round(panel.getBoundingClientRect().width))
7217
+ );
7218
+ observer.observe(panel);
7219
+ return () => observer.disconnect();
7220
+ }, [resizable, rendered]);
7129
7221
  react.useEffect(() => {
7130
7222
  if (!open) {
7131
7223
  setEntered(false);
@@ -7167,41 +7259,80 @@ function SDrawer({
7167
7259
  }
7168
7260
  onOpenChange?.(false);
7169
7261
  };
7262
+ const resizeBounds = () => {
7263
+ const limit = viewportWidth;
7264
+ if (limit === void 0) return { min: void 0, max: void 0 };
7265
+ const min = toPx(minWidth, limit);
7266
+ const max = toPx(maxWidth, limit);
7267
+ return {
7268
+ min: min === void 0 ? void 0 : Math.min(min, limit),
7269
+ max: max === void 0 ? limit : Math.min(max, limit)
7270
+ };
7271
+ };
7272
+ const commitWidth = (next, startWidth) => {
7273
+ const { min, max } = resizeBounds();
7274
+ const clamped = Math.round(clamp3(next, min, max));
7275
+ setCurrentWidth(clamped);
7276
+ if (clamped !== Math.round(startWidth)) onWidthChange?.(clamped);
7277
+ };
7170
7278
  const handleResizeStart = (event) => {
7279
+ if (typeof document === "undefined") return;
7280
+ const panel = panelRef.current;
7281
+ if (!panel) return;
7171
7282
  event.preventDefault();
7172
7283
  event.stopPropagation();
7173
- const panel = contentRef.current?.querySelector("[data-s-drawer-panel]");
7174
- if (!panel) return;
7175
- resizeStateRef.current = {
7176
- startX: event.clientX,
7177
- startWidth: panel.getBoundingClientRect().width,
7178
- minWidth: toPx(minWidth, window.innerWidth),
7179
- maxWidth: toPx(maxWidth, window.innerWidth)
7180
- };
7284
+ event.currentTarget.focus();
7285
+ const startX = event.clientX;
7286
+ const startWidth = panel.getBoundingClientRect().width;
7287
+ const { min, max } = resizeBounds();
7288
+ let latest = startWidth;
7289
+ const { body } = document;
7290
+ const previousCursor = body.style.cursor;
7291
+ const previousUserSelect = body.style.userSelect;
7181
7292
  const onMove = (moveEvent) => {
7182
- const resizeState = resizeStateRef.current;
7183
- if (!resizeState) return;
7184
- const nextWidth = resizeState.startWidth + resizeState.startX - moveEvent.clientX;
7185
- setCurrentWidth(clamp3(nextWidth, resizeState.minWidth, resizeState.maxWidth));
7293
+ latest = Math.round(clamp3(startWidth + (startX - moveEvent.clientX), min, max));
7294
+ setCurrentWidth(latest);
7186
7295
  };
7187
- resizeCleanupRef.current?.();
7188
- const previousCursor = document.body.style.cursor;
7189
- const previousUserSelect = document.body.style.userSelect;
7190
7296
  const cleanupResize = () => {
7191
- resizeStateRef.current = null;
7192
- document.body.style.cursor = previousCursor;
7193
- document.body.style.userSelect = previousUserSelect;
7297
+ body.style.cursor = previousCursor;
7298
+ body.style.userSelect = previousUserSelect;
7194
7299
  document.removeEventListener("mousemove", onMove);
7195
7300
  document.removeEventListener("mouseup", cleanupResize);
7196
7301
  resizeCleanupRef.current = null;
7302
+ setResizing(false);
7303
+ commitWidth(latest, startWidth);
7197
7304
  };
7305
+ resizeCleanupRef.current?.();
7198
7306
  resizeCleanupRef.current = cleanupResize;
7199
- document.body.style.cursor = "col-resize";
7200
- document.body.style.userSelect = "none";
7307
+ body.style.cursor = "col-resize";
7308
+ body.style.userSelect = "none";
7201
7309
  document.addEventListener("mousemove", onMove);
7202
7310
  document.addEventListener("mouseup", cleanupResize);
7311
+ setResizing(true);
7312
+ };
7313
+ const handleResizeKeyDown = (event) => {
7314
+ const panel = panelRef.current;
7315
+ if (!panel) return;
7316
+ const startWidth = panel.getBoundingClientRect().width;
7317
+ const { min, max } = resizeBounds();
7318
+ const step = RESIZE_KEY_STEP[event.shiftKey ? "large" : "normal"];
7319
+ let next = null;
7320
+ if (event.key === "ArrowLeft") next = startWidth + step;
7321
+ else if (event.key === "ArrowRight") next = startWidth - step;
7322
+ else if (event.key === "Home" && min !== void 0) next = min;
7323
+ else if (event.key === "End" && max !== void 0) next = max;
7324
+ if (next === null) return;
7325
+ event.preventDefault();
7326
+ commitWidth(next, startWidth);
7203
7327
  };
7204
7328
  const modalHost = getModalHost();
7329
+ const resizeValueNow = measuredWidth ?? (typeof currentWidth === "number" ? Math.round(currentWidth) : void 0);
7330
+ const { min: resizeMinPx, max: resizeMaxPx } = resizeBounds();
7331
+ const resizeValueMin = resizeMinPx === void 0 ? void 0 : Math.round(resizeMinPx);
7332
+ const resizeValueMax = resizeMaxPx === void 0 ? void 0 : Math.round(resizeMaxPx);
7333
+ const appliedWidth = typeof currentWidth === "number" && viewportWidth !== void 0 ? Math.min(currentWidth, viewportWidth) : currentWidth;
7334
+ const appliedMinWidth = resizeMinPx ?? minWidth;
7335
+ const appliedMaxWidth = resizeMaxPx ?? maxWidth;
7205
7336
  return /* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(RDialog__namespace.Portal, { container: modalHost, forceMount: rendered ? true : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none absolute inset-0", style: { zIndex: layerIndex ?? 0 }, children: [
7206
7337
  /* @__PURE__ */ jsxRuntime.jsx(
7207
7338
  RDialog__namespace.Overlay,
@@ -7243,6 +7374,7 @@ function SDrawer({
7243
7374
  /* @__PURE__ */ jsxRuntime.jsxs(
7244
7375
  "div",
7245
7376
  {
7377
+ ref: panelRef,
7246
7378
  className: cn(
7247
7379
  "pointer-events-auto relative flex h-full max-w-full translate-x-full flex-col overflow-hidden bg-[var(--cmp-drawer-bg)] shadow-[var(--shadow-overlay)] transition-transform duration-300 ease-out data-[state=open]:translate-x-0 data-[state=closed]:translate-x-full",
7248
7380
  shaking && "animate-drawer-shake",
@@ -7250,20 +7382,45 @@ function SDrawer({
7250
7382
  ),
7251
7383
  "data-s-drawer-panel": true,
7252
7384
  "data-state": open && entered ? "open" : "closed",
7253
- style: { width: currentWidth, minWidth, maxWidth, ...style },
7385
+ style: {
7386
+ width: appliedWidth,
7387
+ minWidth: appliedMinWidth,
7388
+ maxWidth: appliedMaxWidth,
7389
+ ...style
7390
+ },
7254
7391
  onAnimationEnd: (event) => {
7255
7392
  if (event.animationName === "drawer-shake") setShaking(false);
7256
7393
  },
7257
7394
  children: [
7258
- resizable && /* @__PURE__ */ jsxRuntime.jsx(
7259
- "button",
7395
+ resizable && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
7396
+ "div",
7260
7397
  {
7261
- type: "button",
7398
+ role: "separator",
7399
+ "aria-orientation": "vertical",
7262
7400
  "aria-label": "Drawer \uB108\uBE44 \uC870\uC808",
7263
- className: "absolute inset-y-0 left-0 z-[1] w-[8px] -translate-x-1/2 cursor-col-resize touch-none border-0 bg-transparent p-0",
7264
- onMouseDown: handleResizeStart
7401
+ "aria-valuenow": resizeValueNow,
7402
+ "aria-valuemin": resizeValueMin,
7403
+ "aria-valuemax": resizeValueMax,
7404
+ "aria-valuetext": resizeValueNow === void 0 ? void 0 : `${resizeValueNow}px`,
7405
+ tabIndex: 0,
7406
+ onMouseDown: handleResizeStart,
7407
+ onKeyDown: handleResizeKeyDown,
7408
+ className: "group absolute inset-y-0 left-0 z-[1] flex cursor-col-resize touch-none justify-start outline-none",
7409
+ style: { width: RESIZE_HIT_WIDTH },
7410
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7411
+ "span",
7412
+ {
7413
+ "aria-hidden": true,
7414
+ className: cn(
7415
+ // 평상시엔 자리만 잡고 칠하지 않는다 — 색이 붙는 것 자체가 조절 가능하다는 신호다.
7416
+ "w-(--cmp-splitter-border-width) transition-colors",
7417
+ "group-hover:bg-(--cmp-splitter-border-color) group-focus-visible:bg-(--cmp-splitter-border-color)",
7418
+ resizing && "bg-(--cmp-splitter-border-color)"
7419
+ )
7420
+ }
7421
+ )
7265
7422
  }
7266
- ),
7423
+ ) }),
7267
7424
  /* @__PURE__ */ jsxRuntime.jsx(
7268
7425
  SOverlayHeader,
7269
7426
  {
@@ -7936,8 +8093,11 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
7936
8093
  onLauncherClick,
7937
8094
  logo,
7938
8095
  topContent,
8096
+ railTop,
7939
8097
  railFooter,
8098
+ menuTop,
7940
8099
  menuFooter,
8100
+ foldedTop,
7941
8101
  foldedFooter,
7942
8102
  resizable = false,
7943
8103
  menuWidth: menuWidthProp,
@@ -8446,6 +8606,7 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8446
8606
  ] })
8447
8607
  }
8448
8608
  ),
8609
+ railFolded && foldedTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-[24px] flex shrink-0 items-center justify-center", children: foldedTop }),
8449
8610
  /* @__PURE__ */ jsxRuntime.jsxs(
8450
8611
  "div",
8451
8612
  {
@@ -8463,9 +8624,9 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8463
8624
  transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
8464
8625
  },
8465
8626
  children: [
8466
- useRail && // 레일 컬럼 — 스크롤되는 아이템 목록(nav) 하단 고정 footer 를 한 컬럼에 담는다.
8467
- // footer 내비게이션이 아니므로 nav(landmark) 밖에 두고, 컬럼 폭·구분선·배경은 이 래퍼가
8468
- // 전체 높이에 걸쳐 책임진다(그래야 footer 자리까지 배경·오른쪽 선이 이어진다).
8627
+ useRail && // 레일 컬럼 — 스크롤되는 아이템 목록(nav) 가운데 두고 위아래에 고정 슬롯(top/footer)을 붙인다.
8628
+ // 슬롯은 내비게이션이 아니므로 nav(landmark) 밖에 두고, 컬럼 폭·구분선·배경은 이 래퍼가
8629
+ // 전체 높이에 걸쳐 책임진다(그래야 슬롯 자리까지 배경·오른쪽 선이 이어진다).
8469
8630
  /* @__PURE__ */ jsxRuntime.jsxs(
8470
8631
  "div",
8471
8632
  {
@@ -8476,6 +8637,7 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8476
8637
  backgroundColor: C.panelBg
8477
8638
  },
8478
8639
  children: [
8640
+ railTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: railTop }),
8479
8641
  /* @__PURE__ */ jsxRuntime.jsx(
8480
8642
  "nav",
8481
8643
  {
@@ -8503,6 +8665,7 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8503
8665
  borderRight: isFull ? rightBorder : void 0
8504
8666
  },
8505
8667
  children: [
8668
+ menuTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: menuTop }),
8506
8669
  /* @__PURE__ */ jsxRuntime.jsx(
8507
8670
  "nav",
8508
8671
  {
@@ -8530,10 +8693,10 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8530
8693
  }
8531
8694
  ),
8532
8695
  header === "fix" && menuResizeHandle,
8533
- /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8534
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: labelTipAnchorRef }),
8535
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
8536
- RPopover2__namespace.Content,
8696
+ /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8697
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { virtualRef: labelTipAnchorRef }),
8698
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
8699
+ RPopover9__namespace.Content,
8537
8700
  {
8538
8701
  side: "right",
8539
8702
  sideOffset: 4,
@@ -8551,16 +8714,81 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8551
8714
  }
8552
8715
  );
8553
8716
  });
8717
+ var SPageHeader = /* @__PURE__ */ withDisplayName(
8718
+ react.forwardRef(function SPageHeader2({ title, subText, fix = false, onBack, slot, scrolled = false, className, style, ...rest }, ref) {
8719
+ const hasBack = onBack != null;
8720
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8721
+ "header",
8722
+ {
8723
+ ref,
8724
+ className: cn(
8725
+ "relative z-10 box-border flex shrink-0 items-center gap-(--cmp-pageHeader-gap) px-(--cmp-pageHeader-paddingX)",
8726
+ fix ? "h-(--cmp-pageHeader-bar-height) border-b border-(--cmp-pageHeader-bar-border) bg-(--cmp-pageHeader-bar-bg) py-(--cmp-pageHeader-bar-paddingY)" : cn(
8727
+ // 배경은 항상 투명 — 페이지 배경을 따라갈지는 fix 가 정하지 scrolled 가 정하지 않는다.
8728
+ // 스크롤되면 그림자만 더해 콘텐츠 위에 뜬 것처럼 보이게 한다.
8729
+ "bg-transparent pt-(--cmp-pageHeader-plain-paddingTop) pb-(--cmp-pageHeader-plain-paddingBottom)",
8730
+ scrolled && "shadow-floating"
8731
+ ),
8732
+ className
8733
+ ),
8734
+ style,
8735
+ ...rest,
8736
+ children: [
8737
+ hasBack && /* @__PURE__ */ jsxRuntime.jsx(
8738
+ SGhostButton,
8739
+ {
8740
+ icon: "chevronLeft",
8741
+ size: "xs",
8742
+ ariaLabel: "\uB4A4\uB85C\uAC00\uAE30",
8743
+ onClick: onBack,
8744
+ className: "shrink-0"
8745
+ }
8746
+ ),
8747
+ /* @__PURE__ */ jsxRuntime.jsxs(
8748
+ "div",
8749
+ {
8750
+ className: cn(
8751
+ "flex min-w-0 flex-1 items-center",
8752
+ fix ? "flex-row gap-(--cmp-pageHeader-gap)" : "flex-col items-start gap-(--cmp-pageHeader-plain-title-gap)",
8753
+ !hasBack && "pl-(--cmp-pageHeader-title-paddingLeft)"
8754
+ ),
8755
+ children: [
8756
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-heading-lg min-w-0 truncate text-(--cmp-pageHeader-title-text)", children: title }),
8757
+ subText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-md-default min-w-0 truncate text-(--cmp-pageHeader-subtitle-text)", children: subText })
8758
+ ]
8759
+ }
8760
+ ),
8761
+ slot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex shrink-0 items-center", children: slot })
8762
+ ]
8763
+ }
8764
+ );
8765
+ }),
8766
+ "SPageHeader"
8767
+ );
8554
8768
  var MIN_CONTENT_WIDTH = `calc(var(${SLAYOUT_MIN_WIDTH_VAR}, 0px) - 100cqw + 100%)`;
8555
8769
  var BACKGROUND_CLASS = {
8556
8770
  frame: `bg-[var(--sys-color-bg-frame)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`,
8557
8771
  neutral: `bg-[var(--sys-color-bg-neutralLight)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_WHITE_CLASS}`,
8558
8772
  screen: `bg-[var(--sys-color-bg-screen)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`
8559
8773
  };
8560
- var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({ background = "frame", scrollEndSpacing = true, className, style, children, ...rest }, ref) {
8774
+ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({
8775
+ background = "frame",
8776
+ scrollEndSpacing = true,
8777
+ header,
8778
+ className,
8779
+ style,
8780
+ children,
8781
+ onScroll,
8782
+ ...rest
8783
+ }, ref) {
8561
8784
  const layout = react.useContext(SLayoutContext);
8562
8785
  const gridStyle = layout?.header === "full" ? { gridArea: "page" } : void 0;
8563
- return /* @__PURE__ */ jsxRuntime.jsx(
8786
+ const [scrolled, setScrolled] = react.useState(false);
8787
+ const handleScroll = (e) => {
8788
+ onScroll?.(e);
8789
+ if (header != null && !header.fix) setScrolled(e.currentTarget.scrollTop > 0);
8790
+ };
8791
+ const main = /* @__PURE__ */ jsxRuntime.jsx(
8564
8792
  "main",
8565
8793
  {
8566
8794
  ref,
@@ -8571,11 +8799,29 @@ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({ background = "fra
8571
8799
  BACKGROUND_CLASS[background],
8572
8800
  className
8573
8801
  ),
8574
- style: { ...gridStyle, ...style },
8802
+ style: header == null ? { ...gridStyle, ...style } : style,
8803
+ onScroll: handleScroll,
8575
8804
  ...rest,
8576
8805
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { minWidth: MIN_CONTENT_WIDTH }, children })
8577
8806
  }
8578
8807
  );
8808
+ if (header == null) return main;
8809
+ return (
8810
+ // fix 가 아닌 헤더는 자기 배경이 투명이라(SPageHeader.tsx) 이 배경이 그대로 비친다 —
8811
+ // 그래서 SPage 의 background 를 main 뿐 아니라 여기에도 칠한다(스크롤바 관련 클래스는
8812
+ // 스크롤 요소가 아닌 여기서는 그냥 무시된다). fix 헤더는 자체 흰 배경이 덮으므로 무관하다.
8813
+ /* @__PURE__ */ jsxRuntime.jsxs(
8814
+ "div",
8815
+ {
8816
+ className: cn("flex min-h-0 min-w-0 flex-1 flex-col", BACKGROUND_CLASS[background]),
8817
+ style: gridStyle,
8818
+ children: [
8819
+ /* @__PURE__ */ jsxRuntime.jsx(SPageHeader, { ...header, scrolled }),
8820
+ main
8821
+ ]
8822
+ }
8823
+ )
8824
+ );
8579
8825
  });
8580
8826
 
8581
8827
  // src/components/SPage/page.config.ts
@@ -10949,7 +11195,7 @@ var SInput = /* @__PURE__ */ react.forwardRef(function SInput2({
10949
11195
  lineHeight: `${f.lineHeight}px`
10950
11196
  },
10951
11197
  children: [
10952
- prefix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: prefix }),
11198
+ prefix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex flex-shrink-0 items-center", children: prefix }),
10953
11199
  /* @__PURE__ */ jsxRuntime.jsx(
10954
11200
  "input",
10955
11201
  {
@@ -10981,7 +11227,7 @@ var SInput = /* @__PURE__ */ react.forwardRef(function SInput2({
10981
11227
  ...rest
10982
11228
  }
10983
11229
  ),
10984
- suffix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: suffix }),
11230
+ suffix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex flex-shrink-0 items-center", children: suffix }),
10985
11231
  clearable && hasValue && !disabled && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
10986
11232
  SGhostButton,
10987
11233
  {
@@ -11879,8 +12125,8 @@ function SDatePicker({
11879
12125
  disabled,
11880
12126
  className,
11881
12127
  style,
11882
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
11883
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
12128
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
12129
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
11884
12130
  "button",
11885
12131
  {
11886
12132
  type: "button",
@@ -11916,13 +12162,16 @@ function SDatePicker({
11916
12162
  ]
11917
12163
  }
11918
12164
  ) }),
11919
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
11920
- RPopover2__namespace.Content,
12165
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12166
+ RPopover9__namespace.Content,
11921
12167
  {
11922
12168
  side: "bottom",
11923
12169
  align: "start",
11924
12170
  sideOffset: 4,
11925
- className: cn("z-50", FLOATING_SLIDE_ANIM),
12171
+ className: cn(
12172
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
12173
+ FLOATING_SLIDE_ANIM
12174
+ ),
11926
12175
  children: /* @__PURE__ */ jsxRuntime.jsx(
11927
12176
  SCalendar,
11928
12177
  {
@@ -12070,78 +12319,81 @@ function RangeCalendar({
12070
12319
  }
12071
12320
  };
12072
12321
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
12073
- const renderPanel = (gy, gm, isLeft) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12074
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12075
- /* @__PURE__ */ jsxRuntime.jsx(
12076
- SGhostButton,
12077
- {
12078
- icon: "chevronLeft",
12079
- size: "xxs",
12080
- ariaLabel: "prevMonth",
12081
- onClick: goPrevMonth,
12082
- className: cn(!isLeft && "invisible pointer-events-none")
12083
- }
12084
- ),
12085
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-[40px] flex-1 items-center justify-center text-center text-[14px] font-medium leading-[24px]", children: [
12086
- gy,
12087
- ".",
12088
- String(gm).padStart(2, "0")
12322
+ const renderPanel = (gy, gm, isLeft) => (
12323
+ // 좁은 창에서 눌려 격자가 깨지지 않도록, 줄이는 대신 부모가 가로 스크롤하게 둔다.
12324
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12325
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12326
+ /* @__PURE__ */ jsxRuntime.jsx(
12327
+ SGhostButton,
12328
+ {
12329
+ icon: "chevronLeft",
12330
+ size: "xxs",
12331
+ ariaLabel: "prevMonth",
12332
+ onClick: goPrevMonth,
12333
+ className: cn(!isLeft && "invisible pointer-events-none")
12334
+ }
12335
+ ),
12336
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-[40px] flex-1 items-center justify-center text-center text-[14px] font-medium leading-[24px]", children: [
12337
+ gy,
12338
+ ".",
12339
+ String(gm).padStart(2, "0")
12340
+ ] }),
12341
+ /* @__PURE__ */ jsxRuntime.jsx(
12342
+ SGhostButton,
12343
+ {
12344
+ icon: "chevronRight",
12345
+ size: "xxs",
12346
+ ariaLabel: "nextMonth",
12347
+ onClick: goNextMonth,
12348
+ className: cn(isLeft && "invisible pointer-events-none")
12349
+ }
12350
+ )
12089
12351
  ] }),
12090
- /* @__PURE__ */ jsxRuntime.jsx(
12091
- SGhostButton,
12092
- {
12093
- icon: "chevronRight",
12094
- size: "xxs",
12095
- ariaLabel: "nextMonth",
12096
- onClick: goNextMonth,
12097
- className: cn(isLeft && "invisible pointer-events-none")
12098
- }
12099
- )
12100
- ] }),
12101
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: GRID_COLS, children: WEEK_LABELS.map((l) => /* @__PURE__ */ jsxRuntime.jsx(
12102
- "span",
12103
- {
12104
- className: "flex h-[20px] items-center justify-center text-[12px] font-normal text-[color:var(--cmp-datepicker-calendar-week-color)]",
12105
- children: l
12106
- },
12107
- l
12108
- )) }),
12109
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(GRID_COLS, "gap-y-[var(--cmp-datepicker-calendar-grid-rowGap)]"), children: monthCells(gy, gm).map((cell) => {
12110
- const isEdge = cell.inCurrentMonth && (cell.date === rStart || complete && cell.date === rEnd);
12111
- const between = cell.inCurrentMonth && inRange(cell.date);
12112
- const disabled = cell.inCurrentMonth && isDisabled(cell.date);
12113
- const isToday = cell.inCurrentMonth && today === cell.date;
12114
- let cellBg;
12115
- if (between) cellBg = RANGE_BG;
12116
- else if (cell.inCurrentMonth && hasRange && cell.date === rStart)
12117
- cellBg = `linear-gradient(to right, transparent 50%, ${RANGE_BG} 50%)`;
12118
- else if (cell.inCurrentMonth && hasRange && cell.date === rEnd) cellBg = RANGE_BG;
12119
- return /* @__PURE__ */ jsxRuntime.jsx(
12120
- "div",
12352
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: GRID_COLS, children: WEEK_LABELS.map((l) => /* @__PURE__ */ jsxRuntime.jsx(
12353
+ "span",
12121
12354
  {
12122
- className: "flex h-[var(--cmp-datepicker-calendar-range-height)] w-[var(--cmp-datepicker-calendar-day-width)] items-center justify-center",
12123
- style: { background: cellBg },
12124
- children: /* @__PURE__ */ jsxRuntime.jsx(
12125
- "button",
12126
- {
12127
- type: "button",
12128
- disabled: !cell.inCurrentMonth || disabled,
12129
- onMouseEnter: () => setHover(cell.date),
12130
- onClick: () => cell.inCurrentMonth && click(cell.date),
12131
- className: cn(
12132
- "relative z-[1] flex h-[var(--cmp-datepicker-calendar-day-circle-size)] w-[var(--cmp-datepicker-calendar-day-circle-size)] items-center justify-center rounded-[var(--cmp-datepicker-calendar-day-circle-radius)] border border-solid border-transparent bg-transparent text-[14px]",
12133
- !cell.inCurrentMonth ? "invisible pointer-events-none" : disabled ? "cursor-not-allowed" : "cursor-pointer",
12134
- isEdge ? "bg-[var(--cmp-datepicker-calendar-day-select-bg)] text-[color:var(--cmp-datepicker-calendar-day-select-text)]" : disabled ? "text-[color:var(--cmp-datepicker-calendar-day-disabled-text)]" : "text-[color:var(--cmp-datepicker-calendar-day-default-text)] hover:border-[color:var(--cmp-datepicker-calendar-day-hover-border)] hover:text-[color:var(--cmp-datepicker-calendar-day-hover-text)]",
12135
- isToday && !disabled && "font-bold"
12136
- ),
12137
- children: cell.inCurrentMonth ? cell.day : ""
12138
- }
12139
- )
12355
+ className: "flex h-[20px] items-center justify-center text-[12px] font-normal text-[color:var(--cmp-datepicker-calendar-week-color)]",
12356
+ children: l
12140
12357
  },
12141
- cell.date
12142
- );
12143
- }) })
12144
- ] });
12358
+ l
12359
+ )) }),
12360
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(GRID_COLS, "gap-y-[var(--cmp-datepicker-calendar-grid-rowGap)]"), children: monthCells(gy, gm).map((cell) => {
12361
+ const isEdge = cell.inCurrentMonth && (cell.date === rStart || complete && cell.date === rEnd);
12362
+ const between = cell.inCurrentMonth && inRange(cell.date);
12363
+ const disabled = cell.inCurrentMonth && isDisabled(cell.date);
12364
+ const isToday = cell.inCurrentMonth && today === cell.date;
12365
+ let cellBg;
12366
+ if (between) cellBg = RANGE_BG;
12367
+ else if (cell.inCurrentMonth && hasRange && cell.date === rStart)
12368
+ cellBg = `linear-gradient(to right, transparent 50%, ${RANGE_BG} 50%)`;
12369
+ else if (cell.inCurrentMonth && hasRange && cell.date === rEnd) cellBg = RANGE_BG;
12370
+ return /* @__PURE__ */ jsxRuntime.jsx(
12371
+ "div",
12372
+ {
12373
+ className: "flex h-[var(--cmp-datepicker-calendar-range-height)] w-[var(--cmp-datepicker-calendar-day-width)] items-center justify-center",
12374
+ style: { background: cellBg },
12375
+ children: /* @__PURE__ */ jsxRuntime.jsx(
12376
+ "button",
12377
+ {
12378
+ type: "button",
12379
+ disabled: !cell.inCurrentMonth || disabled,
12380
+ onMouseEnter: () => setHover(cell.date),
12381
+ onClick: () => cell.inCurrentMonth && click(cell.date),
12382
+ className: cn(
12383
+ "relative z-[1] flex h-[var(--cmp-datepicker-calendar-day-circle-size)] w-[var(--cmp-datepicker-calendar-day-circle-size)] items-center justify-center rounded-[var(--cmp-datepicker-calendar-day-circle-radius)] border border-solid border-transparent bg-transparent text-[14px]",
12384
+ !cell.inCurrentMonth ? "invisible pointer-events-none" : disabled ? "cursor-not-allowed" : "cursor-pointer",
12385
+ isEdge ? "bg-[var(--cmp-datepicker-calendar-day-select-bg)] text-[color:var(--cmp-datepicker-calendar-day-select-text)]" : disabled ? "text-[color:var(--cmp-datepicker-calendar-day-disabled-text)]" : "text-[color:var(--cmp-datepicker-calendar-day-default-text)] hover:border-[color:var(--cmp-datepicker-calendar-day-hover-border)] hover:text-[color:var(--cmp-datepicker-calendar-day-hover-text)]",
12386
+ isToday && !disabled && "font-bold"
12387
+ ),
12388
+ children: cell.inCurrentMonth ? cell.day : ""
12389
+ }
12390
+ )
12391
+ },
12392
+ cell.date
12393
+ );
12394
+ }) })
12395
+ ] })
12396
+ );
12145
12397
  const renderTimeSection = (which, date, suffix) => {
12146
12398
  const active = !!date;
12147
12399
  const [yy = "", mm = "", dd = ""] = date ? date.split("-") : [];
@@ -12209,8 +12461,11 @@ function RangeCalendar({
12209
12461
  "div",
12210
12462
  {
12211
12463
  className: cn(
12212
- "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",
12213
- useTimePicker ? "h-auto" : "h-[364px]"
12464
+ "inline-flex max-w-full 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",
12465
+ // 높이는 고정이 아니라 하한이다 — 5주/6주 달을 오갈 때의 높이 흔들림만 막으면 되고,
12466
+ // 창이 좁아 패널이 가로 스크롤될 때는 스크롤바 높이만큼 늘어날 수 있어야 한다.
12467
+ // (단일 달 SCalendar 도 min-h 를 쓴다)
12468
+ useTimePicker ? "h-auto" : "min-h-[364px]"
12214
12469
  ),
12215
12470
  children: [
12216
12471
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
@@ -12229,11 +12484,20 @@ function RangeCalendar({
12229
12484
  }
12230
12485
  )
12231
12486
  ] }),
12232
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-range-panelGap)]", children: [
12233
- renderPanel(y, m, true),
12234
- /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true }),
12235
- renderPanel(nextMonthYM.year, nextMonthYM.month, false)
12236
- ] }),
12487
+ /* @__PURE__ */ jsxRuntime.jsxs(
12488
+ "div",
12489
+ {
12490
+ className: cn(
12491
+ "flex max-w-full gap-[var(--cmp-datepicker-calendar-range-panelGap)] overflow-x-auto",
12492
+ SCROLLBAR_CLASS
12493
+ ),
12494
+ children: [
12495
+ renderPanel(y, m, true),
12496
+ /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true }),
12497
+ renderPanel(nextMonthYM.year, nextMonthYM.month, false)
12498
+ ]
12499
+ }
12500
+ ),
12237
12501
  useTimePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
12238
12502
  /* @__PURE__ */ jsxRuntime.jsx(SDivider, {}),
12239
12503
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-time-gap)]", children: [
@@ -12292,7 +12556,25 @@ function SDateRangePicker({
12292
12556
  });
12293
12557
  const resolvedError = error || ruleError !== "";
12294
12558
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12295
- const dims = size === "md" ? { padX: 16, gap: 12, icon: 20, minW: 104, fontSize: 14, lineHeight: 24, radius: 6 } : { padX: 12, gap: 8, icon: 16, minW: 80, fontSize: 12, lineHeight: 20, radius: 4 };
12559
+ const dims = size === "md" ? {
12560
+ padX: 16,
12561
+ gap: 12,
12562
+ icon: 20,
12563
+ minW: 104,
12564
+ fieldMinW: 260,
12565
+ fontSize: 14,
12566
+ lineHeight: 24,
12567
+ radius: 6
12568
+ } : {
12569
+ padX: 12,
12570
+ gap: 8,
12571
+ icon: 16,
12572
+ minW: 80,
12573
+ fieldMinW: 216,
12574
+ fontSize: 12,
12575
+ lineHeight: 20,
12576
+ radius: 4
12577
+ };
12296
12578
  const select = (r) => {
12297
12579
  setPendingStart(null);
12298
12580
  onValueChange?.(r);
@@ -12318,11 +12600,12 @@ function SDateRangePicker({
12318
12600
  focused: open,
12319
12601
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12320
12602
  width,
12603
+ minWidth: dims.fieldMinW,
12321
12604
  disabled,
12322
12605
  className,
12323
12606
  style,
12324
12607
  children: /* @__PURE__ */ jsxRuntime.jsxs(
12325
- RPopover2__namespace.Root,
12608
+ RPopover9__namespace.Root,
12326
12609
  {
12327
12610
  open,
12328
12611
  onOpenChange: (o) => {
@@ -12331,7 +12614,7 @@ function SDateRangePicker({
12331
12614
  if (!o) setPendingStart(null);
12332
12615
  },
12333
12616
  children: [
12334
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
12617
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
12335
12618
  "button",
12336
12619
  {
12337
12620
  type: "button",
@@ -12372,13 +12655,16 @@ function SDateRangePicker({
12372
12655
  ]
12373
12656
  }
12374
12657
  ) }),
12375
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12376
- RPopover2__namespace.Content,
12658
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12659
+ RPopover9__namespace.Content,
12377
12660
  {
12378
12661
  side: "bottom",
12379
12662
  align: "start",
12380
12663
  sideOffset: 4,
12381
- className: cn("z-50", FLOATING_SLIDE_ANIM),
12664
+ className: cn(
12665
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
12666
+ FLOATING_SLIDE_ANIM
12667
+ ),
12382
12668
  children: /* @__PURE__ */ jsxRuntime.jsx(
12383
12669
  RangeCalendar,
12384
12670
  {
@@ -13366,7 +13652,7 @@ function SRadioButton({
13366
13652
  disabled: od,
13367
13653
  onClick: () => !od && !selected && onValueChange?.(o.value),
13368
13654
  className: cn(
13369
- "relative box-border inline-flex items-center justify-center whitespace-nowrap border border-solid text-[12px] font-medium leading-[20px] transition-colors",
13655
+ "relative box-border grow inline-flex items-center justify-center whitespace-nowrap border border-solid text-[12px] font-medium leading-[20px] transition-colors",
13370
13656
  i === 0 ? "rounded-l-[var(--cmp-radioButton-radius)]" : "-ml-px",
13371
13657
  i === options.length - 1 ? "rounded-r-[var(--cmp-radioButton-radius)]" : "",
13372
13658
  selected ? "z-10" : "",
@@ -13388,53 +13674,1277 @@ function SRadioButton({
13388
13674
  }
13389
13675
  );
13390
13676
  }
13391
- var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
13392
- values = [],
13677
+ var CHIPFILTER_LISTBOX_WIDTH = 240;
13678
+ var CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH = 246;
13679
+ var CHIPFILTER = {
13680
+ barBg: "var(--cmp-chipFilter-bar-bg)",
13681
+ barBorder: "var(--cmp-chipFilter-bar-border)",
13682
+ barPaddingX: "var(--cmp-chipFilter-bar-paddingX)",
13683
+ barPaddingY: "var(--cmp-chipFilter-bar-paddingY)",
13684
+ barGap: "var(--cmp-chipFilter-bar-gap)",
13685
+ dividerHeight: "var(--cmp-chipFilter-divider-height)",
13686
+ chipHeight: "var(--cmp-chipFilter-chip-height)",
13687
+ chipRadius: "var(--cmp-chipFilter-chip-radius)",
13688
+ chipGap: "var(--cmp-chipFilter-chip-gap)",
13689
+ chipDefaultBg: "var(--cmp-chipFilter-chip-default-bg)",
13690
+ chipDefaultBorder: "var(--cmp-chipFilter-chip-default-border)",
13691
+ chipDefaultText: "var(--cmp-chipFilter-chip-default-text)",
13692
+ chipActiveBorder: "var(--cmp-chipFilter-chip-active-border)",
13693
+ chipWarningBorder: "var(--cmp-chipFilter-chip-warning-border)",
13694
+ chipAppliedBg: "var(--cmp-chipFilter-chip-applied-bg)",
13695
+ chipAppliedBorder: "var(--cmp-chipFilter-chip-applied-border)",
13696
+ chipAppliedLabel: "var(--cmp-chipFilter-chip-applied-text-label)",
13697
+ chipAppliedValue: "var(--color-brilliantblue-80)",
13698
+ chipDisabledBg: "var(--cmp-chipFilter-chip-disabled-bg)",
13699
+ chipDisabledBorder: "var(--cmp-chipFilter-chip-disabled-border)",
13700
+ chipDisabledText: "var(--cmp-chipFilter-chip-disabled-text)",
13701
+ addBorder: "var(--cmp-chipFilter-addButton-border-default)",
13702
+ addBorderDisabled: "var(--cmp-chipFilter-addButton-border-disabled)",
13703
+ addText: "var(--cmp-chipFilter-addButton-text-default)",
13704
+ addTextDisabled: "var(--cmp-chipFilter-addButton-text-disabled)",
13705
+ listboxBg: "var(--cmp-chipFilter-listbox-bg)",
13706
+ listboxBorder: "var(--cmp-chipFilter-listbox-border-color)",
13707
+ listboxRadius: "var(--cmp-chipFilter-listbox-radius)",
13708
+ listboxLabel: "var(--cmp-chipFilter-listbox-top-label-color)",
13709
+ listboxBottomDividerHeight: "var(--cmp-chipFilter-listbox-bottom-divider-height)",
13710
+ itemPaddingLeft: "var(--cmp-chipFilter-item-paddingLeft)",
13711
+ itemPaddingRight: "var(--cmp-chipFilter-item-paddingRight)",
13712
+ itemPaddingY: "var(--cmp-chipFilter-item-paddingY)",
13713
+ itemMultiPaddingX: "var(--cmp-chipFilter-item-multi-paddingX)",
13714
+ itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
13715
+ itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
13716
+ };
13717
+ var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
13718
+ var keywordEntries = (value) => {
13719
+ if (isKeywordValue(value)) return value.keywords;
13720
+ if (Array.isArray(value)) {
13721
+ return value.filter((v) => typeof v === "string");
13722
+ }
13723
+ return [];
13724
+ };
13725
+ var isEmptyValue = (value) => {
13726
+ if (value == null) return true;
13727
+ if (Array.isArray(value)) return value.length === 0 || value.every((v) => v == null || v === "");
13728
+ if (isKeywordValue(value)) return value.keywords.length === 0;
13729
+ return value === "";
13730
+ };
13731
+ var normalizeValues = (value) => value ?? {};
13732
+ var toArrayValue = (value) => Array.isArray(value) ? value.filter(
13733
+ (v) => typeof v === "string" || typeof v === "number"
13734
+ ) : [];
13735
+ var todayIso = () => {
13736
+ const d = /* @__PURE__ */ new Date();
13737
+ const pad = (n) => String(n).padStart(2, "0");
13738
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
13739
+ };
13740
+ var isInlineDateField = (field) => field.type === "date" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
13741
+ var resolveFieldDefault = (field) => {
13742
+ if (field.defaultValue !== void 0) return field.defaultValue;
13743
+ if (isInlineDateField(field)) return todayIso();
13744
+ if (!field.required) return void 0;
13745
+ if (field.type === "single") return field.options?.[0]?.value;
13746
+ if (field.type === "date") return todayIso();
13747
+ return void 0;
13748
+ };
13749
+ var valuesEqual = (a, b) => {
13750
+ if (isKeywordValue(a) || isKeywordValue(b)) {
13751
+ return isKeywordValue(a) && isKeywordValue(b) && a.mode === b.mode && valuesEqual(a.keywords, b.keywords);
13752
+ }
13753
+ if (Array.isArray(a) || Array.isArray(b)) {
13754
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => v === b[i]);
13755
+ }
13756
+ return a === b;
13757
+ };
13758
+ var valuesMapEqual = (a, b) => {
13759
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
13760
+ for (const key of keys) {
13761
+ if (!valuesEqual(a[key], b[key])) return false;
13762
+ }
13763
+ return true;
13764
+ };
13765
+ var isAtDefaultValue = (field, value) => {
13766
+ const def = resolveFieldDefault(field);
13767
+ return def !== void 0 && valuesEqual(value, def);
13768
+ };
13769
+ var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
13770
+ var isGroupedFields = (fields) => fields.length > 0 && "fields" in fields[0];
13771
+ var isGroupSatisfied = (group, values) => {
13772
+ if (!group.rule) return true;
13773
+ const keys = group.fields.map((f) => f.key);
13774
+ if (group.rule.type === "requireKey") return !isEmptyValue(values[group.rule.key]);
13775
+ if (group.rule.type === "requireAll") return keys.every((k) => !isEmptyValue(values[k]));
13776
+ if (group.rule.type === "requireAny") return keys.some((k) => !isEmptyValue(values[k]));
13777
+ return true;
13778
+ };
13779
+ var koreanParticle = (word, consonant, vowel) => {
13780
+ const code = word.charCodeAt(word.length - 1);
13781
+ if (code < 44032 || code > 55203) return consonant;
13782
+ return (code - 44032) % 28 === 0 ? vowel : consonant;
13783
+ };
13784
+ var defaultGroupTooltipMessage = (rule, fields) => {
13785
+ if (rule.type === "requireAll") return "\uD56D\uBAA9\uC744 \uBAA8\uB450 \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.";
13786
+ if (rule.type === "requireAny") {
13787
+ const name = rule.dataGroupName;
13788
+ if (!name) return "\uCD5C\uC18C 1\uAC1C\uC758 \uD544\uD130\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694.";
13789
+ return `\uCD5C\uC18C 1\uAC1C\uC758 ${name}${koreanParticle(name, "\uC744", "\uB97C")} \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`;
13790
+ }
13791
+ const label = fields.find((f) => f.key === rule.key)?.label ?? "\uD56D\uBAA9";
13792
+ return `${label}${koreanParticle(label, "\uC744", "\uB97C")} \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.`;
13793
+ };
13794
+ var matchPresetId = (presets, value) => {
13795
+ if (isEmptyValue(value)) return null;
13796
+ for (const preset of presets) {
13797
+ if (preset.custom || !preset.resolve) continue;
13798
+ const resolved = preset.resolve();
13799
+ if (Array.isArray(resolved) && Array.isArray(value)) {
13800
+ if (resolved[0] === value[0] && resolved[1] === value[1]) return preset.value;
13801
+ } else if (!Array.isArray(resolved) && !Array.isArray(value) && resolved === value) {
13802
+ return preset.value;
13803
+ }
13804
+ }
13805
+ return presets.find((preset) => preset.custom)?.value ?? null;
13806
+ };
13807
+ var optionLabel = (field, value) => field.options?.find((option) => option.value === value)?.label ?? String(value);
13808
+ var formatValueList = (labels) => {
13809
+ if (labels.length <= 1) return labels[0] ?? "";
13810
+ return `${labels[0]} \uC678 ${labels.length - 1}\uAC1C`;
13811
+ };
13812
+ var displayValue = (field, value) => {
13813
+ if (isEmptyValue(value)) return "";
13814
+ if (field.type === "multi")
13815
+ return formatValueList(toArrayValue(value).map((v) => optionLabel(field, v)));
13816
+ if (field.type === "single") return optionLabel(field, value);
13817
+ if (field.type === "date")
13818
+ return Array.isArray(value) ? `${value[0]} ~ ${value[1]}` : String(value);
13819
+ if (field.type === "keyword") return formatValueList(keywordEntries(value));
13820
+ return String(value);
13821
+ };
13822
+ var chipDisplayValue = (field, value) => {
13823
+ const text = displayValue(field, value);
13824
+ if (field.type !== "date" || isEmptyValue(value)) return text;
13825
+ const presets = field.presets ?? [];
13826
+ if (presets.length === 0) return text;
13827
+ const preset = presets.find((p) => p.value === matchPresetId(presets, value));
13828
+ return preset && !preset.custom ? `${preset.label}(${text})` : text;
13829
+ };
13830
+ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
13831
+ fields: fieldsProp = [],
13832
+ value,
13393
13833
  onValueChange,
13394
- onInput,
13395
- onFocus,
13396
- onBlur,
13397
- errors = [],
13398
- disabledChips = [],
13399
- size = "sm",
13834
+ onFilterChange,
13835
+ onSearch,
13836
+ activeKeys,
13837
+ onActiveKeysChange,
13838
+ onReset,
13839
+ onAddFilter,
13840
+ label = "\uAC80\uC0C9 \uD544\uD130",
13841
+ showLabel = false,
13842
+ showAddButton = true,
13843
+ showReset = true,
13400
13844
  disabled = false,
13401
- placeholder = "\uD0DC\uADF8 \uC785\uB825 (Enter\uB85C \uB4F1\uB85D / \uCF64\uB9C8\uB85C \uAD6C\uBD84 / \uB744\uC5B4\uC4F0\uAE30 \uBD88\uAC00)",
13402
- name,
13403
- rules,
13404
- error,
13405
- useReset = false,
13406
- maxCount,
13407
- metaPlacement = "end",
13408
- duplicateLabel,
13409
- suggestions = [],
13410
- loadingSuggestions = false,
13411
- recommendedItems = [],
13412
- loadingRecommendedItems = false,
13413
- dropdownMinWidth = 200,
13414
- label,
13415
- labelWidth,
13416
- hint,
13417
- errorMessage,
13418
- width,
13419
- status,
13420
- icon,
13421
- iconColor,
13422
- labelTooltip,
13423
- labelTooltipProps,
13424
13845
  className,
13425
13846
  style
13426
13847
  }, ref) {
13427
- const autoId = react.useId();
13428
- const resolvedName = name ?? autoId;
13429
- const [inputValue, setInputValue] = react.useState("");
13430
- const [focused, setFocused] = react.useState(false);
13431
- const [hovered, setHovered] = react.useState(false);
13432
- const [activeIndex, setActiveIndex] = react.useState(-1);
13433
- const [suppressSuggestions, setSuppressSuggestions] = react.useState(false);
13434
- const [suppressRecommendations, setSuppressRecommendations] = react.useState(false);
13435
- const inputRef = react.useRef(null);
13436
- const containerRef = react.useRef(null);
13437
- const inputShellRef = react.useRef(null);
13848
+ const usingGroupedFields = isGroupedFields(fieldsProp);
13849
+ const groups = react.useMemo(
13850
+ () => usingGroupedFields ? fieldsProp : [{ fields: fieldsProp }],
13851
+ [usingGroupedFields, fieldsProp]
13852
+ );
13853
+ const fields = react.useMemo(() => groups.flatMap((group) => group.fields), [groups]);
13854
+ const values = normalizeValues(value);
13855
+ const portalContainer = usePortalContainer();
13856
+ const [internalActiveKeys, setInternalActiveKeys] = react.useState(
13857
+ () => fields.filter((field) => field.defaultActive !== false).map((field) => field.key)
13858
+ );
13859
+ const [openKey, setOpenKey] = react.useState(null);
13860
+ const [addOpen, setAddOpen] = react.useState(false);
13861
+ const triggerRefs = react.useRef(/* @__PURE__ */ new Map());
13862
+ const initializedDefaultsRef = react.useRef(/* @__PURE__ */ new Set());
13863
+ const prevOpenKeyRef = react.useRef(null);
13864
+ const valuesRef = react.useRef(values);
13865
+ valuesRef.current = values;
13866
+ const lastSearchedValuesRef = react.useRef(values);
13867
+ const invalidGroups = react.useMemo(() => {
13868
+ const invalid = /* @__PURE__ */ new Set();
13869
+ groups.forEach((group, groupIndex) => {
13870
+ if (!isGroupSatisfied(group, values)) invalid.add(groupIndex);
13871
+ });
13872
+ return invalid;
13873
+ }, [groups, values]);
13874
+ const isGroupsValid = react.useCallback(
13875
+ (current) => groups.every((group) => isGroupSatisfied(group, current)),
13876
+ [groups]
13877
+ );
13878
+ const runSearch = react.useCallback(
13879
+ (next) => {
13880
+ if (valuesMapEqual(lastSearchedValuesRef.current, next)) return;
13881
+ if (!isGroupsValid(next)) return;
13882
+ lastSearchedValuesRef.current = next;
13883
+ onSearch?.(next);
13884
+ },
13885
+ [isGroupsValid, onSearch]
13886
+ );
13887
+ react.useEffect(() => {
13888
+ const prevKey = prevOpenKeyRef.current;
13889
+ if (prevKey != null && prevKey !== openKey) {
13890
+ runSearch(valuesRef.current);
13891
+ }
13892
+ prevOpenKeyRef.current = openKey;
13893
+ }, [openKey, runSearch]);
13894
+ react.useEffect(() => {
13895
+ if (activeKeys) return;
13896
+ setInternalActiveKeys((current) => {
13897
+ const known = new Set(fields.map((field) => field.key));
13898
+ const kept = current.filter((key) => known.has(key));
13899
+ const additions = fields.filter((field) => field.defaultActive !== false && !kept.includes(field.key)).map((field) => field.key);
13900
+ return [...kept, ...additions];
13901
+ });
13902
+ }, [activeKeys, fields]);
13903
+ react.useEffect(() => {
13904
+ const patch = {};
13905
+ let changed = false;
13906
+ fields.forEach((field) => {
13907
+ if (initializedDefaultsRef.current.has(field.key)) return;
13908
+ if (!isEmptyValue(values[field.key])) {
13909
+ initializedDefaultsRef.current.add(field.key);
13910
+ return;
13911
+ }
13912
+ const def = resolveFieldDefault(field);
13913
+ if (def === void 0) return;
13914
+ patch[field.key] = def;
13915
+ initializedDefaultsRef.current.add(field.key);
13916
+ changed = true;
13917
+ });
13918
+ if (changed) onValueChange?.({ ...values, ...patch });
13919
+ }, [fields, values, onValueChange]);
13920
+ const resolvedActiveKeys = activeKeys ?? internalActiveKeys;
13921
+ const activeFields = fields.filter((field) => resolvedActiveKeys.includes(field.key));
13922
+ const inactiveFields = fields.filter((field) => !resolvedActiveKeys.includes(field.key));
13923
+ const groupIndexByKey = /* @__PURE__ */ new Map();
13924
+ groups.forEach((group, groupIndex) => {
13925
+ group.fields.forEach((field) => groupIndexByKey.set(field.key, groupIndex));
13926
+ });
13927
+ const hasValue = fields.some((field) => !isEmptyValue(values[field.key]));
13928
+ const hasRightActions = showAddButton || showReset;
13929
+ const currentOpenField = fields.find((field) => field.key === openKey);
13930
+ const editorPopoverStyle = react.useMemo(() => {
13931
+ if (currentOpenField?.type === "keyword") {
13932
+ return { width: CHIPFILTER_LISTBOX_WIDTH };
13933
+ }
13934
+ if (currentOpenField?.type === "date" && (currentOpenField.presets?.length ?? 0) > 0) {
13935
+ return { width: CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH };
13936
+ }
13937
+ return { width: CHIPFILTER_LISTBOX_WIDTH };
13938
+ }, [currentOpenField?.type, currentOpenField?.presets?.length]);
13939
+ const setActive = (next) => {
13940
+ if (!activeKeys) setInternalActiveKeys(next);
13941
+ onActiveKeysChange?.(next);
13942
+ };
13943
+ const emitValue = (key, nextValue) => {
13944
+ const next = { ...values, [key]: nextValue };
13945
+ onValueChange?.(next);
13946
+ onFilterChange?.({ key, value: nextValue, values: next });
13947
+ return next;
13948
+ };
13949
+ const emitValueAndSearch = (key, nextValue) => {
13950
+ runSearch(emitValue(key, nextValue));
13951
+ };
13952
+ const clearValue = (key) => {
13953
+ const field = fields.find((f) => f.key === key);
13954
+ const def = field ? resolveFieldDefault(field) : void 0;
13955
+ runSearch(emitValue(key, def !== void 0 ? def : null));
13956
+ };
13957
+ const reset = react.useCallback(() => {
13958
+ const next = fields.reduce((acc, field) => {
13959
+ const def = resolveFieldDefault(field);
13960
+ acc[field.key] = def !== void 0 ? def : null;
13961
+ return acc;
13962
+ }, {});
13963
+ onValueChange?.(next);
13964
+ runSearch(next);
13965
+ onReset?.();
13966
+ }, [fields, onReset, onValueChange, runSearch]);
13967
+ const open = react.useCallback(
13968
+ (key) => {
13969
+ const field = fields.find((f) => f.key === key);
13970
+ if (!field || isInlineField(field)) return;
13971
+ setOpenKey(key);
13972
+ },
13973
+ [fields]
13974
+ );
13975
+ const validate = react.useCallback(() => invalidGroups.size === 0, [invalidGroups]);
13976
+ react.useImperativeHandle(ref, () => ({ open, reset, validate }), [open, reset, validate]);
13977
+ const addField = (key) => {
13978
+ const next = [...resolvedActiveKeys, key];
13979
+ setActive(next);
13980
+ onAddFilter?.(key);
13981
+ setAddOpen(false);
13982
+ const field = fields.find((f) => f.key === key);
13983
+ if (field && !isInlineField(field)) requestAnimationFrame(() => setOpenKey(key));
13984
+ };
13985
+ const rootStyle = react.useMemo(
13986
+ () => ({
13987
+ "--chipfilter-bar-bg": CHIPFILTER.barBg,
13988
+ "--chipfilter-bar-border": CHIPFILTER.barBorder,
13989
+ "--chipfilter-bar-padding-x": CHIPFILTER.barPaddingX,
13990
+ "--chipfilter-bar-padding-y": CHIPFILTER.barPaddingY,
13991
+ "--chipfilter-bar-gap": CHIPFILTER.barGap,
13992
+ "--chipfilter-divider-height": CHIPFILTER.dividerHeight,
13993
+ ...style
13994
+ }),
13995
+ [style]
13996
+ );
13997
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13998
+ "div",
13999
+ {
14000
+ className: cn(
14001
+ "flex w-full items-start gap-[var(--chipfilter-bar-gap)] border-b border-solid bg-[var(--chipfilter-bar-bg)] px-[var(--chipfilter-bar-padding-x)] py-[var(--chipfilter-bar-padding-y)]",
14002
+ className
14003
+ ),
14004
+ style: { ...rootStyle, borderColor: "var(--chipfilter-bar-border)" },
14005
+ children: [
14006
+ showLabel && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14007
+ /* @__PURE__ */ jsxRuntime.jsx(STag, { className: "shrink-0", shape: "square", size: "sm", color: "darkblue", label }),
14008
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, { top: true })
14009
+ ] }),
14010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children: [
14011
+ (() => {
14012
+ const nodes = [];
14013
+ let runBuffer = [];
14014
+ let runGroupIndex = null;
14015
+ const flushRun = () => {
14016
+ if (runBuffer.length === 0) return;
14017
+ const group = runGroupIndex != null ? groups[runGroupIndex] : void 0;
14018
+ const message = usingGroupedFields && group?.rule ? group.tooltipMessage ?? defaultGroupTooltipMessage(group.rule, group.fields) : void 0;
14019
+ if (message) {
14020
+ nodes.push(
14021
+ /* @__PURE__ */ jsxRuntime.jsx(
14022
+ GroupTooltip,
14023
+ {
14024
+ message,
14025
+ open: invalidGroups.has(runGroupIndex),
14026
+ children: runBuffer
14027
+ },
14028
+ `group-${runGroupIndex}`
14029
+ )
14030
+ );
14031
+ } else {
14032
+ nodes.push(...runBuffer);
14033
+ }
14034
+ runBuffer = [];
14035
+ };
14036
+ activeFields.forEach((field, index) => {
14037
+ const inline = isInlineField(field);
14038
+ const groupIndex = groupIndexByKey.get(field.key) ?? 0;
14039
+ const warning = field.warning || invalidGroups.has(groupIndex);
14040
+ const prevField = index > 0 ? activeFields[index - 1] : null;
14041
+ const prevGroupIndex = prevField ? groupIndexByKey.get(prevField.key) ?? 0 : null;
14042
+ const needsLeadingDivider = index === 0 ? false : inline || prevField != null && isInlineField(prevField) || groupIndex !== prevGroupIndex;
14043
+ if (groupIndex !== runGroupIndex) {
14044
+ flushRun();
14045
+ runGroupIndex = groupIndex;
14046
+ }
14047
+ const showClear = !isAtDefaultValue(field, values[field.key]);
14048
+ const node = isInlineDateField(field) ? /* @__PURE__ */ jsxRuntime.jsx(
14049
+ InlineDatePresetFilter,
14050
+ {
14051
+ field,
14052
+ value: values[field.key],
14053
+ disabled: disabled || field.disabled,
14054
+ warning,
14055
+ showClear,
14056
+ onValueChange: (next) => emitValueAndSearch(field.key, next),
14057
+ onClear: () => clearValue(field.key)
14058
+ },
14059
+ field.key
14060
+ ) : field.type === "custom" ? /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: field.render?.({
14061
+ value: values[field.key],
14062
+ disabled: disabled || field.disabled,
14063
+ warning,
14064
+ onValueChange: (next) => emitValueAndSearch(field.key, next)
14065
+ }) }, field.key) : /* @__PURE__ */ jsxRuntime.jsx(
14066
+ FilterChip,
14067
+ {
14068
+ field,
14069
+ valueText: chipDisplayValue(field, values[field.key]),
14070
+ active: openKey === field.key,
14071
+ applied: !isEmptyValue(values[field.key]),
14072
+ disabled: disabled || field.disabled,
14073
+ warning,
14074
+ showClear,
14075
+ refCallback: (node2) => triggerRefs.current.set(field.key, node2),
14076
+ onOpen: () => setOpenKey(openKey === field.key ? null : field.key),
14077
+ onClear: () => clearValue(field.key)
14078
+ },
14079
+ field.key
14080
+ );
14081
+ runBuffer.push(
14082
+ needsLeadingDivider ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
14083
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
14084
+ node
14085
+ ] }, `${field.key}-wrap`) : node
14086
+ );
14087
+ });
14088
+ flushRun();
14089
+ return nodes;
14090
+ })(),
14091
+ (() => {
14092
+ const lastField = activeFields[activeFields.length - 1];
14093
+ const needsTrailingDivider = lastField != null && isInlineField(lastField) && !hasRightActions;
14094
+ return needsTrailingDivider ? /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }) : null;
14095
+ })(),
14096
+ hasRightActions && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14097
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
14098
+ showAddButton && /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14099
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14100
+ "button",
14101
+ {
14102
+ type: "button",
14103
+ disabled: disabled || inactiveFields.length === 0,
14104
+ className: "inline-flex shrink-0 cursor-pointer items-center justify-center gap-[var(--cmp-chipFilter-addButton-gap)] whitespace-nowrap border border-solid bg-[var(--cmp-chipFilter-addButton-bg)] px-[var(--cmp-chipFilter-addButton-paddingX)] text-[12px] font-medium leading-[20px] outline-none disabled:cursor-not-allowed",
14105
+ style: {
14106
+ minHeight: "var(--cmp-chipFilter-addButton-height)" ,
14107
+ borderRadius: "var(--cmp-chipFilter-addButton-radius)" ,
14108
+ borderColor: disabled || inactiveFields.length === 0 ? CHIPFILTER.addBorderDisabled : CHIPFILTER.addBorder,
14109
+ color: disabled || inactiveFields.length === 0 ? CHIPFILTER.addTextDisabled : CHIPFILTER.addText
14110
+ },
14111
+ children: [
14112
+ /* @__PURE__ */ jsxRuntime.jsx(
14113
+ SIcon,
14114
+ {
14115
+ name: "add",
14116
+ size: 12,
14117
+ color: disabled || inactiveFields.length === 0 ? CHIPFILTER.addTextDisabled : CHIPFILTER.addText
14118
+ }
14119
+ ),
14120
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\uD544\uD130 \uCD94\uAC00" })
14121
+ ]
14122
+ }
14123
+ ) }),
14124
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14125
+ RPopover9__namespace.Content,
14126
+ {
14127
+ side: "bottom",
14128
+ align: "start",
14129
+ sideOffset: 4,
14130
+ className: cn("z-50 min-w-[180px]", FLOATING_SLIDE_ANIM),
14131
+ children: /* @__PURE__ */ jsxRuntime.jsx(ListBox, { children: inactiveFields.length > 0 ? inactiveFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(MenuItem, { onClick: () => addField(field.key), children: field.label }, field.key)) : /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uCD94\uAC00\uD560 \uD544\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })
14132
+ }
14133
+ ) })
14134
+ ] }),
14135
+ showReset && /* @__PURE__ */ jsxRuntime.jsxs(
14136
+ "button",
14137
+ {
14138
+ type: "button",
14139
+ disabled: disabled || !hasValue,
14140
+ onClick: reset,
14141
+ className: cn(
14142
+ "inline-flex shrink-0 cursor-pointer items-center gap-[4px] border-none bg-transparent p-0 text-[12px] font-medium leading-[20px] disabled:cursor-not-allowed",
14143
+ // 부모 flex gap(6px)은 칩 사이 간격 기준이라, 필터 추가 버튼과의 12px 간격을 맞추려면
14144
+ // 부족한 만큼(12px - gap)을 추가로 띄운다. 필터 추가가 없을 땐 divider와 이미 12px이라 불필요.
14145
+ showAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14146
+ ),
14147
+ style: {
14148
+ color: disabled || !hasValue ? "var(--cmp-textLink-content-disabled)" : "var(--cmp-textLink-content-default)"
14149
+ },
14150
+ children: [
14151
+ /* @__PURE__ */ jsxRuntime.jsx(
14152
+ SIcon,
14153
+ {
14154
+ name: "reset",
14155
+ size: 16,
14156
+ color: disabled || !hasValue ? "var(--cmp-textLink-icon-disabled)" : "var(--cmp-textLink-icon-default)"
14157
+ }
14158
+ ),
14159
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\uAC80\uC0C9 \uCD08\uAE30\uD654" })
14160
+ ]
14161
+ }
14162
+ )
14163
+ ] })
14164
+ ] }),
14165
+ /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: openKey != null, onOpenChange: (next) => !next && setOpenKey(null), children: [
14166
+ /* @__PURE__ */ jsxRuntime.jsx(
14167
+ RPopover9__namespace.Anchor,
14168
+ {
14169
+ virtualRef: {
14170
+ current: openKey ? triggerRefs.current.get(openKey) : null
14171
+ }
14172
+ }
14173
+ ),
14174
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14175
+ RPopover9__namespace.Content,
14176
+ {
14177
+ side: "bottom",
14178
+ align: "start",
14179
+ sideOffset: 4,
14180
+ onOpenAutoFocus: (event) => event.preventDefault(),
14181
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
14182
+ style: editorPopoverStyle,
14183
+ children: currentOpenField && /* @__PURE__ */ jsxRuntime.jsx(
14184
+ EditorPanel,
14185
+ {
14186
+ field: currentOpenField,
14187
+ value: values[currentOpenField.key],
14188
+ disabled: disabled || currentOpenField.disabled,
14189
+ onValueChange: (next) => emitValue(currentOpenField.key, next),
14190
+ onClose: () => setOpenKey(null)
14191
+ }
14192
+ )
14193
+ }
14194
+ ) })
14195
+ ] })
14196
+ ]
14197
+ }
14198
+ );
14199
+ });
14200
+ function Divider({ inset, top } = {}) {
14201
+ return /* @__PURE__ */ jsxRuntime.jsx(
14202
+ "span",
14203
+ {
14204
+ "aria-hidden": true,
14205
+ className: cn(
14206
+ "inline-flex w-px shrink-0 bg-[var(--chipfilter-bar-border)]",
14207
+ top ? "mt-[2px] self-start" : "my-auto",
14208
+ inset && "mx-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14209
+ ),
14210
+ style: { height: "var(--chipfilter-divider-height)" }
14211
+ }
14212
+ );
14213
+ }
14214
+ function GroupTooltip({
14215
+ message,
14216
+ open,
14217
+ children
14218
+ }) {
14219
+ return /* @__PURE__ */ jsxRuntime.jsx(STooltip2, { trigger: "none", open, placement: "top", tooltipType: "warning", message: [message], children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children }) });
14220
+ }
14221
+ function FilterChip({
14222
+ field,
14223
+ valueText,
14224
+ active,
14225
+ applied,
14226
+ disabled,
14227
+ warning,
14228
+ showClear,
14229
+ refCallback,
14230
+ onOpen,
14231
+ onClear
14232
+ }) {
14233
+ const border = disabled ? CHIPFILTER.chipDisabledBorder : warning ? CHIPFILTER.chipWarningBorder : applied ? active ? "var(--color-blue-strong)" : CHIPFILTER.chipAppliedBorder : active ? CHIPFILTER.chipActiveBorder : CHIPFILTER.chipDefaultBorder;
14234
+ const background = disabled ? CHIPFILTER.chipDisabledBg : applied ? CHIPFILTER.chipAppliedBg : CHIPFILTER.chipDefaultBg;
14235
+ const textColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipDefaultText;
14236
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14237
+ "button",
14238
+ {
14239
+ ref: refCallback,
14240
+ type: "button",
14241
+ disabled,
14242
+ "aria-expanded": active,
14243
+ onPointerDown: (event) => event.stopPropagation(),
14244
+ onClick: onOpen,
14245
+ className: "group/chip-filter inline-flex max-w-full shrink-0 cursor-pointer items-center justify-center whitespace-nowrap border border-solid px-[var(--cmp-chipFilter-chip-paddingX)] text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
14246
+ style: {
14247
+ minHeight: CHIPFILTER.chipHeight,
14248
+ gap: CHIPFILTER.chipGap,
14249
+ borderRadius: CHIPFILTER.chipRadius,
14250
+ borderColor: border,
14251
+ background,
14252
+ color: textColor
14253
+ },
14254
+ children: [
14255
+ field.required && /* @__PURE__ */ jsxRuntime.jsx(
14256
+ SIcon,
14257
+ {
14258
+ name: "asterisk",
14259
+ size: 12,
14260
+ color: disabled ? CHIPFILTER.chipDisabledText : "var(--cmp-tag-darkblue-content)"
14261
+ }
14262
+ ),
14263
+ applied ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-[4px] truncate", children: [
14264
+ /* @__PURE__ */ jsxRuntime.jsx(
14265
+ "span",
14266
+ {
14267
+ className: "font-bold",
14268
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel },
14269
+ children: field.label
14270
+ }
14271
+ ),
14272
+ /* @__PURE__ */ jsxRuntime.jsx(
14273
+ "span",
14274
+ {
14275
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel },
14276
+ children: ":"
14277
+ }
14278
+ ),
14279
+ /* @__PURE__ */ jsxRuntime.jsx(
14280
+ "span",
14281
+ {
14282
+ className: "min-w-0 truncate font-bold",
14283
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedValue },
14284
+ children: valueText
14285
+ }
14286
+ )
14287
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14288
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("min-w-0 truncate", active && "font-bold"), children: field.label }),
14289
+ /* @__PURE__ */ jsxRuntime.jsx(
14290
+ SIcon,
14291
+ {
14292
+ name: "caretDown",
14293
+ size: 12,
14294
+ color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipDefaultText
14295
+ }
14296
+ )
14297
+ ] }),
14298
+ applied && !disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
14299
+ "span",
14300
+ {
14301
+ role: "button",
14302
+ tabIndex: 0,
14303
+ "aria-label": `${field.label} \uD544\uD130 \uC9C0\uC6B0\uAE30`,
14304
+ className: "inline-flex size-[16px] items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)]",
14305
+ onClick: (event) => {
14306
+ event.preventDefault();
14307
+ event.stopPropagation();
14308
+ onClear();
14309
+ },
14310
+ onKeyDown: (event) => {
14311
+ if (event.key !== "Enter" && event.key !== " ") return;
14312
+ event.preventDefault();
14313
+ event.stopPropagation();
14314
+ onClear();
14315
+ },
14316
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14317
+ }
14318
+ )
14319
+ ]
14320
+ }
14321
+ );
14322
+ }
14323
+ function EditorPanel({
14324
+ field,
14325
+ value,
14326
+ disabled,
14327
+ onValueChange,
14328
+ onClose
14329
+ }) {
14330
+ const wrapsInPaddedTop = field.type === "date";
14331
+ const isMulti = field.type === "multi";
14332
+ const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
14333
+ const selectedValues = isMulti ? toArrayValue(value) : [];
14334
+ const allSelected = isMulti && selectableValues.length > 0 && selectableValues.every((v) => selectedValues.includes(v));
14335
+ const toggleSelectAll = () => {
14336
+ onValueChange(
14337
+ allSelected ? selectedValues.filter((v) => !selectableValues.includes(v)) : [...selectedValues, ...selectableValues.filter((v) => !selectedValues.includes(v))]
14338
+ );
14339
+ };
14340
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListBox, { children: [
14341
+ wrapsInPaddedTop ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: /* @__PURE__ */ jsxRuntime.jsx(
14342
+ FilterEditor,
14343
+ {
14344
+ field,
14345
+ value,
14346
+ disabled,
14347
+ onValueChange
14348
+ }
14349
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
14350
+ FilterEditor,
14351
+ {
14352
+ field,
14353
+ value,
14354
+ disabled,
14355
+ onValueChange
14356
+ }
14357
+ ),
14358
+ /* @__PURE__ */ jsxRuntime.jsxs(
14359
+ "div",
14360
+ {
14361
+ className: "flex items-center justify-end gap-[var(--cmp-chipFilter-listbox-bottom-gap)] border-t border-solid px-[var(--cmp-chipFilter-listbox-bottom-paddingX)] py-[var(--cmp-chipFilter-listbox-bottom-paddingY)]",
14362
+ style: { borderColor: CHIPFILTER.listboxBorder },
14363
+ children: [
14364
+ isMulti && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14365
+ /* @__PURE__ */ jsxRuntime.jsx(
14366
+ STextLink,
14367
+ {
14368
+ size: "sm",
14369
+ label: allSelected ? "\uC120\uD0DD \uD574\uC81C" : "\uC804\uCCB4 \uC120\uD0DD",
14370
+ labelClass: "font-bold",
14371
+ disabled,
14372
+ onClick: toggleSelectAll,
14373
+ style: { color: "var(--sys-color-fg-secondary)" }
14374
+ }
14375
+ ),
14376
+ /* @__PURE__ */ jsxRuntime.jsx(
14377
+ "span",
14378
+ {
14379
+ "aria-hidden": true,
14380
+ className: "inline-flex w-px shrink-0",
14381
+ style: {
14382
+ height: CHIPFILTER.listboxBottomDividerHeight,
14383
+ background: CHIPFILTER.listboxBorder
14384
+ }
14385
+ }
14386
+ )
14387
+ ] }),
14388
+ /* @__PURE__ */ jsxRuntime.jsx(
14389
+ STextLink,
14390
+ {
14391
+ size: "sm",
14392
+ label: "\uAC80\uC0C9",
14393
+ labelClass: "font-bold",
14394
+ onClick: onClose,
14395
+ style: { color: CHIPFILTER.chipAppliedValue }
14396
+ }
14397
+ )
14398
+ ]
14399
+ }
14400
+ )
14401
+ ] });
14402
+ }
14403
+ function FilterEditor({
14404
+ field,
14405
+ value,
14406
+ disabled,
14407
+ onValueChange
14408
+ }) {
14409
+ if (field.type === "date") {
14410
+ return /* @__PURE__ */ jsxRuntime.jsx(
14411
+ DatePresetEditor,
14412
+ {
14413
+ field,
14414
+ value,
14415
+ disabled,
14416
+ onValueChange
14417
+ }
14418
+ );
14419
+ }
14420
+ if (field.type === "keyword") {
14421
+ return /* @__PURE__ */ jsxRuntime.jsx(
14422
+ KeywordEditor,
14423
+ {
14424
+ field,
14425
+ value: isKeywordValue(value) ? value : keywordEntries(value),
14426
+ disabled,
14427
+ onValueChange
14428
+ }
14429
+ );
14430
+ }
14431
+ return /* @__PURE__ */ jsxRuntime.jsx(
14432
+ OptionList,
14433
+ {
14434
+ field,
14435
+ value: field.type === "multi" ? toArrayValue(value) : value,
14436
+ multiple: field.type === "multi",
14437
+ disabled,
14438
+ onValueChange
14439
+ }
14440
+ );
14441
+ }
14442
+ function DatePresetEditor({
14443
+ field,
14444
+ value,
14445
+ disabled,
14446
+ onValueChange
14447
+ }) {
14448
+ const presets = field.presets ?? [];
14449
+ const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value));
14450
+ const syncedInitialValueRef = react.useRef(!isEmptyValue(value));
14451
+ react.useEffect(() => {
14452
+ if (syncedInitialValueRef.current || isEmptyValue(value)) return;
14453
+ syncedInitialValueRef.current = true;
14454
+ setSelectedId(matchPresetId(presets, value));
14455
+ }, [value, presets]);
14456
+ const selectedPreset = presets.find((preset) => preset.value === selectedId);
14457
+ const selectPreset = (preset) => {
14458
+ setSelectedId(preset.value);
14459
+ if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : null);
14460
+ };
14461
+ if (presets.length === 0) {
14462
+ return /* @__PURE__ */ jsxRuntime.jsx(
14463
+ SDatePicker,
14464
+ {
14465
+ value: typeof value === "string" ? value : null,
14466
+ onValueChange,
14467
+ selectable: field.selectable,
14468
+ disabled,
14469
+ placeholder: field.placeholder
14470
+ }
14471
+ );
14472
+ }
14473
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)]", children: [
14474
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[8px]", children: presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
14475
+ SRadio,
14476
+ {
14477
+ val: preset.value,
14478
+ value: selectedId,
14479
+ label: preset.label,
14480
+ disabled,
14481
+ onValueChange: () => selectPreset(preset)
14482
+ },
14483
+ preset.value
14484
+ )) }),
14485
+ selectedPreset?.custom && /* @__PURE__ */ jsxRuntime.jsx(
14486
+ SDateRangePicker,
14487
+ {
14488
+ value: Array.isArray(value) && value.length === 2 ? value : null,
14489
+ onValueChange,
14490
+ selectable: field.selectable,
14491
+ maxRange: field.maxRange,
14492
+ disabled
14493
+ }
14494
+ )
14495
+ ] });
14496
+ }
14497
+ function InlineDatePresetFilter({
14498
+ field,
14499
+ value,
14500
+ disabled,
14501
+ warning,
14502
+ showClear,
14503
+ onValueChange,
14504
+ onClear
14505
+ }) {
14506
+ const presets = (field.presets ?? []).filter((preset) => !preset.custom);
14507
+ const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value));
14508
+ const syncedInitialValueRef = react.useRef(!isEmptyValue(value));
14509
+ react.useEffect(() => {
14510
+ if (syncedInitialValueRef.current || isEmptyValue(value)) return;
14511
+ syncedInitialValueRef.current = true;
14512
+ setSelectedId(matchPresetId(presets, value));
14513
+ }, [value, presets]);
14514
+ const applied = !isEmptyValue(value);
14515
+ const rangeValue = Array.isArray(value) ? value : typeof value === "string" && value !== "" ? [value, value] : null;
14516
+ const [pickerOpen, setPickerOpen] = react.useState(false);
14517
+ const portalContainer = usePortalContainer();
14518
+ const selectPreset = (id) => {
14519
+ const preset = presets.find((p) => p.value === id);
14520
+ if (!preset) return;
14521
+ setSelectedId(preset.value);
14522
+ onValueChange(preset.resolve ? preset.resolve() : null);
14523
+ };
14524
+ const badge = applied && /* @__PURE__ */ jsxRuntime.jsxs(
14525
+ "span",
14526
+ {
14527
+ className: "inline-flex shrink-0 items-center gap-[4px] whitespace-nowrap border border-solid px-[var(--cmp-chipFilter-chip-paddingX)] text-[12px] leading-[20px]",
14528
+ style: {
14529
+ minHeight: CHIPFILTER.chipHeight,
14530
+ borderRadius: CHIPFILTER.chipRadius,
14531
+ borderColor: warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder,
14532
+ background: CHIPFILTER.chipAppliedBg,
14533
+ color: CHIPFILTER.chipAppliedLabel,
14534
+ cursor: rangeValue ? "pointer" : void 0
14535
+ },
14536
+ children: [
14537
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold", children: [
14538
+ field.label,
14539
+ ":"
14540
+ ] }),
14541
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", style: { color: CHIPFILTER.chipAppliedValue }, children: displayValue(field, value) }),
14542
+ !disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
14543
+ "span",
14544
+ {
14545
+ role: "button",
14546
+ tabIndex: 0,
14547
+ "aria-label": `${field.label} \uD544\uD130 \uC9C0\uC6B0\uAE30`,
14548
+ className: "inline-flex size-[16px] items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)]",
14549
+ onClick: (event) => {
14550
+ event.stopPropagation();
14551
+ onClear();
14552
+ },
14553
+ onKeyDown: (event) => {
14554
+ if (event.key !== "Enter" && event.key !== " ") return;
14555
+ event.preventDefault();
14556
+ event.stopPropagation();
14557
+ onClear();
14558
+ },
14559
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14560
+ }
14561
+ )
14562
+ ]
14563
+ }
14564
+ );
14565
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-[8px]", children: [
14566
+ /* @__PURE__ */ jsxRuntime.jsx(
14567
+ SRadioButton,
14568
+ {
14569
+ size: "xs",
14570
+ options: presets.map((preset) => ({ value: preset.value, label: preset.label })),
14571
+ value: selectedId,
14572
+ disabled,
14573
+ onValueChange: (next) => selectPreset(String(next))
14574
+ }
14575
+ ),
14576
+ applied && (rangeValue ? (
14577
+ // 뱃지 모양은 그대로 두고, 눌렀을 때 캘린더가 열려 기간을 직접 다시 고를 수 있게 한다.
14578
+ // 직접 고르면 프리셋 선택은 해제된다.
14579
+ /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
14580
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
14581
+ "button",
14582
+ {
14583
+ type: "button",
14584
+ disabled,
14585
+ className: "border-none bg-transparent p-0 outline-none disabled:cursor-not-allowed",
14586
+ children: badge
14587
+ }
14588
+ ) }),
14589
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14590
+ RPopover9__namespace.Content,
14591
+ {
14592
+ side: "bottom",
14593
+ align: "start",
14594
+ sideOffset: 4,
14595
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
14596
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14597
+ RangeCalendar,
14598
+ {
14599
+ value: rangeValue,
14600
+ selectable: field.selectable,
14601
+ maxRange: field.maxRange,
14602
+ onSelect: (range) => {
14603
+ setSelectedId(null);
14604
+ onValueChange(range);
14605
+ setPickerOpen(false);
14606
+ }
14607
+ }
14608
+ )
14609
+ }
14610
+ ) })
14611
+ ] })
14612
+ ) : badge)
14613
+ ] });
14614
+ }
14615
+ var MATCH_MODE_OPTIONS = [
14616
+ { value: "contains", label: "\uD3EC\uD568" },
14617
+ { value: "exact", label: "\uC77C\uCE58" }
14618
+ ];
14619
+ var EXCLUDE_MATCH_MODE_OPTION = {
14620
+ value: "excludes",
14621
+ label: "\uBBF8\uD3EC\uD568"
14622
+ };
14623
+ function KeywordEditor({
14624
+ field,
14625
+ value,
14626
+ disabled,
14627
+ onValueChange
14628
+ }) {
14629
+ const entries3 = keywordEntries(value);
14630
+ const [draft, setDraft] = react.useState("");
14631
+ const [mode, setMode] = react.useState(
14632
+ isKeywordValue(value) ? value.mode : "contains"
14633
+ );
14634
+ const emit3 = (nextEntries) => {
14635
+ onValueChange(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
14636
+ };
14637
+ const addEntry = (raw) => {
14638
+ const next = raw.trim();
14639
+ if (next === "" || entries3.includes(next)) return;
14640
+ emit3([...entries3, next]);
14641
+ setDraft("");
14642
+ };
14643
+ const removeEntry = (entry) => {
14644
+ emit3(entries3.filter((e) => e !== entry));
14645
+ };
14646
+ const onKeyDown = (event) => {
14647
+ if (event.key === "Enter") {
14648
+ if (event.nativeEvent.isComposing) return;
14649
+ event.preventDefault();
14650
+ addEntry(draft);
14651
+ }
14652
+ };
14653
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14654
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: [
14655
+ field.matchModes && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
14656
+ /* @__PURE__ */ jsxRuntime.jsx(
14657
+ "span",
14658
+ {
14659
+ className: "shrink-0 whitespace-nowrap text-[12px] font-medium leading-[20px]",
14660
+ style: { color: CHIPFILTER.listboxLabel },
14661
+ children: "\uAC80\uC0C9 \uC870\uAC74 :"
14662
+ }
14663
+ ),
14664
+ /* @__PURE__ */ jsxRuntime.jsx(
14665
+ SRadioButton,
14666
+ {
14667
+ size: "xs",
14668
+ style: { width: 150 },
14669
+ options: field.excludeMode ? [...MATCH_MODE_OPTIONS, EXCLUDE_MATCH_MODE_OPTION] : MATCH_MODE_OPTIONS,
14670
+ value: mode,
14671
+ disabled,
14672
+ onValueChange: (next) => setMode(next)
14673
+ }
14674
+ )
14675
+ ] }),
14676
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--sys-color-field-border-default)] bg-[var(--sys-color-field-bg-default)] px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
14677
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
14678
+ /* @__PURE__ */ jsxRuntime.jsx(
14679
+ "input",
14680
+ {
14681
+ value: draft,
14682
+ disabled,
14683
+ placeholder: field.placeholder ?? "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00",
14684
+ onChange: (event) => setDraft(event.target.value),
14685
+ onKeyDown,
14686
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--sys-color-field-text-default)] outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed"
14687
+ }
14688
+ )
14689
+ ] })
14690
+ ] }),
14691
+ entries3.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
14692
+ "div",
14693
+ {
14694
+ className: "max-h-[220px] overflow-auto border-t border-solid py-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14695
+ style: { borderColor: CHIPFILTER.listboxBorder },
14696
+ children: entries3.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
14697
+ KeywordEntryRow,
14698
+ {
14699
+ label: optionLabel(field, entry),
14700
+ disabled,
14701
+ onRemove: () => removeEntry(entry)
14702
+ },
14703
+ entry
14704
+ ))
14705
+ }
14706
+ )
14707
+ ] });
14708
+ }
14709
+ function KeywordEntryRow({
14710
+ label,
14711
+ disabled,
14712
+ onRemove
14713
+ }) {
14714
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14715
+ "div",
14716
+ {
14717
+ className: "group flex w-full items-center justify-between gap-[8px] text-[12px] leading-[20px]",
14718
+ style: {
14719
+ paddingTop: CHIPFILTER.itemPaddingY,
14720
+ paddingBottom: CHIPFILTER.itemPaddingY,
14721
+ paddingLeft: CHIPFILTER.itemPaddingLeft,
14722
+ paddingRight: CHIPFILTER.itemPaddingRight,
14723
+ color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.itemTextDefault
14724
+ },
14725
+ children: [
14726
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("min-w-0 truncate", !disabled && "group-hover:font-bold"), children: label }),
14727
+ !disabled && /* @__PURE__ */ jsxRuntime.jsx(
14728
+ "button",
14729
+ {
14730
+ type: "button",
14731
+ "aria-label": `${label} \uC0AD\uC81C`,
14732
+ className: "invisible inline-flex size-[16px] shrink-0 cursor-pointer items-center justify-center group-hover:visible",
14733
+ onClick: onRemove,
14734
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14735
+ }
14736
+ )
14737
+ ]
14738
+ }
14739
+ );
14740
+ }
14741
+ var OPTION_SEARCH_THRESHOLD = 11;
14742
+ function OptionList({
14743
+ field,
14744
+ value,
14745
+ multiple,
14746
+ disabled,
14747
+ onValueChange
14748
+ }) {
14749
+ const selected = multiple ? toArrayValue(value) : [];
14750
+ const singleValue = !multiple && (typeof value === "string" || typeof value === "number") ? value : null;
14751
+ const [search, setSearch] = react.useState("");
14752
+ const options = field.options ?? [];
14753
+ const showSearch = options.length >= OPTION_SEARCH_THRESHOLD;
14754
+ const filteredOptions = react.useMemo(() => {
14755
+ if (!showSearch || search.trim() === "") return options;
14756
+ const query = search.trim().toLowerCase();
14757
+ return options.filter((option) => option.label.toLowerCase().includes(query));
14758
+ }, [options, search, showSearch]);
14759
+ if (options.length === 0) return /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
14760
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14761
+ "div",
14762
+ {
14763
+ className: cn(
14764
+ "max-h-[240px] overflow-auto",
14765
+ !showSearch && "pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14766
+ "pb-[var(--cmp-chipFilter-listbox-list-paddingY)]"
14767
+ ),
14768
+ children: [
14769
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx(
14770
+ "div",
14771
+ {
14772
+ className: "sticky top-0 z-[1] border-b border-solid px-[8px] pb-[4px] pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14773
+ style: { background: CHIPFILTER.listboxBg, borderColor: CHIPFILTER.listboxBorder },
14774
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--sys-color-field-border-default)] bg-[var(--sys-color-field-bg-default)] px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
14775
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
14776
+ /* @__PURE__ */ jsxRuntime.jsx(
14777
+ "input",
14778
+ {
14779
+ value: search,
14780
+ disabled,
14781
+ placeholder: "\uAC80\uC0C9",
14782
+ onChange: (event) => setSearch(event.target.value),
14783
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--sys-color-field-text-default)] outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed"
14784
+ }
14785
+ ),
14786
+ search !== "" && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
14787
+ SGhostButton,
14788
+ {
14789
+ icon: "close",
14790
+ size: "xxs",
14791
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
14792
+ className: "flex-shrink-0",
14793
+ onClick: () => setSearch("")
14794
+ }
14795
+ )
14796
+ ] })
14797
+ }
14798
+ ),
14799
+ showSearch && filteredOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
14800
+ filteredOptions.map((option) => {
14801
+ const checked = multiple ? selected.includes(option.value) : singleValue === option.value;
14802
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14803
+ MenuItem,
14804
+ {
14805
+ selected: checked,
14806
+ disabled: disabled || option.disabled,
14807
+ multi: multiple,
14808
+ onClick: () => {
14809
+ if (multiple) {
14810
+ const next = checked ? selected.filter((v) => v !== option.value) : [...selected, option.value];
14811
+ onValueChange(next);
14812
+ } else {
14813
+ onValueChange(option.value);
14814
+ }
14815
+ },
14816
+ children: [
14817
+ multiple && /* @__PURE__ */ jsxRuntime.jsx(OptionCheckbox, { checked }),
14818
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: option.label })
14819
+ ]
14820
+ },
14821
+ String(option.value)
14822
+ );
14823
+ })
14824
+ ]
14825
+ }
14826
+ );
14827
+ }
14828
+ function OptionCheckbox({ checked }) {
14829
+ return /* @__PURE__ */ jsxRuntime.jsx(
14830
+ "span",
14831
+ {
14832
+ className: cn(
14833
+ "inline-flex size-[16px] shrink-0 items-center justify-center rounded-[2px] border border-solid",
14834
+ checked ? "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
14835
+ ),
14836
+ children: checked && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "checkboxCheck", size: 12, color: "var(--cmp-checkbox-checked-icon-default)" })
14837
+ }
14838
+ );
14839
+ }
14840
+ function ListBox({ children }) {
14841
+ return /* @__PURE__ */ jsxRuntime.jsx(
14842
+ "div",
14843
+ {
14844
+ className: "overflow-hidden border border-solid shadow-[var(--shadow-overlay)]",
14845
+ style: {
14846
+ borderRadius: CHIPFILTER.listboxRadius,
14847
+ background: CHIPFILTER.listboxBg,
14848
+ borderColor: CHIPFILTER.listboxBorder
14849
+ },
14850
+ children
14851
+ }
14852
+ );
14853
+ }
14854
+ function MenuItem({
14855
+ children,
14856
+ selected,
14857
+ disabled,
14858
+ multi,
14859
+ onMouseEnter,
14860
+ onClick
14861
+ }) {
14862
+ return /* @__PURE__ */ jsxRuntime.jsx(
14863
+ "button",
14864
+ {
14865
+ type: "button",
14866
+ disabled,
14867
+ onMouseEnter,
14868
+ onClick,
14869
+ className: cn(
14870
+ "flex w-full min-w-0 cursor-pointer items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
14871
+ selected ? "font-bold" : "font-normal",
14872
+ !disabled && "hover:font-bold"
14873
+ ),
14874
+ style: {
14875
+ paddingTop: CHIPFILTER.itemPaddingY,
14876
+ paddingBottom: CHIPFILTER.itemPaddingY,
14877
+ paddingLeft: multi ? CHIPFILTER.itemMultiPaddingX : CHIPFILTER.itemPaddingLeft,
14878
+ paddingRight: multi ? CHIPFILTER.itemMultiPaddingX : CHIPFILTER.itemPaddingRight,
14879
+ color: disabled ? CHIPFILTER.chipDisabledText : selected ? CHIPFILTER.itemTextSelected : CHIPFILTER.itemTextDefault
14880
+ },
14881
+ children
14882
+ }
14883
+ );
14884
+ }
14885
+ function EmptyItem({ children }) {
14886
+ return /* @__PURE__ */ jsxRuntime.jsx(
14887
+ "div",
14888
+ {
14889
+ className: "text-[12px] leading-[20px]",
14890
+ style: {
14891
+ paddingTop: CHIPFILTER.itemPaddingY,
14892
+ paddingBottom: CHIPFILTER.itemPaddingY,
14893
+ paddingLeft: CHIPFILTER.itemPaddingLeft,
14894
+ paddingRight: CHIPFILTER.itemPaddingRight,
14895
+ color: CHIPFILTER.chipDisabledText
14896
+ },
14897
+ children
14898
+ }
14899
+ );
14900
+ }
14901
+ var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
14902
+ values = [],
14903
+ onValueChange,
14904
+ onInput,
14905
+ onFocus,
14906
+ onBlur,
14907
+ errors = [],
14908
+ disabledChips = [],
14909
+ size = "sm",
14910
+ disabled = false,
14911
+ placeholder = "\uD0DC\uADF8 \uC785\uB825 (Enter\uB85C \uB4F1\uB85D / \uCF64\uB9C8\uB85C \uAD6C\uBD84 / \uB744\uC5B4\uC4F0\uAE30 \uBD88\uAC00)",
14912
+ name,
14913
+ rules,
14914
+ error,
14915
+ useReset = false,
14916
+ maxCount,
14917
+ metaPlacement = "end",
14918
+ duplicateLabel,
14919
+ suggestions = [],
14920
+ loadingSuggestions = false,
14921
+ recommendedItems = [],
14922
+ loadingRecommendedItems = false,
14923
+ dropdownMinWidth = 200,
14924
+ label,
14925
+ labelWidth,
14926
+ hint,
14927
+ errorMessage,
14928
+ width,
14929
+ status,
14930
+ icon,
14931
+ iconColor,
14932
+ labelTooltip,
14933
+ labelTooltipProps,
14934
+ className,
14935
+ style
14936
+ }, ref) {
14937
+ const autoId = react.useId();
14938
+ const resolvedName = name ?? autoId;
14939
+ const [inputValue, setInputValue] = react.useState("");
14940
+ const [focused, setFocused] = react.useState(false);
14941
+ const [hovered, setHovered] = react.useState(false);
14942
+ const [activeIndex, setActiveIndex] = react.useState(-1);
14943
+ const [suppressSuggestions, setSuppressSuggestions] = react.useState(false);
14944
+ const [suppressRecommendations, setSuppressRecommendations] = react.useState(false);
14945
+ const inputRef = react.useRef(null);
14946
+ const containerRef = react.useRef(null);
14947
+ const inputShellRef = react.useRef(null);
13438
14948
  const isComposingRef = react.useRef(false);
13439
14949
  react.useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }), []);
13440
14950
  const chipError = (i) => {
@@ -14111,7 +15621,7 @@ function TimeSelector({
14111
15621
  const renderColumn = (type) => {
14112
15622
  const isHour = type === "hour";
14113
15623
  const val = isHour ? displayHour : minute;
14114
- const displayValue = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
15624
+ const displayValue2 = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
14115
15625
  return /* @__PURE__ */ jsxRuntime.jsxs(
14116
15626
  "div",
14117
15627
  {
@@ -14132,7 +15642,7 @@ function TimeSelector({
14132
15642
  {
14133
15643
  "aria-label": isHour ? "\uC2DC" : "\uBD84",
14134
15644
  inputMode: "numeric",
14135
- value: displayValue,
15645
+ value: displayValue2,
14136
15646
  placeholder: "-",
14137
15647
  onFocus: () => beginEdit(type, val),
14138
15648
  onBlur: () => endEdit(type),
@@ -14327,8 +15837,8 @@ function STimePicker({
14327
15837
  disabled,
14328
15838
  className,
14329
15839
  style,
14330
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: commitOpen, children: [
14331
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
15840
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: commitOpen, children: [
15841
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14332
15842
  "div",
14333
15843
  {
14334
15844
  ref: triggerRef,
@@ -14388,14 +15898,17 @@ function STimePicker({
14388
15898
  ]
14389
15899
  }
14390
15900
  ) }),
14391
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14392
- RPopover2__namespace.Content,
15901
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
15902
+ RPopover9__namespace.Content,
14393
15903
  {
14394
15904
  side: "bottom",
14395
15905
  align: "start",
14396
15906
  sideOffset: 4,
14397
15907
  alignOffset: popoverAlignOffset,
14398
- className: cn("z-50", FLOATING_SLIDE_ANIM),
15908
+ className: cn(
15909
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
15910
+ FLOATING_SLIDE_ANIM
15911
+ ),
14399
15912
  children: /* @__PURE__ */ jsxRuntime.jsx(
14400
15913
  TimeSelector,
14401
15914
  {
@@ -14554,7 +16067,7 @@ function RangeTimeSelector({
14554
16067
  const { minute } = getParts(side);
14555
16068
  const val = isHour ? getDisplayHour(side) : minute;
14556
16069
  const currentEditingKey = getEditingKey(side, kind);
14557
- const displayValue = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
16070
+ const displayValue2 = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
14558
16071
  return /* @__PURE__ */ jsxRuntime.jsxs(
14559
16072
  "div",
14560
16073
  {
@@ -14575,7 +16088,7 @@ function RangeTimeSelector({
14575
16088
  {
14576
16089
  "aria-label": `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"}`,
14577
16090
  inputMode: "numeric",
14578
- value: displayValue,
16091
+ value: displayValue2,
14579
16092
  placeholder: "-",
14580
16093
  onFocus: () => beginEdit(side, kind, val),
14581
16094
  onBlur: () => endEdit(side, kind),
@@ -14786,8 +16299,8 @@ function STimeRangePicker({
14786
16299
  disabled,
14787
16300
  className,
14788
16301
  style,
14789
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: commitOpen, children: [
14790
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
16302
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: commitOpen, children: [
16303
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14791
16304
  "div",
14792
16305
  {
14793
16306
  ref: triggerRef,
@@ -14847,14 +16360,17 @@ function STimeRangePicker({
14847
16360
  ]
14848
16361
  }
14849
16362
  ) }),
14850
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14851
- RPopover2__namespace.Content,
16363
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
16364
+ RPopover9__namespace.Content,
14852
16365
  {
14853
16366
  side: "bottom",
14854
16367
  align: "start",
14855
16368
  sideOffset: 4,
14856
16369
  alignOffset: popoverAlignOffset,
14857
- className: cn("z-50", FLOATING_SLIDE_ANIM),
16370
+ className: cn(
16371
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
16372
+ FLOATING_SLIDE_ANIM
16373
+ ),
14858
16374
  children: /* @__PURE__ */ jsxRuntime.jsx(
14859
16375
  RangeTimeSelector,
14860
16376
  {
@@ -14887,6 +16403,7 @@ exports.GNB_RAIL = GNB_RAIL;
14887
16403
  exports.GNB_RAIL_WIDTH = GNB_RAIL_WIDTH;
14888
16404
  exports.ICONS = ICONS;
14889
16405
  exports.PAGE_BACKGROUNDS = PAGE_BACKGROUNDS;
16406
+ exports.RangeCalendar = RangeCalendar;
14890
16407
  exports.SActionModal = SActionModal;
14891
16408
  exports.SBadge = SBadge;
14892
16409
  exports.SBarcodeInput = SBarcodeInput;
@@ -14897,6 +16414,7 @@ exports.SCallout = SCallout;
14897
16414
  exports.SCard = SCard;
14898
16415
  exports.SCheckbox = SCheckbox;
14899
16416
  exports.SChip = SChip2;
16417
+ exports.SChipFilter = SChipFilter;
14900
16418
  exports.SChipInput = SChipInput;
14901
16419
  exports.SCircleProgress = SCircleProgress;
14902
16420
  exports.SConfirmModal = SConfirmModal;