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.js CHANGED
@@ -5,7 +5,7 @@ import { forwardRef, useState, useRef, useEffect, createContext, useContext, use
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { createPortal } from 'react-dom';
7
7
  import * as RCheckbox from '@radix-ui/react-checkbox';
8
- import * as RPopover2 from '@radix-ui/react-popover';
8
+ import * as RPopover9 from '@radix-ui/react-popover';
9
9
  import { createRoot } from 'react-dom/client';
10
10
  import * as RTooltip from '@radix-ui/react-tooltip';
11
11
  import * as RDropdown from '@radix-ui/react-dropdown-menu';
@@ -2565,6 +2565,27 @@ var Icon_sidebar = (p) => {
2565
2565
  }
2566
2566
  );
2567
2567
  };
2568
+ var Icon_star = (p) => {
2569
+ const { size = 24, ...rest } = p;
2570
+ return /* @__PURE__ */ jsx(
2571
+ "svg",
2572
+ {
2573
+ width: size,
2574
+ height: size,
2575
+ viewBox: "0 0 24 24",
2576
+ fill: "none",
2577
+ xmlns: "http://www.w3.org/2000/svg",
2578
+ ...rest,
2579
+ children: /* @__PURE__ */ jsx(
2580
+ "path",
2581
+ {
2582
+ 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",
2583
+ fill: "currentColor"
2584
+ }
2585
+ )
2586
+ }
2587
+ );
2588
+ };
2568
2589
  var Icon_store = (p) => {
2569
2590
  const { size = 24, ...rest } = p;
2570
2591
  return /* @__PURE__ */ jsxs(
@@ -3059,6 +3080,7 @@ var ICONS = {
3059
3080
  settingOutline: Icon_settingOutline,
3060
3081
  shield: Icon_shield,
3061
3082
  sidebar: Icon_sidebar,
3083
+ star: Icon_star,
3062
3084
  store: Icon_store,
3063
3085
  synchronize: Icon_synchronize,
3064
3086
  thumbsDown: Icon_thumbsDown,
@@ -3163,6 +3185,7 @@ var SDraggableItemView = /* @__PURE__ */ forwardRef(
3163
3185
  bordered,
3164
3186
  selected,
3165
3187
  dense,
3188
+ size,
3166
3189
  dragging,
3167
3190
  disabled,
3168
3191
  hovered,
@@ -3176,6 +3199,8 @@ var SDraggableItemView = /* @__PURE__ */ forwardRef(
3176
3199
  const supportingTextNode = renderSlot(supportingText, renderState);
3177
3200
  const hasSupportingText = supportingTextNode != null && supportingTextNode !== "";
3178
3201
  const trailingNode = renderSlot(trailing, renderState);
3202
+ const titleTypo = size === "sm" ? selected ? "typo-item-sm-selected" : "typo-item-sm-default" : selected ? "typo-item-md-selected" : "typo-item-md-default";
3203
+ 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)";
3179
3204
  return /* @__PURE__ */ jsxs(
3180
3205
  "div",
3181
3206
  {
@@ -3183,16 +3208,19 @@ var SDraggableItemView = /* @__PURE__ */ forwardRef(
3183
3208
  "aria-disabled": disabled || void 0,
3184
3209
  "data-dragging": dragging || void 0,
3185
3210
  className: cn(
3186
- "relative flex w-full items-center gap-(--cmp-listDraggableItem-gap) overflow-hidden px-(--cmp-listDraggableItem-paddingX) text-[12px] leading-[20px]",
3187
- 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)",
3211
+ "relative flex w-full items-center gap-(--cmp-listDraggableItem-gap) overflow-hidden px-(--cmp-listDraggableItem-paddingX)",
3212
+ titleTypo,
3213
+ 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)",
3188
3214
  !disabled && !selected && !dragging && "hover:bg-(--cmp-listDraggableItem-bg-hover)",
3189
- 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)",
3190
- bordered ? cn(
3215
+ hasSupportingText && supportingTextPosition === "bottom" ? dense ? "min-h-[52px]" : "min-h-[60px]" : dense ? "h-[32px]" : "h-[40px]",
3216
+ paddingYClass,
3217
+ // 항목 사이 구분선은 리스트가 그린다 — SDraggableList 의 separator (SList.tsx)
3218
+ bordered && cn(
3191
3219
  "rounded-(--cmp-listDraggableItem-radius-bordered) border border-(--cmp-listDraggableItem-border-default)",
3192
3220
  !disabled && !selected && !dragging && "hover:border-(--cmp-listDraggableItem-bordered-border-hover)",
3193
3221
  !disabled && selected && !dragging && "border-(--cmp-listDraggableItem-bordered-border-selected) bg-(--cmp-listDraggableItem-bordered-bg-selected)",
3194
3222
  !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)"
3195
- ) : "border-b border-(--cmp-listDraggableItem-border-default)",
3223
+ ),
3196
3224
  disabled && "cursor-not-allowed",
3197
3225
  className
3198
3226
  ),
@@ -3244,7 +3272,7 @@ var SDraggableItemView = /* @__PURE__ */ forwardRef(
3244
3272
  "span",
3245
3273
  {
3246
3274
  className: cn(
3247
- "min-w-0 truncate text-[12px] font-normal leading-[20px] text-(--sys-color-fg-secondary)",
3275
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3248
3276
  supportingTextPosition === "right" && "flex-1"
3249
3277
  ),
3250
3278
  children: supportingTextNode
@@ -3272,6 +3300,7 @@ var SDraggableItemImpl = /* @__PURE__ */ forwardRef(function SDraggableItem({
3272
3300
  selected = false,
3273
3301
  dragging = false,
3274
3302
  dense = false,
3303
+ size = "sm",
3275
3304
  disabled = false,
3276
3305
  dragOverlay = true,
3277
3306
  dragOverlayOpacity = 0.75,
@@ -3343,6 +3372,7 @@ var SDraggableItemImpl = /* @__PURE__ */ forwardRef(function SDraggableItem({
3343
3372
  bordered,
3344
3373
  selected,
3345
3374
  dense,
3375
+ size,
3346
3376
  dragging: dragging || overlayVisible,
3347
3377
  disabled,
3348
3378
  hovered,
@@ -3377,6 +3407,7 @@ var SDraggableItemImpl = /* @__PURE__ */ forwardRef(function SDraggableItem({
3377
3407
  bordered,
3378
3408
  selected,
3379
3409
  dense,
3410
+ size,
3380
3411
  dragging: true,
3381
3412
  disabled,
3382
3413
  hovered: true,
@@ -3391,7 +3422,7 @@ var SDraggableItemImpl = /* @__PURE__ */ forwardRef(function SDraggableItem({
3391
3422
  ] });
3392
3423
  });
3393
3424
  var SDraggableItem2 = /* @__PURE__ */ withDisplayName(SDraggableItemImpl, "SDraggableItem");
3394
- var SListImpl = /* @__PURE__ */ forwardRef(function SList({ className, children, useGap = false, usePadding = false, ...rest }, ref) {
3425
+ var SListImpl = /* @__PURE__ */ forwardRef(function SList({ className, children, useGap = false, usePadding = false, separator = false, ...rest }, ref) {
3395
3426
  return /* @__PURE__ */ jsx(
3396
3427
  "div",
3397
3428
  {
@@ -3399,6 +3430,7 @@ var SListImpl = /* @__PURE__ */ forwardRef(function SList({ className, children,
3399
3430
  className: cn(
3400
3431
  useGap && "flex flex-col gap-(--cmp-list-group-gap)",
3401
3432
  usePadding && "p-(--cmp-list-group-padding)",
3433
+ separator && "[&>*]:border-b [&>*]:border-(--cmp-listItem-border-default)",
3402
3434
  className
3403
3435
  ),
3404
3436
  ...rest,
@@ -3728,6 +3760,7 @@ var SExpansionItem = /* @__PURE__ */ forwardRef(
3728
3760
  interaction,
3729
3761
  accentStripe = false,
3730
3762
  dense = false,
3763
+ size = "sm",
3731
3764
  disabled = false,
3732
3765
  onToggle,
3733
3766
  className,
@@ -3743,6 +3776,7 @@ var SExpansionItem = /* @__PURE__ */ forwardRef(
3743
3776
  const hasChevronInteraction = interaction === "chevron";
3744
3777
  const showInteractionChevron = hasChevronInteraction && !disabled && (isSelected || hovered);
3745
3778
  const interactionChevronColor = isSelected ? "var(--sys-color-fg-brand)" : "var(--sys-color-fg-quaternary)";
3779
+ const titleTypo = size === "sm" ? isExpanded ? "typo-item-sm-selected" : "typo-item-sm-default" : isExpanded ? "typo-item-md-selected" : "typo-item-md-default";
3746
3780
  const reportExpandedDescendant = useCallback(
3747
3781
  (delta) => {
3748
3782
  setExpandedDescendantCount((current) => Math.max(0, current + delta));
@@ -3768,7 +3802,7 @@ var SExpansionItem = /* @__PURE__ */ forwardRef(
3768
3802
  ref,
3769
3803
  className: cn(
3770
3804
  "group/expansion-item relative overflow-hidden bg-(--cmp-listExpansionItem-title-bg-default)",
3771
- !bordered && "border-b border-(--cmp-listExpansionItem-border-default)",
3805
+ // 항목 사이 구분선은 리스트가 그린다 — SList/SExpansionList 의 separator (SList.tsx)
3772
3806
  // 보더 색은 시맨틱 토큰(border-default → hover strong → 펼침 accentLight)을 쓴다.
3773
3807
  // TODO: expansionItem/bordered/radius 토큰 생성 후 rounded 고정값도 토큰으로 교체한다.
3774
3808
  bordered && "rounded-[8px] border border-border-default hover:border-border-strong [&:has(>button:hover)]:border-border-strong",
@@ -3800,7 +3834,8 @@ var SExpansionItem = /* @__PURE__ */ forwardRef(
3800
3834
  onToggle?.(next, event);
3801
3835
  },
3802
3836
  className: cn(
3803
- "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)",
3837
+ "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)",
3838
+ titleTypo,
3804
3839
  isExpanded && "border-b border-(--cmp-listExpansionItem-title-border)",
3805
3840
  hasChevronInteraction ? "hover:bg-(--sys-color-bg-neutralBright)" : !isExpanded && "hover:bg-(--cmp-listExpansionItem-title-bg-hover)",
3806
3841
  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)",
@@ -3837,7 +3872,7 @@ var SExpansionItem = /* @__PURE__ */ forwardRef(
3837
3872
  "span",
3838
3873
  {
3839
3874
  className: cn(
3840
- "min-w-0 truncate text-[12px] font-normal leading-[20px] text-(--sys-color-fg-secondary)",
3875
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3841
3876
  supportingTextPosition === "right" && "flex-1"
3842
3877
  ),
3843
3878
  children: supportingText
@@ -3877,10 +3912,11 @@ var SListItemImpl = /* @__PURE__ */ forwardRef(function SListItem({
3877
3912
  trailing,
3878
3913
  bordered = false,
3879
3914
  selected = false,
3880
- clickable = true,
3915
+ clickable = false,
3881
3916
  interaction,
3882
3917
  accentStripe = false,
3883
3918
  dense = false,
3919
+ size = "sm",
3884
3920
  disabled = false,
3885
3921
  className,
3886
3922
  onMouseEnter,
@@ -3899,6 +3935,7 @@ var SListItemImpl = /* @__PURE__ */ forwardRef(function SListItem({
3899
3935
  const hasChevronInteraction = interaction === "chevron";
3900
3936
  const showInteractionChevron = hasChevronInteraction && isInteractive && (isSelected || hovered);
3901
3937
  const interactionChevronColor = isSelected ? "var(--sys-color-fg-brand)" : "var(--sys-color-fg-quaternary)";
3938
+ const titleTypo = size === "sm" ? isSelected ? "typo-item-sm-selected" : "typo-item-sm-default" : isSelected ? "typo-item-md-selected" : "typo-item-md-default";
3902
3939
  const handleMouseEnter = (event) => {
3903
3940
  if (isInteractive) setHovered(true);
3904
3941
  onMouseEnter?.(event);
@@ -3915,7 +3952,8 @@ var SListItemImpl = /* @__PURE__ */ forwardRef(function SListItem({
3915
3952
  onMouseEnter: handleMouseEnter,
3916
3953
  onMouseLeave: handleMouseLeave,
3917
3954
  className: cn(
3918
- "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)",
3955
+ "group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-(--cmp-listItem-text-default)",
3956
+ titleTypo,
3919
3957
  disabled ? "pointer-events-none cursor-not-allowed text-(--sys-color-disabled-text)" : isSelected ? cn(
3920
3958
  "cursor-pointer text-(--cmp-listItem-text-selected)",
3921
3959
  "bg-(--cmp-listItem-bg-selected)"
@@ -3927,10 +3965,10 @@ var SListItemImpl = /* @__PURE__ */ forwardRef(function SListItem({
3927
3965
  "gap-(--cmp-listItem-gap)",
3928
3966
  "pr-(--cmp-listItem-paddingX)",
3929
3967
  depth === 2 ? "pl-[36px]" : depth === 3 ? "pl-[56px]" : "pl-[16px]",
3930
- bordered ? cn(
3968
+ bordered && cn(
3931
3969
  "overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default)",
3932
3970
  isSelected ? "border-(--cmp-listItem-bordered-border-selected)" : isInteractive && "hover:border-(--cmp-listItem-bordered-border-hover)"
3933
- ) : "border-b border-(--cmp-listItem-border-default)",
3971
+ ),
3934
3972
  className
3935
3973
  ),
3936
3974
  ...rest,
@@ -3956,7 +3994,7 @@ var SListItemImpl = /* @__PURE__ */ forwardRef(function SListItem({
3956
3994
  "span",
3957
3995
  {
3958
3996
  className: cn(
3959
- "min-w-0 truncate text-[12px] font-normal leading-[20px] text-(--sys-color-fg-secondary)",
3997
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3960
3998
  supportingTextPosition === "right" && "flex-1"
3961
3999
  ),
3962
4000
  children: supportingTextNode
@@ -4009,6 +4047,8 @@ var SExpansionListImpl = /* @__PURE__ */ forwardRef(function SExpansionList({ ch
4009
4047
  const depth2 = child.props.depth ?? 1;
4010
4048
  const itemKey2 = String(child.key ?? index);
4011
4049
  return cloneElement(child, {
4050
+ // 선택을 리스트가 관리하는 이상 아이템은 클릭 가능해야 한다 — SListItem 의 기본값(false)을 뒤집는다
4051
+ clickable: child.props.clickable ?? true,
4012
4052
  selected: selectedKeys[depth2] === itemKey2,
4013
4053
  onClick: (event) => {
4014
4054
  setSelectedKeys((current) => ({ ...current, [depth2]: itemKey2 }));
@@ -4314,8 +4354,8 @@ function SGhostButton({
4314
4354
  }
4315
4355
  );
4316
4356
  if (!hasTooltip) return btn;
4317
- return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpen, children: [
4318
- /* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4357
+ return /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: setOpen, children: [
4358
+ /* @__PURE__ */ jsx(RPopover9.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4319
4359
  "span",
4320
4360
  {
4321
4361
  className: cn("inline-flex", anchorClassName),
@@ -4327,8 +4367,8 @@ function SGhostButton({
4327
4367
  children: btn
4328
4368
  }
4329
4369
  ) }),
4330
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4331
- RPopover2.Content,
4370
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4371
+ RPopover9.Content,
4332
4372
  {
4333
4373
  side: "top",
4334
4374
  sideOffset: 4,
@@ -4799,7 +4839,7 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4799
4839
  ...restContentProps
4800
4840
  } = contentProps ?? {};
4801
4841
  const contentClass = cn(
4802
- "z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
4842
+ "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",
4803
4843
  FLOATING_SLIDE_ANIM,
4804
4844
  className,
4805
4845
  contentPropsClassName
@@ -4811,14 +4851,14 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4811
4851
  ...style,
4812
4852
  ...contentPropsStyle
4813
4853
  };
4814
- return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpenState, children: [
4854
+ return /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: setOpenState, children: [
4815
4855
  isNone ? (
4816
4856
  // 트리거 없음: children 을 래퍼 없이 그대로 앵커로 쓴다 (소비자 레이아웃 보존).
4817
4857
  // mouse/focus 핸들러가 없으므로 useClose 없이도 자동 닫힘이 발생하지 않는다.
4818
- /* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: triggerNode })
4858
+ /* @__PURE__ */ jsx(RPopover9.Anchor, { asChild: true, children: triggerNode })
4819
4859
  ) : isHover ? (
4820
4860
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
4821
- /* @__PURE__ */ jsx(RPopover2.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4861
+ /* @__PURE__ */ jsx(RPopover9.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4822
4862
  "span",
4823
4863
  {
4824
4864
  className: "inline-flex",
@@ -4829,9 +4869,9 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4829
4869
  children: triggerNode
4830
4870
  }
4831
4871
  ) })
4832
- ) : /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: triggerNode }),
4833
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
4834
- RPopover2.Content,
4872
+ ) : /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: triggerNode }),
4873
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
4874
+ RPopover9.Content,
4835
4875
  {
4836
4876
  side: placement,
4837
4877
  sideOffset,
@@ -4865,7 +4905,7 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4865
4905
  }
4866
4906
  )
4867
4907
  ] }),
4868
- /* @__PURE__ */ jsx(RPopover2.Arrow, { width: 16, height: 12, style: { fill: bg } })
4908
+ /* @__PURE__ */ jsx(RPopover9.Arrow, { width: 16, height: 12, style: { fill: bg } })
4869
4909
  ]
4870
4910
  }
4871
4911
  ) })
@@ -5873,6 +5913,15 @@ var STextLink = /* @__PURE__ */ forwardRef(function STextLink2({
5873
5913
  /* @__PURE__ */ jsx(RTooltip.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(RTooltip.Content, { side: "top", sideOffset: 4, className: cn("z-50", FLOATING_SLIDE_ANIM), children: /* @__PURE__ */ jsx(STag, { shape: "square", size: "xs", color: "grey", label: text }) }) })
5874
5914
  ] }) });
5875
5915
  });
5916
+
5917
+ // src/lib/floating-width.ts
5918
+ var POPOVER_AVAILABLE_WIDTH = "var(--radix-popover-content-available-width)";
5919
+ var DROPDOWN_AVAILABLE_WIDTH = "var(--radix-dropdown-menu-content-available-width)";
5920
+ function clampToAvailableWidth(value, availableWidth) {
5921
+ if (value == null || value === "") return availableWidth;
5922
+ const css = typeof value === "number" ? `${value}px` : value;
5923
+ return `min(${css}, ${availableWidth})`;
5924
+ }
5876
5925
  var SIZE2 = {
5877
5926
  xs: { height: 24, paddingX: 8, gap: 6, arrow: 12, fontSize: 12, minWidth: 64, splitMinWidth: 72 },
5878
5927
  sm: {
@@ -5909,6 +5958,7 @@ function splitDivider(color, outline) {
5909
5958
  };
5910
5959
  return (outline ? outlined : solid)[color];
5911
5960
  }
5961
+ var VIEWPORT_PADDING = 12;
5912
5962
  var itemClass = cn(
5913
5963
  "flex min-h-[28px] cursor-pointer select-none items-center gap-[8px] rounded-[2px] px-[12px] py-[4px] text-[12px] leading-[20px] outline-none",
5914
5964
  "text-[color:var(--sys-color-fg-secondary)]",
@@ -5990,12 +6040,16 @@ var SDropdownButtonImpl = /* @__PURE__ */ forwardRef(
5990
6040
  sideOffset: 4,
5991
6041
  align: "start",
5992
6042
  alignOffset: split ? splitAlignOffset : 0,
6043
+ collisionPadding: VIEWPORT_PADDING,
5993
6044
  className: cn(
5994
6045
  "z-50 overflow-hidden rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-normal-sm",
5995
- !split && "min-w-[var(--radix-dropdown-menu-trigger-width)]",
6046
+ // 창보다 넓은 메뉴는 창 폭에 맞춰 줄인다. 트리거 너비를 따르는 하한도 같은 상한으로
6047
+ // 감싸야 한다 — min-width 가 max-width 를 이기기 때문이다.
6048
+ "max-w-[var(--radix-dropdown-menu-content-available-width)]",
6049
+ !split && "min-w-[min(var(--radix-dropdown-menu-trigger-width),var(--radix-dropdown-menu-content-available-width))]",
5996
6050
  FLOATING_SLIDE_ANIM
5997
6051
  ),
5998
- style: split ? { minWidth: splitWidth } : void 0,
6052
+ style: split ? { minWidth: clampToAvailableWidth(splitWidth, DROPDOWN_AVAILABLE_WIDTH) } : void 0,
5999
6053
  children: items.map((it) => /* @__PURE__ */ jsxs(
6000
6054
  RDropdown.Item,
6001
6055
  {
@@ -6007,7 +6061,7 @@ var SDropdownButtonImpl = /* @__PURE__ */ forwardRef(
6007
6061
  /* @__PURE__ */ jsx(
6008
6062
  "span",
6009
6063
  {
6010
- className: "min-w-0 whitespace-nowrap",
6064
+ className: "min-w-0 break-words",
6011
6065
  dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(it.label) }
6012
6066
  }
6013
6067
  )
@@ -6219,16 +6273,20 @@ function SPopover({
6219
6273
  const descColor = type === "default" ? "var(--cmp-popover-default-description)" : "var(--cmp-popover-light-description)";
6220
6274
  const leftLinkLabelClass = type === "default" ? "text-[color:var(--cmp-popover-default-subAction)]" : "text-[color:var(--cmp-popover-light-subAction)]";
6221
6275
  const triggerNode = trigger ?? /* @__PURE__ */ jsx("span", { className: "inline-flex cursor-pointer", "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(SIcon, { name: icon, size: iconSize, color }) });
6222
- return /* @__PURE__ */ jsxs(RPopover2.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6223
- /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: triggerNode }),
6224
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6225
- RPopover2.Content,
6276
+ return /* @__PURE__ */ jsxs(RPopover9.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6277
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: triggerNode }),
6278
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6279
+ RPopover9.Content,
6226
6280
  {
6227
6281
  side: placement,
6228
6282
  sideOffset: 8,
6229
6283
  className: cn(
6230
6284
  // 닫기 버튼이 항상 있으므로 우측은 close clearance(36px) 로 고정
6231
- "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",
6285
+ "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",
6286
+ // 창보다 넓어지면 창 폭에 맞춘다. 최소 너비도 같은 상한으로 감싼다 —
6287
+ // min-width 가 max-width 를 이기므로 감싸지 않으면 상한이 무력화된다.
6288
+ "max-w-[var(--radix-popover-content-available-width)]",
6289
+ "min-w-[min(160px,var(--radix-popover-content-available-width))]",
6232
6290
  FLOATING_SLIDE_ANIM,
6233
6291
  menuClassName
6234
6292
  ),
@@ -6284,7 +6342,7 @@ function SPopover({
6284
6342
  ]
6285
6343
  }
6286
6344
  ),
6287
- /* @__PURE__ */ jsx(RPopover2.Arrow, { width: 16, height: 12, style: { fill: bg } })
6345
+ /* @__PURE__ */ jsx(RPopover9.Arrow, { width: 16, height: 12, style: { fill: bg } })
6288
6346
  ]
6289
6347
  }
6290
6348
  ) })
@@ -6313,8 +6371,9 @@ function SPortal({
6313
6371
  ariaLabel
6314
6372
  }) {
6315
6373
  const portalContainer = usePortalContainer();
6374
+ const { minWidth: styleMinWidth, maxWidth: styleMaxWidth, ...restStyle } = style ?? {};
6316
6375
  return /* @__PURE__ */ jsxs(
6317
- RPopover2.Root,
6376
+ RPopover9.Root,
6318
6377
  {
6319
6378
  open,
6320
6379
  onOpenChange: (next) => {
@@ -6322,9 +6381,9 @@ function SPortal({
6322
6381
  if (!next) onClose?.();
6323
6382
  },
6324
6383
  children: [
6325
- /* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: anchorRef }),
6326
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
6327
- RPopover2.Content,
6384
+ /* @__PURE__ */ jsx(RPopover9.Anchor, { virtualRef: anchorRef }),
6385
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
6386
+ RPopover9.Content,
6328
6387
  {
6329
6388
  side: placement,
6330
6389
  align,
@@ -6351,11 +6410,18 @@ function SPortal({
6351
6410
  className: cn(
6352
6411
  "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
6353
6412
  maxHeight != null && "overflow-y-auto",
6354
- fit && "min-w-(--radix-popover-trigger-width)",
6413
+ // 앵커가 창보다 넓을 수 있는 자리(가로 스크롤 안의 트리거 등)에서도 하한이 상한을
6414
+ // 이기지 않도록, 앵커 너비 역시 가용 폭으로 가둔다.
6415
+ fit && "min-w-[min(var(--radix-popover-trigger-width),var(--radix-popover-content-available-width))]",
6355
6416
  FLOATING_SLIDE_ANIM,
6356
6417
  className
6357
6418
  ),
6358
- style: { maxHeight, ...style },
6419
+ style: {
6420
+ maxHeight,
6421
+ ...restStyle,
6422
+ ...styleMinWidth != null ? { minWidth: clampToAvailableWidth(styleMinWidth, POPOVER_AVAILABLE_WIDTH) } : {},
6423
+ maxWidth: clampToAvailableWidth(styleMaxWidth, POPOVER_AVAILABLE_WIDTH)
6424
+ },
6359
6425
  children
6360
6426
  }
6361
6427
  ) })
@@ -6515,16 +6581,18 @@ function SGuide({
6515
6581
  );
6516
6582
  if (isNotion)
6517
6583
  return /* @__PURE__ */ jsx("span", { className, style, children: trigger });
6518
- return /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: setOpen, children: [
6519
- /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: trigger }),
6520
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6521
- RPopover2.Content,
6584
+ return /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: setOpen, children: [
6585
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: trigger }),
6586
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6587
+ RPopover9.Content,
6522
6588
  {
6523
6589
  side: "bottom",
6524
6590
  align: "start",
6525
6591
  sideOffset: 4,
6526
6592
  className: cn(
6527
6593
  "relative z-50 rounded-[var(--cmp-guide-contents-radius)] bg-[var(--color-white)]",
6594
+ // popupWidth(기본 426px)가 창보다 넓으면 창 폭에 맞춘다 — max-width 가 width 를 이긴다.
6595
+ "max-w-[var(--radix-popover-content-available-width)]",
6528
6596
  FLOATING_SLIDE_ANIM
6529
6597
  ),
6530
6598
  style: {
@@ -7029,6 +7097,8 @@ function SLoadingModal({
7029
7097
  );
7030
7098
  }
7031
7099
  var CLOSE_ANIMATION_DURATION2 = 300;
7100
+ var RESIZE_KEY_STEP = { normal: 10, large: 50 };
7101
+ var RESIZE_HIT_WIDTH = "calc(var(--cmp-splitter-border-width) + var(--cmp-splitter-border-padding) * 2)";
7032
7102
  function toPx(value, percentBase) {
7033
7103
  if (typeof value === "number") return value;
7034
7104
  if (typeof value !== "string") return void 0;
@@ -7066,6 +7136,7 @@ function SDrawer({
7066
7136
  resizable = false,
7067
7137
  minWidth,
7068
7138
  maxWidth,
7139
+ onWidthChange,
7069
7140
  footerLeft,
7070
7141
  button,
7071
7142
  children,
@@ -7078,11 +7149,16 @@ function SDrawer({
7078
7149
  const [portalContainer, setPortalContainer] = useState(null);
7079
7150
  const backdropPointerDownRef = useRef(false);
7080
7151
  const contentRef = useRef(null);
7152
+ const panelRef = useRef(null);
7081
7153
  const stackRegistrationRef = useRef(null);
7082
7154
  const resizeCleanupRef = useRef(null);
7083
- const resizeStateRef = useRef(null);
7084
7155
  const [layerIndex, setLayerIndex] = useState(null);
7085
7156
  const [currentWidth, setCurrentWidth] = useState(width);
7157
+ const [resizing, setResizing] = useState(false);
7158
+ const [measuredWidth, setMeasuredWidth] = useState(null);
7159
+ const [viewportWidth, setViewportWidth] = useState(
7160
+ () => typeof window === "undefined" ? void 0 : window.innerWidth
7161
+ );
7086
7162
  useEffect(() => {
7087
7163
  setCurrentWidth(width);
7088
7164
  }, [width]);
@@ -7098,6 +7174,22 @@ function SDrawer({
7098
7174
  useEffect(() => {
7099
7175
  return () => resizeCleanupRef.current?.();
7100
7176
  }, []);
7177
+ useEffect(() => {
7178
+ if (!rendered) return;
7179
+ const sync = () => setViewportWidth(window.innerWidth);
7180
+ sync();
7181
+ window.addEventListener("resize", sync);
7182
+ return () => window.removeEventListener("resize", sync);
7183
+ }, [rendered]);
7184
+ useEffect(() => {
7185
+ const panel = panelRef.current;
7186
+ if (!resizable || !rendered || !panel || typeof ResizeObserver === "undefined") return;
7187
+ const observer = new ResizeObserver(
7188
+ () => setMeasuredWidth(Math.round(panel.getBoundingClientRect().width))
7189
+ );
7190
+ observer.observe(panel);
7191
+ return () => observer.disconnect();
7192
+ }, [resizable, rendered]);
7101
7193
  useEffect(() => {
7102
7194
  if (!open) {
7103
7195
  setEntered(false);
@@ -7139,41 +7231,80 @@ function SDrawer({
7139
7231
  }
7140
7232
  onOpenChange?.(false);
7141
7233
  };
7234
+ const resizeBounds = () => {
7235
+ const limit = viewportWidth;
7236
+ if (limit === void 0) return { min: void 0, max: void 0 };
7237
+ const min = toPx(minWidth, limit);
7238
+ const max = toPx(maxWidth, limit);
7239
+ return {
7240
+ min: min === void 0 ? void 0 : Math.min(min, limit),
7241
+ max: max === void 0 ? limit : Math.min(max, limit)
7242
+ };
7243
+ };
7244
+ const commitWidth = (next, startWidth) => {
7245
+ const { min, max } = resizeBounds();
7246
+ const clamped = Math.round(clamp3(next, min, max));
7247
+ setCurrentWidth(clamped);
7248
+ if (clamped !== Math.round(startWidth)) onWidthChange?.(clamped);
7249
+ };
7142
7250
  const handleResizeStart = (event) => {
7251
+ if (typeof document === "undefined") return;
7252
+ const panel = panelRef.current;
7253
+ if (!panel) return;
7143
7254
  event.preventDefault();
7144
7255
  event.stopPropagation();
7145
- const panel = contentRef.current?.querySelector("[data-s-drawer-panel]");
7146
- if (!panel) return;
7147
- resizeStateRef.current = {
7148
- startX: event.clientX,
7149
- startWidth: panel.getBoundingClientRect().width,
7150
- minWidth: toPx(minWidth, window.innerWidth),
7151
- maxWidth: toPx(maxWidth, window.innerWidth)
7152
- };
7256
+ event.currentTarget.focus();
7257
+ const startX = event.clientX;
7258
+ const startWidth = panel.getBoundingClientRect().width;
7259
+ const { min, max } = resizeBounds();
7260
+ let latest = startWidth;
7261
+ const { body } = document;
7262
+ const previousCursor = body.style.cursor;
7263
+ const previousUserSelect = body.style.userSelect;
7153
7264
  const onMove = (moveEvent) => {
7154
- const resizeState = resizeStateRef.current;
7155
- if (!resizeState) return;
7156
- const nextWidth = resizeState.startWidth + resizeState.startX - moveEvent.clientX;
7157
- setCurrentWidth(clamp3(nextWidth, resizeState.minWidth, resizeState.maxWidth));
7265
+ latest = Math.round(clamp3(startWidth + (startX - moveEvent.clientX), min, max));
7266
+ setCurrentWidth(latest);
7158
7267
  };
7159
- resizeCleanupRef.current?.();
7160
- const previousCursor = document.body.style.cursor;
7161
- const previousUserSelect = document.body.style.userSelect;
7162
7268
  const cleanupResize = () => {
7163
- resizeStateRef.current = null;
7164
- document.body.style.cursor = previousCursor;
7165
- document.body.style.userSelect = previousUserSelect;
7269
+ body.style.cursor = previousCursor;
7270
+ body.style.userSelect = previousUserSelect;
7166
7271
  document.removeEventListener("mousemove", onMove);
7167
7272
  document.removeEventListener("mouseup", cleanupResize);
7168
7273
  resizeCleanupRef.current = null;
7274
+ setResizing(false);
7275
+ commitWidth(latest, startWidth);
7169
7276
  };
7277
+ resizeCleanupRef.current?.();
7170
7278
  resizeCleanupRef.current = cleanupResize;
7171
- document.body.style.cursor = "col-resize";
7172
- document.body.style.userSelect = "none";
7279
+ body.style.cursor = "col-resize";
7280
+ body.style.userSelect = "none";
7173
7281
  document.addEventListener("mousemove", onMove);
7174
7282
  document.addEventListener("mouseup", cleanupResize);
7283
+ setResizing(true);
7284
+ };
7285
+ const handleResizeKeyDown = (event) => {
7286
+ const panel = panelRef.current;
7287
+ if (!panel) return;
7288
+ const startWidth = panel.getBoundingClientRect().width;
7289
+ const { min, max } = resizeBounds();
7290
+ const step = RESIZE_KEY_STEP[event.shiftKey ? "large" : "normal"];
7291
+ let next = null;
7292
+ if (event.key === "ArrowLeft") next = startWidth + step;
7293
+ else if (event.key === "ArrowRight") next = startWidth - step;
7294
+ else if (event.key === "Home" && min !== void 0) next = min;
7295
+ else if (event.key === "End" && max !== void 0) next = max;
7296
+ if (next === null) return;
7297
+ event.preventDefault();
7298
+ commitWidth(next, startWidth);
7175
7299
  };
7176
7300
  const modalHost = getModalHost();
7301
+ const resizeValueNow = measuredWidth ?? (typeof currentWidth === "number" ? Math.round(currentWidth) : void 0);
7302
+ const { min: resizeMinPx, max: resizeMaxPx } = resizeBounds();
7303
+ const resizeValueMin = resizeMinPx === void 0 ? void 0 : Math.round(resizeMinPx);
7304
+ const resizeValueMax = resizeMaxPx === void 0 ? void 0 : Math.round(resizeMaxPx);
7305
+ const appliedWidth = typeof currentWidth === "number" && viewportWidth !== void 0 ? Math.min(currentWidth, viewportWidth) : currentWidth;
7306
+ const appliedMinWidth = resizeMinPx ?? minWidth;
7307
+ const appliedMaxWidth = resizeMaxPx ?? maxWidth;
7177
7308
  return /* @__PURE__ */ jsx(RDialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsx(RDialog.Portal, { container: modalHost, forceMount: rendered ? true : void 0, children: /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute inset-0", style: { zIndex: layerIndex ?? 0 }, children: [
7178
7309
  /* @__PURE__ */ jsx(
7179
7310
  RDialog.Overlay,
@@ -7215,6 +7346,7 @@ function SDrawer({
7215
7346
  /* @__PURE__ */ jsxs(
7216
7347
  "div",
7217
7348
  {
7349
+ ref: panelRef,
7218
7350
  className: cn(
7219
7351
  "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",
7220
7352
  shaking && "animate-drawer-shake",
@@ -7222,20 +7354,45 @@ function SDrawer({
7222
7354
  ),
7223
7355
  "data-s-drawer-panel": true,
7224
7356
  "data-state": open && entered ? "open" : "closed",
7225
- style: { width: currentWidth, minWidth, maxWidth, ...style },
7357
+ style: {
7358
+ width: appliedWidth,
7359
+ minWidth: appliedMinWidth,
7360
+ maxWidth: appliedMaxWidth,
7361
+ ...style
7362
+ },
7226
7363
  onAnimationEnd: (event) => {
7227
7364
  if (event.animationName === "drawer-shake") setShaking(false);
7228
7365
  },
7229
7366
  children: [
7230
- resizable && /* @__PURE__ */ jsx(
7231
- "button",
7367
+ resizable && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
7368
+ "div",
7232
7369
  {
7233
- type: "button",
7370
+ role: "separator",
7371
+ "aria-orientation": "vertical",
7234
7372
  "aria-label": "Drawer \uB108\uBE44 \uC870\uC808",
7235
- 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",
7236
- onMouseDown: handleResizeStart
7373
+ "aria-valuenow": resizeValueNow,
7374
+ "aria-valuemin": resizeValueMin,
7375
+ "aria-valuemax": resizeValueMax,
7376
+ "aria-valuetext": resizeValueNow === void 0 ? void 0 : `${resizeValueNow}px`,
7377
+ tabIndex: 0,
7378
+ onMouseDown: handleResizeStart,
7379
+ onKeyDown: handleResizeKeyDown,
7380
+ className: "group absolute inset-y-0 left-0 z-[1] flex cursor-col-resize touch-none justify-start outline-none",
7381
+ style: { width: RESIZE_HIT_WIDTH },
7382
+ children: /* @__PURE__ */ jsx(
7383
+ "span",
7384
+ {
7385
+ "aria-hidden": true,
7386
+ className: cn(
7387
+ // 평상시엔 자리만 잡고 칠하지 않는다 — 색이 붙는 것 자체가 조절 가능하다는 신호다.
7388
+ "w-(--cmp-splitter-border-width) transition-colors",
7389
+ "group-hover:bg-(--cmp-splitter-border-color) group-focus-visible:bg-(--cmp-splitter-border-color)",
7390
+ resizing && "bg-(--cmp-splitter-border-color)"
7391
+ )
7392
+ }
7393
+ )
7237
7394
  }
7238
- ),
7395
+ ) }),
7239
7396
  /* @__PURE__ */ jsx(
7240
7397
  SOverlayHeader,
7241
7398
  {
@@ -7908,8 +8065,11 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
7908
8065
  onLauncherClick,
7909
8066
  logo,
7910
8067
  topContent,
8068
+ railTop,
7911
8069
  railFooter,
8070
+ menuTop,
7912
8071
  menuFooter,
8072
+ foldedTop,
7913
8073
  foldedFooter,
7914
8074
  resizable = false,
7915
8075
  menuWidth: menuWidthProp,
@@ -8418,6 +8578,7 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8418
8578
  ] })
8419
8579
  }
8420
8580
  ),
8581
+ railFolded && foldedTop && /* @__PURE__ */ jsx("div", { className: "mt-[24px] flex shrink-0 items-center justify-center", children: foldedTop }),
8421
8582
  /* @__PURE__ */ jsxs(
8422
8583
  "div",
8423
8584
  {
@@ -8435,9 +8596,9 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8435
8596
  transition: `transform ${GNB_FOLD_MS}ms ease-out, ${hideAfterSlide(folded)}`
8436
8597
  },
8437
8598
  children: [
8438
- useRail && // 레일 컬럼 — 스크롤되는 아이템 목록(nav) 하단 고정 footer 를 한 컬럼에 담는다.
8439
- // footer 내비게이션이 아니므로 nav(landmark) 밖에 두고, 컬럼 폭·구분선·배경은 이 래퍼가
8440
- // 전체 높이에 걸쳐 책임진다(그래야 footer 자리까지 배경·오른쪽 선이 이어진다).
8599
+ useRail && // 레일 컬럼 — 스크롤되는 아이템 목록(nav) 가운데 두고 위아래에 고정 슬롯(top/footer)을 붙인다.
8600
+ // 슬롯은 내비게이션이 아니므로 nav(landmark) 밖에 두고, 컬럼 폭·구분선·배경은 이 래퍼가
8601
+ // 전체 높이에 걸쳐 책임진다(그래야 슬롯 자리까지 배경·오른쪽 선이 이어진다).
8441
8602
  /* @__PURE__ */ jsxs(
8442
8603
  "div",
8443
8604
  {
@@ -8448,6 +8609,7 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8448
8609
  backgroundColor: C.panelBg
8449
8610
  },
8450
8611
  children: [
8612
+ railTop && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: railTop }),
8451
8613
  /* @__PURE__ */ jsx(
8452
8614
  "nav",
8453
8615
  {
@@ -8475,6 +8637,7 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8475
8637
  borderRight: isFull ? rightBorder : void 0
8476
8638
  },
8477
8639
  children: [
8640
+ menuTop && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: menuTop }),
8478
8641
  /* @__PURE__ */ jsx(
8479
8642
  "nav",
8480
8643
  {
@@ -8502,10 +8665,10 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8502
8665
  }
8503
8666
  ),
8504
8667
  header === "fix" && menuResizeHandle,
8505
- /* @__PURE__ */ jsxs(RPopover2.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8506
- /* @__PURE__ */ jsx(RPopover2.Anchor, { virtualRef: labelTipAnchorRef }),
8507
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
8508
- RPopover2.Content,
8668
+ /* @__PURE__ */ jsxs(RPopover9.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8669
+ /* @__PURE__ */ jsx(RPopover9.Anchor, { virtualRef: labelTipAnchorRef }),
8670
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
8671
+ RPopover9.Content,
8509
8672
  {
8510
8673
  side: "right",
8511
8674
  sideOffset: 4,
@@ -8523,16 +8686,81 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8523
8686
  }
8524
8687
  );
8525
8688
  });
8689
+ var SPageHeader = /* @__PURE__ */ withDisplayName(
8690
+ forwardRef(function SPageHeader2({ title, subText, fix = false, onBack, slot, scrolled = false, className, style, ...rest }, ref) {
8691
+ const hasBack = onBack != null;
8692
+ return /* @__PURE__ */ jsxs(
8693
+ "header",
8694
+ {
8695
+ ref,
8696
+ className: cn(
8697
+ "relative z-10 box-border flex shrink-0 items-center gap-(--cmp-pageHeader-gap) px-(--cmp-pageHeader-paddingX)",
8698
+ fix ? "h-(--cmp-pageHeader-bar-height) border-b border-(--cmp-pageHeader-bar-border) bg-(--cmp-pageHeader-bar-bg) py-(--cmp-pageHeader-bar-paddingY)" : cn(
8699
+ // 배경은 항상 투명 — 페이지 배경을 따라갈지는 fix 가 정하지 scrolled 가 정하지 않는다.
8700
+ // 스크롤되면 그림자만 더해 콘텐츠 위에 뜬 것처럼 보이게 한다.
8701
+ "bg-transparent pt-(--cmp-pageHeader-plain-paddingTop) pb-(--cmp-pageHeader-plain-paddingBottom)",
8702
+ scrolled && "shadow-floating"
8703
+ ),
8704
+ className
8705
+ ),
8706
+ style,
8707
+ ...rest,
8708
+ children: [
8709
+ hasBack && /* @__PURE__ */ jsx(
8710
+ SGhostButton,
8711
+ {
8712
+ icon: "chevronLeft",
8713
+ size: "xs",
8714
+ ariaLabel: "\uB4A4\uB85C\uAC00\uAE30",
8715
+ onClick: onBack,
8716
+ className: "shrink-0"
8717
+ }
8718
+ ),
8719
+ /* @__PURE__ */ jsxs(
8720
+ "div",
8721
+ {
8722
+ className: cn(
8723
+ "flex min-w-0 flex-1 items-center",
8724
+ fix ? "flex-row gap-(--cmp-pageHeader-gap)" : "flex-col items-start gap-(--cmp-pageHeader-plain-title-gap)",
8725
+ !hasBack && "pl-(--cmp-pageHeader-title-paddingLeft)"
8726
+ ),
8727
+ children: [
8728
+ /* @__PURE__ */ jsx("span", { className: "typo-heading-lg min-w-0 truncate text-(--cmp-pageHeader-title-text)", children: title }),
8729
+ subText && /* @__PURE__ */ jsx("span", { className: "typo-body-md-default min-w-0 truncate text-(--cmp-pageHeader-subtitle-text)", children: subText })
8730
+ ]
8731
+ }
8732
+ ),
8733
+ slot && /* @__PURE__ */ jsx("div", { className: "ml-auto flex shrink-0 items-center", children: slot })
8734
+ ]
8735
+ }
8736
+ );
8737
+ }),
8738
+ "SPageHeader"
8739
+ );
8526
8740
  var MIN_CONTENT_WIDTH = `calc(var(${SLAYOUT_MIN_WIDTH_VAR}, 0px) - 100cqw + 100%)`;
8527
8741
  var BACKGROUND_CLASS = {
8528
8742
  frame: `bg-[var(--sys-color-bg-frame)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`,
8529
8743
  neutral: `bg-[var(--sys-color-bg-neutralLight)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_WHITE_CLASS}`,
8530
8744
  screen: `bg-[var(--sys-color-bg-screen)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`
8531
8745
  };
8532
- var SPage = /* @__PURE__ */ forwardRef(function SPage2({ background = "frame", scrollEndSpacing = true, className, style, children, ...rest }, ref) {
8746
+ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8747
+ background = "frame",
8748
+ scrollEndSpacing = true,
8749
+ header,
8750
+ className,
8751
+ style,
8752
+ children,
8753
+ onScroll,
8754
+ ...rest
8755
+ }, ref) {
8533
8756
  const layout = useContext(SLayoutContext);
8534
8757
  const gridStyle = layout?.header === "full" ? { gridArea: "page" } : void 0;
8535
- return /* @__PURE__ */ jsx(
8758
+ const [scrolled, setScrolled] = useState(false);
8759
+ const handleScroll = (e) => {
8760
+ onScroll?.(e);
8761
+ if (header != null && !header.fix) setScrolled(e.currentTarget.scrollTop > 0);
8762
+ };
8763
+ const main = /* @__PURE__ */ jsx(
8536
8764
  "main",
8537
8765
  {
8538
8766
  ref,
@@ -8543,11 +8771,29 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({ background = "frame", s
8543
8771
  BACKGROUND_CLASS[background],
8544
8772
  className
8545
8773
  ),
8546
- style: { ...gridStyle, ...style },
8774
+ style: header == null ? { ...gridStyle, ...style } : style,
8775
+ onScroll: handleScroll,
8547
8776
  ...rest,
8548
8777
  children: /* @__PURE__ */ jsx("div", { style: { minWidth: MIN_CONTENT_WIDTH }, children })
8549
8778
  }
8550
8779
  );
8780
+ if (header == null) return main;
8781
+ return (
8782
+ // fix 가 아닌 헤더는 자기 배경이 투명이라(SPageHeader.tsx) 이 배경이 그대로 비친다 —
8783
+ // 그래서 SPage 의 background 를 main 뿐 아니라 여기에도 칠한다(스크롤바 관련 클래스는
8784
+ // 스크롤 요소가 아닌 여기서는 그냥 무시된다). fix 헤더는 자체 흰 배경이 덮으므로 무관하다.
8785
+ /* @__PURE__ */ jsxs(
8786
+ "div",
8787
+ {
8788
+ className: cn("flex min-h-0 min-w-0 flex-1 flex-col", BACKGROUND_CLASS[background]),
8789
+ style: gridStyle,
8790
+ children: [
8791
+ /* @__PURE__ */ jsx(SPageHeader, { ...header, scrolled }),
8792
+ main
8793
+ ]
8794
+ }
8795
+ )
8796
+ );
8551
8797
  });
8552
8798
 
8553
8799
  // src/components/SPage/page.config.ts
@@ -10921,7 +11167,7 @@ var SInput = /* @__PURE__ */ forwardRef(function SInput2({
10921
11167
  lineHeight: `${f.lineHeight}px`
10922
11168
  },
10923
11169
  children: [
10924
- prefix != null && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: prefix }),
11170
+ prefix != null && /* @__PURE__ */ jsx("span", { className: "flex flex-shrink-0 items-center", children: prefix }),
10925
11171
  /* @__PURE__ */ jsx(
10926
11172
  "input",
10927
11173
  {
@@ -10953,7 +11199,7 @@ var SInput = /* @__PURE__ */ forwardRef(function SInput2({
10953
11199
  ...rest
10954
11200
  }
10955
11201
  ),
10956
- suffix != null && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: suffix }),
11202
+ suffix != null && /* @__PURE__ */ jsx("span", { className: "flex flex-shrink-0 items-center", children: suffix }),
10957
11203
  clearable && hasValue && !disabled && !readOnly && /* @__PURE__ */ jsx(
10958
11204
  SGhostButton,
10959
11205
  {
@@ -11851,8 +12097,8 @@ function SDatePicker({
11851
12097
  disabled,
11852
12098
  className,
11853
12099
  style,
11854
- children: /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
11855
- /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
12100
+ children: /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
12101
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
11856
12102
  "button",
11857
12103
  {
11858
12104
  type: "button",
@@ -11888,13 +12134,16 @@ function SDatePicker({
11888
12134
  ]
11889
12135
  }
11890
12136
  ) }),
11891
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
11892
- RPopover2.Content,
12137
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
12138
+ RPopover9.Content,
11893
12139
  {
11894
12140
  side: "bottom",
11895
12141
  align: "start",
11896
12142
  sideOffset: 4,
11897
- className: cn("z-50", FLOATING_SLIDE_ANIM),
12143
+ className: cn(
12144
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
12145
+ FLOATING_SLIDE_ANIM
12146
+ ),
11898
12147
  children: /* @__PURE__ */ jsx(
11899
12148
  SCalendar,
11900
12149
  {
@@ -12042,78 +12291,81 @@ function RangeCalendar({
12042
12291
  }
12043
12292
  };
12044
12293
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
12045
- const renderPanel = (gy, gm, isLeft) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12046
- /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12047
- /* @__PURE__ */ jsx(
12048
- SGhostButton,
12049
- {
12050
- icon: "chevronLeft",
12051
- size: "xxs",
12052
- ariaLabel: "prevMonth",
12053
- onClick: goPrevMonth,
12054
- className: cn(!isLeft && "invisible pointer-events-none")
12055
- }
12056
- ),
12057
- /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-[40px] flex-1 items-center justify-center text-center text-[14px] font-medium leading-[24px]", children: [
12058
- gy,
12059
- ".",
12060
- String(gm).padStart(2, "0")
12294
+ const renderPanel = (gy, gm, isLeft) => (
12295
+ // 좁은 창에서 눌려 격자가 깨지지 않도록, 줄이는 대신 부모가 가로 스크롤하게 둔다.
12296
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12297
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12298
+ /* @__PURE__ */ jsx(
12299
+ SGhostButton,
12300
+ {
12301
+ icon: "chevronLeft",
12302
+ size: "xxs",
12303
+ ariaLabel: "prevMonth",
12304
+ onClick: goPrevMonth,
12305
+ className: cn(!isLeft && "invisible pointer-events-none")
12306
+ }
12307
+ ),
12308
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-[40px] flex-1 items-center justify-center text-center text-[14px] font-medium leading-[24px]", children: [
12309
+ gy,
12310
+ ".",
12311
+ String(gm).padStart(2, "0")
12312
+ ] }),
12313
+ /* @__PURE__ */ jsx(
12314
+ SGhostButton,
12315
+ {
12316
+ icon: "chevronRight",
12317
+ size: "xxs",
12318
+ ariaLabel: "nextMonth",
12319
+ onClick: goNextMonth,
12320
+ className: cn(isLeft && "invisible pointer-events-none")
12321
+ }
12322
+ )
12061
12323
  ] }),
12062
- /* @__PURE__ */ jsx(
12063
- SGhostButton,
12064
- {
12065
- icon: "chevronRight",
12066
- size: "xxs",
12067
- ariaLabel: "nextMonth",
12068
- onClick: goNextMonth,
12069
- className: cn(isLeft && "invisible pointer-events-none")
12070
- }
12071
- )
12072
- ] }),
12073
- /* @__PURE__ */ jsx("div", { className: GRID_COLS, children: WEEK_LABELS.map((l) => /* @__PURE__ */ jsx(
12074
- "span",
12075
- {
12076
- className: "flex h-[20px] items-center justify-center text-[12px] font-normal text-[color:var(--cmp-datepicker-calendar-week-color)]",
12077
- children: l
12078
- },
12079
- l
12080
- )) }),
12081
- /* @__PURE__ */ jsx("div", { className: cn(GRID_COLS, "gap-y-[var(--cmp-datepicker-calendar-grid-rowGap)]"), children: monthCells(gy, gm).map((cell) => {
12082
- const isEdge = cell.inCurrentMonth && (cell.date === rStart || complete && cell.date === rEnd);
12083
- const between = cell.inCurrentMonth && inRange(cell.date);
12084
- const disabled = cell.inCurrentMonth && isDisabled(cell.date);
12085
- const isToday = cell.inCurrentMonth && today === cell.date;
12086
- let cellBg;
12087
- if (between) cellBg = RANGE_BG;
12088
- else if (cell.inCurrentMonth && hasRange && cell.date === rStart)
12089
- cellBg = `linear-gradient(to right, transparent 50%, ${RANGE_BG} 50%)`;
12090
- else if (cell.inCurrentMonth && hasRange && cell.date === rEnd) cellBg = RANGE_BG;
12091
- return /* @__PURE__ */ jsx(
12092
- "div",
12324
+ /* @__PURE__ */ jsx("div", { className: GRID_COLS, children: WEEK_LABELS.map((l) => /* @__PURE__ */ jsx(
12325
+ "span",
12093
12326
  {
12094
- className: "flex h-[var(--cmp-datepicker-calendar-range-height)] w-[var(--cmp-datepicker-calendar-day-width)] items-center justify-center",
12095
- style: { background: cellBg },
12096
- children: /* @__PURE__ */ jsx(
12097
- "button",
12098
- {
12099
- type: "button",
12100
- disabled: !cell.inCurrentMonth || disabled,
12101
- onMouseEnter: () => setHover(cell.date),
12102
- onClick: () => cell.inCurrentMonth && click(cell.date),
12103
- className: cn(
12104
- "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]",
12105
- !cell.inCurrentMonth ? "invisible pointer-events-none" : disabled ? "cursor-not-allowed" : "cursor-pointer",
12106
- 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)]",
12107
- isToday && !disabled && "font-bold"
12108
- ),
12109
- children: cell.inCurrentMonth ? cell.day : ""
12110
- }
12111
- )
12327
+ className: "flex h-[20px] items-center justify-center text-[12px] font-normal text-[color:var(--cmp-datepicker-calendar-week-color)]",
12328
+ children: l
12112
12329
  },
12113
- cell.date
12114
- );
12115
- }) })
12116
- ] });
12330
+ l
12331
+ )) }),
12332
+ /* @__PURE__ */ jsx("div", { className: cn(GRID_COLS, "gap-y-[var(--cmp-datepicker-calendar-grid-rowGap)]"), children: monthCells(gy, gm).map((cell) => {
12333
+ const isEdge = cell.inCurrentMonth && (cell.date === rStart || complete && cell.date === rEnd);
12334
+ const between = cell.inCurrentMonth && inRange(cell.date);
12335
+ const disabled = cell.inCurrentMonth && isDisabled(cell.date);
12336
+ const isToday = cell.inCurrentMonth && today === cell.date;
12337
+ let cellBg;
12338
+ if (between) cellBg = RANGE_BG;
12339
+ else if (cell.inCurrentMonth && hasRange && cell.date === rStart)
12340
+ cellBg = `linear-gradient(to right, transparent 50%, ${RANGE_BG} 50%)`;
12341
+ else if (cell.inCurrentMonth && hasRange && cell.date === rEnd) cellBg = RANGE_BG;
12342
+ return /* @__PURE__ */ jsx(
12343
+ "div",
12344
+ {
12345
+ className: "flex h-[var(--cmp-datepicker-calendar-range-height)] w-[var(--cmp-datepicker-calendar-day-width)] items-center justify-center",
12346
+ style: { background: cellBg },
12347
+ children: /* @__PURE__ */ jsx(
12348
+ "button",
12349
+ {
12350
+ type: "button",
12351
+ disabled: !cell.inCurrentMonth || disabled,
12352
+ onMouseEnter: () => setHover(cell.date),
12353
+ onClick: () => cell.inCurrentMonth && click(cell.date),
12354
+ className: cn(
12355
+ "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]",
12356
+ !cell.inCurrentMonth ? "invisible pointer-events-none" : disabled ? "cursor-not-allowed" : "cursor-pointer",
12357
+ 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)]",
12358
+ isToday && !disabled && "font-bold"
12359
+ ),
12360
+ children: cell.inCurrentMonth ? cell.day : ""
12361
+ }
12362
+ )
12363
+ },
12364
+ cell.date
12365
+ );
12366
+ }) })
12367
+ ] })
12368
+ );
12117
12369
  const renderTimeSection = (which, date, suffix) => {
12118
12370
  const active = !!date;
12119
12371
  const [yy = "", mm = "", dd = ""] = date ? date.split("-") : [];
@@ -12181,8 +12433,11 @@ function RangeCalendar({
12181
12433
  "div",
12182
12434
  {
12183
12435
  className: cn(
12184
- "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",
12185
- useTimePicker ? "h-auto" : "h-[364px]"
12436
+ "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",
12437
+ // 높이는 고정이 아니라 하한이다 — 5주/6주 달을 오갈 때의 높이 흔들림만 막으면 되고,
12438
+ // 창이 좁아 패널이 가로 스크롤될 때는 스크롤바 높이만큼 늘어날 수 있어야 한다.
12439
+ // (단일 달 SCalendar 도 min-h 를 쓴다)
12440
+ useTimePicker ? "h-auto" : "min-h-[364px]"
12186
12441
  ),
12187
12442
  children: [
12188
12443
  /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
@@ -12201,11 +12456,20 @@ function RangeCalendar({
12201
12456
  }
12202
12457
  )
12203
12458
  ] }),
12204
- /* @__PURE__ */ jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-range-panelGap)]", children: [
12205
- renderPanel(y, m, true),
12206
- /* @__PURE__ */ jsx(SDivider, { vertical: true }),
12207
- renderPanel(nextMonthYM.year, nextMonthYM.month, false)
12208
- ] }),
12459
+ /* @__PURE__ */ jsxs(
12460
+ "div",
12461
+ {
12462
+ className: cn(
12463
+ "flex max-w-full gap-[var(--cmp-datepicker-calendar-range-panelGap)] overflow-x-auto",
12464
+ SCROLLBAR_CLASS
12465
+ ),
12466
+ children: [
12467
+ renderPanel(y, m, true),
12468
+ /* @__PURE__ */ jsx(SDivider, { vertical: true }),
12469
+ renderPanel(nextMonthYM.year, nextMonthYM.month, false)
12470
+ ]
12471
+ }
12472
+ ),
12209
12473
  useTimePicker && /* @__PURE__ */ jsxs(Fragment, { children: [
12210
12474
  /* @__PURE__ */ jsx(SDivider, {}),
12211
12475
  /* @__PURE__ */ jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-time-gap)]", children: [
@@ -12264,7 +12528,25 @@ function SDateRangePicker({
12264
12528
  });
12265
12529
  const resolvedError = error || ruleError !== "";
12266
12530
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12267
- 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 };
12531
+ const dims = size === "md" ? {
12532
+ padX: 16,
12533
+ gap: 12,
12534
+ icon: 20,
12535
+ minW: 104,
12536
+ fieldMinW: 260,
12537
+ fontSize: 14,
12538
+ lineHeight: 24,
12539
+ radius: 6
12540
+ } : {
12541
+ padX: 12,
12542
+ gap: 8,
12543
+ icon: 16,
12544
+ minW: 80,
12545
+ fieldMinW: 216,
12546
+ fontSize: 12,
12547
+ lineHeight: 20,
12548
+ radius: 4
12549
+ };
12268
12550
  const select = (r) => {
12269
12551
  setPendingStart(null);
12270
12552
  onValueChange?.(r);
@@ -12290,11 +12572,12 @@ function SDateRangePicker({
12290
12572
  focused: open,
12291
12573
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12292
12574
  width,
12575
+ minWidth: dims.fieldMinW,
12293
12576
  disabled,
12294
12577
  className,
12295
12578
  style,
12296
12579
  children: /* @__PURE__ */ jsxs(
12297
- RPopover2.Root,
12580
+ RPopover9.Root,
12298
12581
  {
12299
12582
  open,
12300
12583
  onOpenChange: (o) => {
@@ -12303,7 +12586,7 @@ function SDateRangePicker({
12303
12586
  if (!o) setPendingStart(null);
12304
12587
  },
12305
12588
  children: [
12306
- /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
12589
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
12307
12590
  "button",
12308
12591
  {
12309
12592
  type: "button",
@@ -12344,13 +12627,16 @@ function SDateRangePicker({
12344
12627
  ]
12345
12628
  }
12346
12629
  ) }),
12347
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
12348
- RPopover2.Content,
12630
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
12631
+ RPopover9.Content,
12349
12632
  {
12350
12633
  side: "bottom",
12351
12634
  align: "start",
12352
12635
  sideOffset: 4,
12353
- className: cn("z-50", FLOATING_SLIDE_ANIM),
12636
+ className: cn(
12637
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
12638
+ FLOATING_SLIDE_ANIM
12639
+ ),
12354
12640
  children: /* @__PURE__ */ jsx(
12355
12641
  RangeCalendar,
12356
12642
  {
@@ -13338,7 +13624,7 @@ function SRadioButton({
13338
13624
  disabled: od,
13339
13625
  onClick: () => !od && !selected && onValueChange?.(o.value),
13340
13626
  className: cn(
13341
- "relative box-border inline-flex items-center justify-center whitespace-nowrap border border-solid text-[12px] font-medium leading-[20px] transition-colors",
13627
+ "relative box-border grow inline-flex items-center justify-center whitespace-nowrap border border-solid text-[12px] font-medium leading-[20px] transition-colors",
13342
13628
  i === 0 ? "rounded-l-[var(--cmp-radioButton-radius)]" : "-ml-px",
13343
13629
  i === options.length - 1 ? "rounded-r-[var(--cmp-radioButton-radius)]" : "",
13344
13630
  selected ? "z-10" : "",
@@ -13360,66 +13646,1290 @@ function SRadioButton({
13360
13646
  }
13361
13647
  );
13362
13648
  }
13363
- var SChipInput = /* @__PURE__ */ forwardRef(function SChipInput2({
13364
- values = [],
13649
+ var CHIPFILTER_LISTBOX_WIDTH = 240;
13650
+ var CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH = 246;
13651
+ var CHIPFILTER = {
13652
+ barBg: "var(--cmp-chipFilter-bar-bg)",
13653
+ barBorder: "var(--cmp-chipFilter-bar-border)",
13654
+ barPaddingX: "var(--cmp-chipFilter-bar-paddingX)",
13655
+ barPaddingY: "var(--cmp-chipFilter-bar-paddingY)",
13656
+ barGap: "var(--cmp-chipFilter-bar-gap)",
13657
+ dividerHeight: "var(--cmp-chipFilter-divider-height)",
13658
+ chipHeight: "var(--cmp-chipFilter-chip-height)",
13659
+ chipRadius: "var(--cmp-chipFilter-chip-radius)",
13660
+ chipGap: "var(--cmp-chipFilter-chip-gap)",
13661
+ chipDefaultBg: "var(--cmp-chipFilter-chip-default-bg)",
13662
+ chipDefaultBorder: "var(--cmp-chipFilter-chip-default-border)",
13663
+ chipDefaultText: "var(--cmp-chipFilter-chip-default-text)",
13664
+ chipActiveBorder: "var(--cmp-chipFilter-chip-active-border)",
13665
+ chipWarningBorder: "var(--cmp-chipFilter-chip-warning-border)",
13666
+ chipAppliedBg: "var(--cmp-chipFilter-chip-applied-bg)",
13667
+ chipAppliedBorder: "var(--cmp-chipFilter-chip-applied-border)",
13668
+ chipAppliedLabel: "var(--cmp-chipFilter-chip-applied-text-label)",
13669
+ chipAppliedValue: "var(--color-brilliantblue-80)",
13670
+ chipDisabledBg: "var(--cmp-chipFilter-chip-disabled-bg)",
13671
+ chipDisabledBorder: "var(--cmp-chipFilter-chip-disabled-border)",
13672
+ chipDisabledText: "var(--cmp-chipFilter-chip-disabled-text)",
13673
+ addBorder: "var(--cmp-chipFilter-addButton-border-default)",
13674
+ addBorderDisabled: "var(--cmp-chipFilter-addButton-border-disabled)",
13675
+ addText: "var(--cmp-chipFilter-addButton-text-default)",
13676
+ addTextDisabled: "var(--cmp-chipFilter-addButton-text-disabled)",
13677
+ listboxBg: "var(--cmp-chipFilter-listbox-bg)",
13678
+ listboxBorder: "var(--cmp-chipFilter-listbox-border-color)",
13679
+ listboxRadius: "var(--cmp-chipFilter-listbox-radius)",
13680
+ listboxLabel: "var(--cmp-chipFilter-listbox-top-label-color)",
13681
+ listboxBottomDividerHeight: "var(--cmp-chipFilter-listbox-bottom-divider-height)",
13682
+ itemPaddingLeft: "var(--cmp-chipFilter-item-paddingLeft)",
13683
+ itemPaddingRight: "var(--cmp-chipFilter-item-paddingRight)",
13684
+ itemPaddingY: "var(--cmp-chipFilter-item-paddingY)",
13685
+ itemMultiPaddingX: "var(--cmp-chipFilter-item-multi-paddingX)",
13686
+ itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
13687
+ itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
13688
+ };
13689
+ var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
13690
+ var keywordEntries = (value) => {
13691
+ if (isKeywordValue(value)) return value.keywords;
13692
+ if (Array.isArray(value)) {
13693
+ return value.filter((v) => typeof v === "string");
13694
+ }
13695
+ return [];
13696
+ };
13697
+ var isEmptyValue = (value) => {
13698
+ if (value == null) return true;
13699
+ if (Array.isArray(value)) return value.length === 0 || value.every((v) => v == null || v === "");
13700
+ if (isKeywordValue(value)) return value.keywords.length === 0;
13701
+ return value === "";
13702
+ };
13703
+ var normalizeValues = (value) => value ?? {};
13704
+ var toArrayValue = (value) => Array.isArray(value) ? value.filter(
13705
+ (v) => typeof v === "string" || typeof v === "number"
13706
+ ) : [];
13707
+ var todayIso = () => {
13708
+ const d = /* @__PURE__ */ new Date();
13709
+ const pad = (n) => String(n).padStart(2, "0");
13710
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
13711
+ };
13712
+ var isInlineDateField = (field) => field.type === "date" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
13713
+ var resolveFieldDefault = (field) => {
13714
+ if (field.defaultValue !== void 0) return field.defaultValue;
13715
+ if (isInlineDateField(field)) return todayIso();
13716
+ if (!field.required) return void 0;
13717
+ if (field.type === "single") return field.options?.[0]?.value;
13718
+ if (field.type === "date") return todayIso();
13719
+ return void 0;
13720
+ };
13721
+ var valuesEqual = (a, b) => {
13722
+ if (isKeywordValue(a) || isKeywordValue(b)) {
13723
+ return isKeywordValue(a) && isKeywordValue(b) && a.mode === b.mode && valuesEqual(a.keywords, b.keywords);
13724
+ }
13725
+ if (Array.isArray(a) || Array.isArray(b)) {
13726
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => v === b[i]);
13727
+ }
13728
+ return a === b;
13729
+ };
13730
+ var valuesMapEqual = (a, b) => {
13731
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
13732
+ for (const key of keys) {
13733
+ if (!valuesEqual(a[key], b[key])) return false;
13734
+ }
13735
+ return true;
13736
+ };
13737
+ var isAtDefaultValue = (field, value) => {
13738
+ const def = resolveFieldDefault(field);
13739
+ return def !== void 0 && valuesEqual(value, def);
13740
+ };
13741
+ var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
13742
+ var isGroupedFields = (fields) => fields.length > 0 && "fields" in fields[0];
13743
+ var isGroupSatisfied = (group, values) => {
13744
+ if (!group.rule) return true;
13745
+ const keys = group.fields.map((f) => f.key);
13746
+ if (group.rule.type === "requireKey") return !isEmptyValue(values[group.rule.key]);
13747
+ if (group.rule.type === "requireAll") return keys.every((k) => !isEmptyValue(values[k]));
13748
+ if (group.rule.type === "requireAny") return keys.some((k) => !isEmptyValue(values[k]));
13749
+ return true;
13750
+ };
13751
+ var koreanParticle = (word, consonant, vowel) => {
13752
+ const code = word.charCodeAt(word.length - 1);
13753
+ if (code < 44032 || code > 55203) return consonant;
13754
+ return (code - 44032) % 28 === 0 ? vowel : consonant;
13755
+ };
13756
+ var defaultGroupTooltipMessage = (rule, fields) => {
13757
+ if (rule.type === "requireAll") return "\uD56D\uBAA9\uC744 \uBAA8\uB450 \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.";
13758
+ if (rule.type === "requireAny") {
13759
+ const name = rule.dataGroupName;
13760
+ if (!name) return "\uCD5C\uC18C 1\uAC1C\uC758 \uD544\uD130\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694.";
13761
+ return `\uCD5C\uC18C 1\uAC1C\uC758 ${name}${koreanParticle(name, "\uC744", "\uB97C")} \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`;
13762
+ }
13763
+ const label = fields.find((f) => f.key === rule.key)?.label ?? "\uD56D\uBAA9";
13764
+ return `${label}${koreanParticle(label, "\uC744", "\uB97C")} \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.`;
13765
+ };
13766
+ var matchPresetId = (presets, value) => {
13767
+ if (isEmptyValue(value)) return null;
13768
+ for (const preset of presets) {
13769
+ if (preset.custom || !preset.resolve) continue;
13770
+ const resolved = preset.resolve();
13771
+ if (Array.isArray(resolved) && Array.isArray(value)) {
13772
+ if (resolved[0] === value[0] && resolved[1] === value[1]) return preset.value;
13773
+ } else if (!Array.isArray(resolved) && !Array.isArray(value) && resolved === value) {
13774
+ return preset.value;
13775
+ }
13776
+ }
13777
+ return presets.find((preset) => preset.custom)?.value ?? null;
13778
+ };
13779
+ var optionLabel = (field, value) => field.options?.find((option) => option.value === value)?.label ?? String(value);
13780
+ var formatValueList = (labels) => {
13781
+ if (labels.length <= 1) return labels[0] ?? "";
13782
+ return `${labels[0]} \uC678 ${labels.length - 1}\uAC1C`;
13783
+ };
13784
+ var displayValue = (field, value) => {
13785
+ if (isEmptyValue(value)) return "";
13786
+ if (field.type === "multi")
13787
+ return formatValueList(toArrayValue(value).map((v) => optionLabel(field, v)));
13788
+ if (field.type === "single") return optionLabel(field, value);
13789
+ if (field.type === "date")
13790
+ return Array.isArray(value) ? `${value[0]} ~ ${value[1]}` : String(value);
13791
+ if (field.type === "keyword") return formatValueList(keywordEntries(value));
13792
+ return String(value);
13793
+ };
13794
+ var chipDisplayValue = (field, value) => {
13795
+ const text = displayValue(field, value);
13796
+ if (field.type !== "date" || isEmptyValue(value)) return text;
13797
+ const presets = field.presets ?? [];
13798
+ if (presets.length === 0) return text;
13799
+ const preset = presets.find((p) => p.value === matchPresetId(presets, value));
13800
+ return preset && !preset.custom ? `${preset.label}(${text})` : text;
13801
+ };
13802
+ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13803
+ fields: fieldsProp = [],
13804
+ value,
13365
13805
  onValueChange,
13366
- onInput,
13367
- onFocus,
13368
- onBlur,
13369
- errors = [],
13370
- disabledChips = [],
13371
- size = "sm",
13806
+ onFilterChange,
13807
+ onSearch,
13808
+ activeKeys,
13809
+ onActiveKeysChange,
13810
+ onReset,
13811
+ onAddFilter,
13812
+ label = "\uAC80\uC0C9 \uD544\uD130",
13813
+ showLabel = false,
13814
+ showAddButton = true,
13815
+ showReset = true,
13372
13816
  disabled = false,
13373
- placeholder = "\uD0DC\uADF8 \uC785\uB825 (Enter\uB85C \uB4F1\uB85D / \uCF64\uB9C8\uB85C \uAD6C\uBD84 / \uB744\uC5B4\uC4F0\uAE30 \uBD88\uAC00)",
13374
- name,
13375
- rules,
13376
- error,
13377
- useReset = false,
13378
- maxCount,
13379
- metaPlacement = "end",
13380
- duplicateLabel,
13381
- suggestions = [],
13382
- loadingSuggestions = false,
13383
- recommendedItems = [],
13384
- loadingRecommendedItems = false,
13385
- dropdownMinWidth = 200,
13386
- label,
13387
- labelWidth,
13388
- hint,
13389
- errorMessage,
13390
- width,
13391
- status,
13392
- icon,
13393
- iconColor,
13394
- labelTooltip,
13395
- labelTooltipProps,
13396
13817
  className,
13397
13818
  style
13398
13819
  }, ref) {
13399
- const autoId = useId();
13400
- const resolvedName = name ?? autoId;
13401
- const [inputValue, setInputValue] = useState("");
13402
- const [focused, setFocused] = useState(false);
13403
- const [hovered, setHovered] = useState(false);
13404
- const [activeIndex, setActiveIndex] = useState(-1);
13405
- const [suppressSuggestions, setSuppressSuggestions] = useState(false);
13406
- const [suppressRecommendations, setSuppressRecommendations] = useState(false);
13407
- const inputRef = useRef(null);
13408
- const containerRef = useRef(null);
13409
- const inputShellRef = useRef(null);
13410
- const isComposingRef = useRef(false);
13411
- useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }), []);
13412
- const chipError = (i) => {
13413
- if (values.indexOf(values[i]) !== i) return true;
13414
- if (typeof errors === "function") return errors(values[i] ?? "");
13415
- return errors[i] ?? false;
13416
- };
13417
- const chipDisabled = (i) => {
13418
- if (disabled) return true;
13419
- if (typeof disabledChips === "function") return disabledChips(values[i] ?? "");
13420
- return disabledChips[i] ?? false;
13421
- };
13422
- const duplicateMessage = useMemo(() => {
13820
+ const usingGroupedFields = isGroupedFields(fieldsProp);
13821
+ const groups = useMemo(
13822
+ () => usingGroupedFields ? fieldsProp : [{ fields: fieldsProp }],
13823
+ [usingGroupedFields, fieldsProp]
13824
+ );
13825
+ const fields = useMemo(() => groups.flatMap((group) => group.fields), [groups]);
13826
+ const values = normalizeValues(value);
13827
+ const portalContainer = usePortalContainer();
13828
+ const [internalActiveKeys, setInternalActiveKeys] = useState(
13829
+ () => fields.filter((field) => field.defaultActive !== false).map((field) => field.key)
13830
+ );
13831
+ const [openKey, setOpenKey] = useState(null);
13832
+ const [addOpen, setAddOpen] = useState(false);
13833
+ const triggerRefs = useRef(/* @__PURE__ */ new Map());
13834
+ const initializedDefaultsRef = useRef(/* @__PURE__ */ new Set());
13835
+ const prevOpenKeyRef = useRef(null);
13836
+ const valuesRef = useRef(values);
13837
+ valuesRef.current = values;
13838
+ const lastSearchedValuesRef = useRef(values);
13839
+ const invalidGroups = useMemo(() => {
13840
+ const invalid = /* @__PURE__ */ new Set();
13841
+ groups.forEach((group, groupIndex) => {
13842
+ if (!isGroupSatisfied(group, values)) invalid.add(groupIndex);
13843
+ });
13844
+ return invalid;
13845
+ }, [groups, values]);
13846
+ const isGroupsValid = useCallback(
13847
+ (current) => groups.every((group) => isGroupSatisfied(group, current)),
13848
+ [groups]
13849
+ );
13850
+ const runSearch = useCallback(
13851
+ (next) => {
13852
+ if (valuesMapEqual(lastSearchedValuesRef.current, next)) return;
13853
+ if (!isGroupsValid(next)) return;
13854
+ lastSearchedValuesRef.current = next;
13855
+ onSearch?.(next);
13856
+ },
13857
+ [isGroupsValid, onSearch]
13858
+ );
13859
+ useEffect(() => {
13860
+ const prevKey = prevOpenKeyRef.current;
13861
+ if (prevKey != null && prevKey !== openKey) {
13862
+ runSearch(valuesRef.current);
13863
+ }
13864
+ prevOpenKeyRef.current = openKey;
13865
+ }, [openKey, runSearch]);
13866
+ useEffect(() => {
13867
+ if (activeKeys) return;
13868
+ setInternalActiveKeys((current) => {
13869
+ const known = new Set(fields.map((field) => field.key));
13870
+ const kept = current.filter((key) => known.has(key));
13871
+ const additions = fields.filter((field) => field.defaultActive !== false && !kept.includes(field.key)).map((field) => field.key);
13872
+ return [...kept, ...additions];
13873
+ });
13874
+ }, [activeKeys, fields]);
13875
+ useEffect(() => {
13876
+ const patch = {};
13877
+ let changed = false;
13878
+ fields.forEach((field) => {
13879
+ if (initializedDefaultsRef.current.has(field.key)) return;
13880
+ if (!isEmptyValue(values[field.key])) {
13881
+ initializedDefaultsRef.current.add(field.key);
13882
+ return;
13883
+ }
13884
+ const def = resolveFieldDefault(field);
13885
+ if (def === void 0) return;
13886
+ patch[field.key] = def;
13887
+ initializedDefaultsRef.current.add(field.key);
13888
+ changed = true;
13889
+ });
13890
+ if (changed) onValueChange?.({ ...values, ...patch });
13891
+ }, [fields, values, onValueChange]);
13892
+ const resolvedActiveKeys = activeKeys ?? internalActiveKeys;
13893
+ const activeFields = fields.filter((field) => resolvedActiveKeys.includes(field.key));
13894
+ const inactiveFields = fields.filter((field) => !resolvedActiveKeys.includes(field.key));
13895
+ const groupIndexByKey = /* @__PURE__ */ new Map();
13896
+ groups.forEach((group, groupIndex) => {
13897
+ group.fields.forEach((field) => groupIndexByKey.set(field.key, groupIndex));
13898
+ });
13899
+ const hasValue = fields.some((field) => !isEmptyValue(values[field.key]));
13900
+ const hasRightActions = showAddButton || showReset;
13901
+ const currentOpenField = fields.find((field) => field.key === openKey);
13902
+ const editorPopoverStyle = useMemo(() => {
13903
+ if (currentOpenField?.type === "keyword") {
13904
+ return { width: CHIPFILTER_LISTBOX_WIDTH };
13905
+ }
13906
+ if (currentOpenField?.type === "date" && (currentOpenField.presets?.length ?? 0) > 0) {
13907
+ return { width: CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH };
13908
+ }
13909
+ return { width: CHIPFILTER_LISTBOX_WIDTH };
13910
+ }, [currentOpenField?.type, currentOpenField?.presets?.length]);
13911
+ const setActive = (next) => {
13912
+ if (!activeKeys) setInternalActiveKeys(next);
13913
+ onActiveKeysChange?.(next);
13914
+ };
13915
+ const emitValue = (key, nextValue) => {
13916
+ const next = { ...values, [key]: nextValue };
13917
+ onValueChange?.(next);
13918
+ onFilterChange?.({ key, value: nextValue, values: next });
13919
+ return next;
13920
+ };
13921
+ const emitValueAndSearch = (key, nextValue) => {
13922
+ runSearch(emitValue(key, nextValue));
13923
+ };
13924
+ const clearValue = (key) => {
13925
+ const field = fields.find((f) => f.key === key);
13926
+ const def = field ? resolveFieldDefault(field) : void 0;
13927
+ runSearch(emitValue(key, def !== void 0 ? def : null));
13928
+ };
13929
+ const reset = useCallback(() => {
13930
+ const next = fields.reduce((acc, field) => {
13931
+ const def = resolveFieldDefault(field);
13932
+ acc[field.key] = def !== void 0 ? def : null;
13933
+ return acc;
13934
+ }, {});
13935
+ onValueChange?.(next);
13936
+ runSearch(next);
13937
+ onReset?.();
13938
+ }, [fields, onReset, onValueChange, runSearch]);
13939
+ const open = useCallback(
13940
+ (key) => {
13941
+ const field = fields.find((f) => f.key === key);
13942
+ if (!field || isInlineField(field)) return;
13943
+ setOpenKey(key);
13944
+ },
13945
+ [fields]
13946
+ );
13947
+ const validate = useCallback(() => invalidGroups.size === 0, [invalidGroups]);
13948
+ useImperativeHandle(ref, () => ({ open, reset, validate }), [open, reset, validate]);
13949
+ const addField = (key) => {
13950
+ const next = [...resolvedActiveKeys, key];
13951
+ setActive(next);
13952
+ onAddFilter?.(key);
13953
+ setAddOpen(false);
13954
+ const field = fields.find((f) => f.key === key);
13955
+ if (field && !isInlineField(field)) requestAnimationFrame(() => setOpenKey(key));
13956
+ };
13957
+ const rootStyle = useMemo(
13958
+ () => ({
13959
+ "--chipfilter-bar-bg": CHIPFILTER.barBg,
13960
+ "--chipfilter-bar-border": CHIPFILTER.barBorder,
13961
+ "--chipfilter-bar-padding-x": CHIPFILTER.barPaddingX,
13962
+ "--chipfilter-bar-padding-y": CHIPFILTER.barPaddingY,
13963
+ "--chipfilter-bar-gap": CHIPFILTER.barGap,
13964
+ "--chipfilter-divider-height": CHIPFILTER.dividerHeight,
13965
+ ...style
13966
+ }),
13967
+ [style]
13968
+ );
13969
+ return /* @__PURE__ */ jsxs(
13970
+ "div",
13971
+ {
13972
+ className: cn(
13973
+ "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)]",
13974
+ className
13975
+ ),
13976
+ style: { ...rootStyle, borderColor: "var(--chipfilter-bar-border)" },
13977
+ children: [
13978
+ showLabel && /* @__PURE__ */ jsxs(Fragment, { children: [
13979
+ /* @__PURE__ */ jsx(STag, { className: "shrink-0", shape: "square", size: "sm", color: "darkblue", label }),
13980
+ /* @__PURE__ */ jsx(Divider, { top: true })
13981
+ ] }),
13982
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children: [
13983
+ (() => {
13984
+ const nodes = [];
13985
+ let runBuffer = [];
13986
+ let runGroupIndex = null;
13987
+ const flushRun = () => {
13988
+ if (runBuffer.length === 0) return;
13989
+ const group = runGroupIndex != null ? groups[runGroupIndex] : void 0;
13990
+ const message = usingGroupedFields && group?.rule ? group.tooltipMessage ?? defaultGroupTooltipMessage(group.rule, group.fields) : void 0;
13991
+ if (message) {
13992
+ nodes.push(
13993
+ /* @__PURE__ */ jsx(
13994
+ GroupTooltip,
13995
+ {
13996
+ message,
13997
+ open: invalidGroups.has(runGroupIndex),
13998
+ children: runBuffer
13999
+ },
14000
+ `group-${runGroupIndex}`
14001
+ )
14002
+ );
14003
+ } else {
14004
+ nodes.push(...runBuffer);
14005
+ }
14006
+ runBuffer = [];
14007
+ };
14008
+ activeFields.forEach((field, index) => {
14009
+ const inline = isInlineField(field);
14010
+ const groupIndex = groupIndexByKey.get(field.key) ?? 0;
14011
+ const warning = field.warning || invalidGroups.has(groupIndex);
14012
+ const prevField = index > 0 ? activeFields[index - 1] : null;
14013
+ const prevGroupIndex = prevField ? groupIndexByKey.get(prevField.key) ?? 0 : null;
14014
+ const needsLeadingDivider = index === 0 ? false : inline || prevField != null && isInlineField(prevField) || groupIndex !== prevGroupIndex;
14015
+ if (groupIndex !== runGroupIndex) {
14016
+ flushRun();
14017
+ runGroupIndex = groupIndex;
14018
+ }
14019
+ const showClear = !isAtDefaultValue(field, values[field.key]);
14020
+ const node = isInlineDateField(field) ? /* @__PURE__ */ jsx(
14021
+ InlineDatePresetFilter,
14022
+ {
14023
+ field,
14024
+ value: values[field.key],
14025
+ disabled: disabled || field.disabled,
14026
+ warning,
14027
+ showClear,
14028
+ onValueChange: (next) => emitValueAndSearch(field.key, next),
14029
+ onClear: () => clearValue(field.key)
14030
+ },
14031
+ field.key
14032
+ ) : field.type === "custom" ? /* @__PURE__ */ jsx(Fragment$1, { children: field.render?.({
14033
+ value: values[field.key],
14034
+ disabled: disabled || field.disabled,
14035
+ warning,
14036
+ onValueChange: (next) => emitValueAndSearch(field.key, next)
14037
+ }) }, field.key) : /* @__PURE__ */ jsx(
14038
+ FilterChip,
14039
+ {
14040
+ field,
14041
+ valueText: chipDisplayValue(field, values[field.key]),
14042
+ active: openKey === field.key,
14043
+ applied: !isEmptyValue(values[field.key]),
14044
+ disabled: disabled || field.disabled,
14045
+ warning,
14046
+ showClear,
14047
+ refCallback: (node2) => triggerRefs.current.set(field.key, node2),
14048
+ onOpen: () => setOpenKey(openKey === field.key ? null : field.key),
14049
+ onClear: () => clearValue(field.key)
14050
+ },
14051
+ field.key
14052
+ );
14053
+ runBuffer.push(
14054
+ needsLeadingDivider ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
14055
+ /* @__PURE__ */ jsx(Divider, { inset: true }),
14056
+ node
14057
+ ] }, `${field.key}-wrap`) : node
14058
+ );
14059
+ });
14060
+ flushRun();
14061
+ return nodes;
14062
+ })(),
14063
+ (() => {
14064
+ const lastField = activeFields[activeFields.length - 1];
14065
+ const needsTrailingDivider = lastField != null && isInlineField(lastField) && !hasRightActions;
14066
+ return needsTrailingDivider ? /* @__PURE__ */ jsx(Divider, { inset: true }) : null;
14067
+ })(),
14068
+ hasRightActions && /* @__PURE__ */ jsxs(Fragment, { children: [
14069
+ /* @__PURE__ */ jsx(Divider, { inset: true }),
14070
+ showAddButton && /* @__PURE__ */ jsxs(RPopover9.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14071
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
14072
+ "button",
14073
+ {
14074
+ type: "button",
14075
+ disabled: disabled || inactiveFields.length === 0,
14076
+ 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",
14077
+ style: {
14078
+ minHeight: "var(--cmp-chipFilter-addButton-height)" ,
14079
+ borderRadius: "var(--cmp-chipFilter-addButton-radius)" ,
14080
+ borderColor: disabled || inactiveFields.length === 0 ? CHIPFILTER.addBorderDisabled : CHIPFILTER.addBorder,
14081
+ color: disabled || inactiveFields.length === 0 ? CHIPFILTER.addTextDisabled : CHIPFILTER.addText
14082
+ },
14083
+ children: [
14084
+ /* @__PURE__ */ jsx(
14085
+ SIcon,
14086
+ {
14087
+ name: "add",
14088
+ size: 12,
14089
+ color: disabled || inactiveFields.length === 0 ? CHIPFILTER.addTextDisabled : CHIPFILTER.addText
14090
+ }
14091
+ ),
14092
+ /* @__PURE__ */ jsx("span", { children: "\uD544\uD130 \uCD94\uAC00" })
14093
+ ]
14094
+ }
14095
+ ) }),
14096
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14097
+ RPopover9.Content,
14098
+ {
14099
+ side: "bottom",
14100
+ align: "start",
14101
+ sideOffset: 4,
14102
+ className: cn("z-50 min-w-[180px]", FLOATING_SLIDE_ANIM),
14103
+ children: /* @__PURE__ */ jsx(ListBox, { children: inactiveFields.length > 0 ? inactiveFields.map((field) => /* @__PURE__ */ jsx(MenuItem, { onClick: () => addField(field.key), children: field.label }, field.key)) : /* @__PURE__ */ jsx(EmptyItem, { children: "\uCD94\uAC00\uD560 \uD544\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })
14104
+ }
14105
+ ) })
14106
+ ] }),
14107
+ showReset && /* @__PURE__ */ jsxs(
14108
+ "button",
14109
+ {
14110
+ type: "button",
14111
+ disabled: disabled || !hasValue,
14112
+ onClick: reset,
14113
+ className: cn(
14114
+ "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",
14115
+ // 부모 flex gap(6px)은 칩 사이 간격 기준이라, 필터 추가 버튼과의 12px 간격을 맞추려면
14116
+ // 부족한 만큼(12px - gap)을 추가로 띄운다. 필터 추가가 없을 땐 divider와 이미 12px이라 불필요.
14117
+ showAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14118
+ ),
14119
+ style: {
14120
+ color: disabled || !hasValue ? "var(--cmp-textLink-content-disabled)" : "var(--cmp-textLink-content-default)"
14121
+ },
14122
+ children: [
14123
+ /* @__PURE__ */ jsx(
14124
+ SIcon,
14125
+ {
14126
+ name: "reset",
14127
+ size: 16,
14128
+ color: disabled || !hasValue ? "var(--cmp-textLink-icon-disabled)" : "var(--cmp-textLink-icon-default)"
14129
+ }
14130
+ ),
14131
+ /* @__PURE__ */ jsx("span", { children: "\uAC80\uC0C9 \uCD08\uAE30\uD654" })
14132
+ ]
14133
+ }
14134
+ )
14135
+ ] })
14136
+ ] }),
14137
+ /* @__PURE__ */ jsxs(RPopover9.Root, { open: openKey != null, onOpenChange: (next) => !next && setOpenKey(null), children: [
14138
+ /* @__PURE__ */ jsx(
14139
+ RPopover9.Anchor,
14140
+ {
14141
+ virtualRef: {
14142
+ current: openKey ? triggerRefs.current.get(openKey) : null
14143
+ }
14144
+ }
14145
+ ),
14146
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14147
+ RPopover9.Content,
14148
+ {
14149
+ side: "bottom",
14150
+ align: "start",
14151
+ sideOffset: 4,
14152
+ onOpenAutoFocus: (event) => event.preventDefault(),
14153
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
14154
+ style: editorPopoverStyle,
14155
+ children: currentOpenField && /* @__PURE__ */ jsx(
14156
+ EditorPanel,
14157
+ {
14158
+ field: currentOpenField,
14159
+ value: values[currentOpenField.key],
14160
+ disabled: disabled || currentOpenField.disabled,
14161
+ onValueChange: (next) => emitValue(currentOpenField.key, next),
14162
+ onClose: () => setOpenKey(null)
14163
+ }
14164
+ )
14165
+ }
14166
+ ) })
14167
+ ] })
14168
+ ]
14169
+ }
14170
+ );
14171
+ });
14172
+ function Divider({ inset, top } = {}) {
14173
+ return /* @__PURE__ */ jsx(
14174
+ "span",
14175
+ {
14176
+ "aria-hidden": true,
14177
+ className: cn(
14178
+ "inline-flex w-px shrink-0 bg-[var(--chipfilter-bar-border)]",
14179
+ top ? "mt-[2px] self-start" : "my-auto",
14180
+ inset && "mx-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14181
+ ),
14182
+ style: { height: "var(--chipfilter-divider-height)" }
14183
+ }
14184
+ );
14185
+ }
14186
+ function GroupTooltip({
14187
+ message,
14188
+ open,
14189
+ children
14190
+ }) {
14191
+ return /* @__PURE__ */ jsx(STooltip2, { trigger: "none", open, placement: "top", tooltipType: "warning", message: [message], children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children }) });
14192
+ }
14193
+ function FilterChip({
14194
+ field,
14195
+ valueText,
14196
+ active,
14197
+ applied,
14198
+ disabled,
14199
+ warning,
14200
+ showClear,
14201
+ refCallback,
14202
+ onOpen,
14203
+ onClear
14204
+ }) {
14205
+ const border = disabled ? CHIPFILTER.chipDisabledBorder : warning ? CHIPFILTER.chipWarningBorder : applied ? active ? "var(--color-blue-strong)" : CHIPFILTER.chipAppliedBorder : active ? CHIPFILTER.chipActiveBorder : CHIPFILTER.chipDefaultBorder;
14206
+ const background = disabled ? CHIPFILTER.chipDisabledBg : applied ? CHIPFILTER.chipAppliedBg : CHIPFILTER.chipDefaultBg;
14207
+ const textColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipDefaultText;
14208
+ return /* @__PURE__ */ jsxs(
14209
+ "button",
14210
+ {
14211
+ ref: refCallback,
14212
+ type: "button",
14213
+ disabled,
14214
+ "aria-expanded": active,
14215
+ onPointerDown: (event) => event.stopPropagation(),
14216
+ onClick: onOpen,
14217
+ 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",
14218
+ style: {
14219
+ minHeight: CHIPFILTER.chipHeight,
14220
+ gap: CHIPFILTER.chipGap,
14221
+ borderRadius: CHIPFILTER.chipRadius,
14222
+ borderColor: border,
14223
+ background,
14224
+ color: textColor
14225
+ },
14226
+ children: [
14227
+ field.required && /* @__PURE__ */ jsx(
14228
+ SIcon,
14229
+ {
14230
+ name: "asterisk",
14231
+ size: 12,
14232
+ color: disabled ? CHIPFILTER.chipDisabledText : "var(--cmp-tag-darkblue-content)"
14233
+ }
14234
+ ),
14235
+ applied ? /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center gap-[4px] truncate", children: [
14236
+ /* @__PURE__ */ jsx(
14237
+ "span",
14238
+ {
14239
+ className: "font-bold",
14240
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel },
14241
+ children: field.label
14242
+ }
14243
+ ),
14244
+ /* @__PURE__ */ jsx(
14245
+ "span",
14246
+ {
14247
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel },
14248
+ children: ":"
14249
+ }
14250
+ ),
14251
+ /* @__PURE__ */ jsx(
14252
+ "span",
14253
+ {
14254
+ className: "min-w-0 truncate font-bold",
14255
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedValue },
14256
+ children: valueText
14257
+ }
14258
+ )
14259
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
14260
+ /* @__PURE__ */ jsx("span", { className: cn("min-w-0 truncate", active && "font-bold"), children: field.label }),
14261
+ /* @__PURE__ */ jsx(
14262
+ SIcon,
14263
+ {
14264
+ name: "caretDown",
14265
+ size: 12,
14266
+ color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipDefaultText
14267
+ }
14268
+ )
14269
+ ] }),
14270
+ applied && !disabled && showClear && /* @__PURE__ */ jsx(
14271
+ "span",
14272
+ {
14273
+ role: "button",
14274
+ tabIndex: 0,
14275
+ "aria-label": `${field.label} \uD544\uD130 \uC9C0\uC6B0\uAE30`,
14276
+ className: "inline-flex size-[16px] items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)]",
14277
+ onClick: (event) => {
14278
+ event.preventDefault();
14279
+ event.stopPropagation();
14280
+ onClear();
14281
+ },
14282
+ onKeyDown: (event) => {
14283
+ if (event.key !== "Enter" && event.key !== " ") return;
14284
+ event.preventDefault();
14285
+ event.stopPropagation();
14286
+ onClear();
14287
+ },
14288
+ children: /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14289
+ }
14290
+ )
14291
+ ]
14292
+ }
14293
+ );
14294
+ }
14295
+ function EditorPanel({
14296
+ field,
14297
+ value,
14298
+ disabled,
14299
+ onValueChange,
14300
+ onClose
14301
+ }) {
14302
+ const wrapsInPaddedTop = field.type === "date";
14303
+ const isMulti = field.type === "multi";
14304
+ const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
14305
+ const selectedValues = isMulti ? toArrayValue(value) : [];
14306
+ const allSelected = isMulti && selectableValues.length > 0 && selectableValues.every((v) => selectedValues.includes(v));
14307
+ const toggleSelectAll = () => {
14308
+ onValueChange(
14309
+ allSelected ? selectedValues.filter((v) => !selectableValues.includes(v)) : [...selectedValues, ...selectableValues.filter((v) => !selectedValues.includes(v))]
14310
+ );
14311
+ };
14312
+ return /* @__PURE__ */ jsxs(ListBox, { children: [
14313
+ wrapsInPaddedTop ? /* @__PURE__ */ 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__ */ jsx(
14314
+ FilterEditor,
14315
+ {
14316
+ field,
14317
+ value,
14318
+ disabled,
14319
+ onValueChange
14320
+ }
14321
+ ) }) : /* @__PURE__ */ jsx(
14322
+ FilterEditor,
14323
+ {
14324
+ field,
14325
+ value,
14326
+ disabled,
14327
+ onValueChange
14328
+ }
14329
+ ),
14330
+ /* @__PURE__ */ jsxs(
14331
+ "div",
14332
+ {
14333
+ 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)]",
14334
+ style: { borderColor: CHIPFILTER.listboxBorder },
14335
+ children: [
14336
+ isMulti && /* @__PURE__ */ jsxs(Fragment, { children: [
14337
+ /* @__PURE__ */ jsx(
14338
+ STextLink,
14339
+ {
14340
+ size: "sm",
14341
+ label: allSelected ? "\uC120\uD0DD \uD574\uC81C" : "\uC804\uCCB4 \uC120\uD0DD",
14342
+ labelClass: "font-bold",
14343
+ disabled,
14344
+ onClick: toggleSelectAll,
14345
+ style: { color: "var(--sys-color-fg-secondary)" }
14346
+ }
14347
+ ),
14348
+ /* @__PURE__ */ jsx(
14349
+ "span",
14350
+ {
14351
+ "aria-hidden": true,
14352
+ className: "inline-flex w-px shrink-0",
14353
+ style: {
14354
+ height: CHIPFILTER.listboxBottomDividerHeight,
14355
+ background: CHIPFILTER.listboxBorder
14356
+ }
14357
+ }
14358
+ )
14359
+ ] }),
14360
+ /* @__PURE__ */ jsx(
14361
+ STextLink,
14362
+ {
14363
+ size: "sm",
14364
+ label: "\uAC80\uC0C9",
14365
+ labelClass: "font-bold",
14366
+ onClick: onClose,
14367
+ style: { color: CHIPFILTER.chipAppliedValue }
14368
+ }
14369
+ )
14370
+ ]
14371
+ }
14372
+ )
14373
+ ] });
14374
+ }
14375
+ function FilterEditor({
14376
+ field,
14377
+ value,
14378
+ disabled,
14379
+ onValueChange
14380
+ }) {
14381
+ if (field.type === "date") {
14382
+ return /* @__PURE__ */ jsx(
14383
+ DatePresetEditor,
14384
+ {
14385
+ field,
14386
+ value,
14387
+ disabled,
14388
+ onValueChange
14389
+ }
14390
+ );
14391
+ }
14392
+ if (field.type === "keyword") {
14393
+ return /* @__PURE__ */ jsx(
14394
+ KeywordEditor,
14395
+ {
14396
+ field,
14397
+ value: isKeywordValue(value) ? value : keywordEntries(value),
14398
+ disabled,
14399
+ onValueChange
14400
+ }
14401
+ );
14402
+ }
14403
+ return /* @__PURE__ */ jsx(
14404
+ OptionList,
14405
+ {
14406
+ field,
14407
+ value: field.type === "multi" ? toArrayValue(value) : value,
14408
+ multiple: field.type === "multi",
14409
+ disabled,
14410
+ onValueChange
14411
+ }
14412
+ );
14413
+ }
14414
+ function DatePresetEditor({
14415
+ field,
14416
+ value,
14417
+ disabled,
14418
+ onValueChange
14419
+ }) {
14420
+ const presets = field.presets ?? [];
14421
+ const [selectedId, setSelectedId] = useState(() => matchPresetId(presets, value));
14422
+ const syncedInitialValueRef = useRef(!isEmptyValue(value));
14423
+ useEffect(() => {
14424
+ if (syncedInitialValueRef.current || isEmptyValue(value)) return;
14425
+ syncedInitialValueRef.current = true;
14426
+ setSelectedId(matchPresetId(presets, value));
14427
+ }, [value, presets]);
14428
+ const selectedPreset = presets.find((preset) => preset.value === selectedId);
14429
+ const selectPreset = (preset) => {
14430
+ setSelectedId(preset.value);
14431
+ if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : null);
14432
+ };
14433
+ if (presets.length === 0) {
14434
+ return /* @__PURE__ */ jsx(
14435
+ SDatePicker,
14436
+ {
14437
+ value: typeof value === "string" ? value : null,
14438
+ onValueChange,
14439
+ selectable: field.selectable,
14440
+ disabled,
14441
+ placeholder: field.placeholder
14442
+ }
14443
+ );
14444
+ }
14445
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)]", children: [
14446
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-[8px]", children: presets.map((preset) => /* @__PURE__ */ jsx(
14447
+ SRadio,
14448
+ {
14449
+ val: preset.value,
14450
+ value: selectedId,
14451
+ label: preset.label,
14452
+ disabled,
14453
+ onValueChange: () => selectPreset(preset)
14454
+ },
14455
+ preset.value
14456
+ )) }),
14457
+ selectedPreset?.custom && /* @__PURE__ */ jsx(
14458
+ SDateRangePicker,
14459
+ {
14460
+ value: Array.isArray(value) && value.length === 2 ? value : null,
14461
+ onValueChange,
14462
+ selectable: field.selectable,
14463
+ maxRange: field.maxRange,
14464
+ disabled
14465
+ }
14466
+ )
14467
+ ] });
14468
+ }
14469
+ function InlineDatePresetFilter({
14470
+ field,
14471
+ value,
14472
+ disabled,
14473
+ warning,
14474
+ showClear,
14475
+ onValueChange,
14476
+ onClear
14477
+ }) {
14478
+ const presets = (field.presets ?? []).filter((preset) => !preset.custom);
14479
+ const [selectedId, setSelectedId] = useState(() => matchPresetId(presets, value));
14480
+ const syncedInitialValueRef = useRef(!isEmptyValue(value));
14481
+ useEffect(() => {
14482
+ if (syncedInitialValueRef.current || isEmptyValue(value)) return;
14483
+ syncedInitialValueRef.current = true;
14484
+ setSelectedId(matchPresetId(presets, value));
14485
+ }, [value, presets]);
14486
+ const applied = !isEmptyValue(value);
14487
+ const rangeValue = Array.isArray(value) ? value : typeof value === "string" && value !== "" ? [value, value] : null;
14488
+ const [pickerOpen, setPickerOpen] = useState(false);
14489
+ const portalContainer = usePortalContainer();
14490
+ const selectPreset = (id) => {
14491
+ const preset = presets.find((p) => p.value === id);
14492
+ if (!preset) return;
14493
+ setSelectedId(preset.value);
14494
+ onValueChange(preset.resolve ? preset.resolve() : null);
14495
+ };
14496
+ const badge = applied && /* @__PURE__ */ jsxs(
14497
+ "span",
14498
+ {
14499
+ className: "inline-flex shrink-0 items-center gap-[4px] whitespace-nowrap border border-solid px-[var(--cmp-chipFilter-chip-paddingX)] text-[12px] leading-[20px]",
14500
+ style: {
14501
+ minHeight: CHIPFILTER.chipHeight,
14502
+ borderRadius: CHIPFILTER.chipRadius,
14503
+ borderColor: warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder,
14504
+ background: CHIPFILTER.chipAppliedBg,
14505
+ color: CHIPFILTER.chipAppliedLabel,
14506
+ cursor: rangeValue ? "pointer" : void 0
14507
+ },
14508
+ children: [
14509
+ /* @__PURE__ */ jsxs("span", { className: "font-bold", children: [
14510
+ field.label,
14511
+ ":"
14512
+ ] }),
14513
+ /* @__PURE__ */ jsx("span", { className: "font-bold", style: { color: CHIPFILTER.chipAppliedValue }, children: displayValue(field, value) }),
14514
+ !disabled && showClear && /* @__PURE__ */ jsx(
14515
+ "span",
14516
+ {
14517
+ role: "button",
14518
+ tabIndex: 0,
14519
+ "aria-label": `${field.label} \uD544\uD130 \uC9C0\uC6B0\uAE30`,
14520
+ className: "inline-flex size-[16px] items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)]",
14521
+ onClick: (event) => {
14522
+ event.stopPropagation();
14523
+ onClear();
14524
+ },
14525
+ onKeyDown: (event) => {
14526
+ if (event.key !== "Enter" && event.key !== " ") return;
14527
+ event.preventDefault();
14528
+ event.stopPropagation();
14529
+ onClear();
14530
+ },
14531
+ children: /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14532
+ }
14533
+ )
14534
+ ]
14535
+ }
14536
+ );
14537
+ return /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-[8px]", children: [
14538
+ /* @__PURE__ */ jsx(
14539
+ SRadioButton,
14540
+ {
14541
+ size: "xs",
14542
+ options: presets.map((preset) => ({ value: preset.value, label: preset.label })),
14543
+ value: selectedId,
14544
+ disabled,
14545
+ onValueChange: (next) => selectPreset(String(next))
14546
+ }
14547
+ ),
14548
+ applied && (rangeValue ? (
14549
+ // 뱃지 모양은 그대로 두고, 눌렀을 때 캘린더가 열려 기간을 직접 다시 고를 수 있게 한다.
14550
+ // 직접 고르면 프리셋 선택은 해제된다.
14551
+ /* @__PURE__ */ jsxs(RPopover9.Root, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
14552
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
14553
+ "button",
14554
+ {
14555
+ type: "button",
14556
+ disabled,
14557
+ className: "border-none bg-transparent p-0 outline-none disabled:cursor-not-allowed",
14558
+ children: badge
14559
+ }
14560
+ ) }),
14561
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14562
+ RPopover9.Content,
14563
+ {
14564
+ side: "bottom",
14565
+ align: "start",
14566
+ sideOffset: 4,
14567
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
14568
+ children: /* @__PURE__ */ jsx(
14569
+ RangeCalendar,
14570
+ {
14571
+ value: rangeValue,
14572
+ selectable: field.selectable,
14573
+ maxRange: field.maxRange,
14574
+ onSelect: (range) => {
14575
+ setSelectedId(null);
14576
+ onValueChange(range);
14577
+ setPickerOpen(false);
14578
+ }
14579
+ }
14580
+ )
14581
+ }
14582
+ ) })
14583
+ ] })
14584
+ ) : badge)
14585
+ ] });
14586
+ }
14587
+ var MATCH_MODE_OPTIONS = [
14588
+ { value: "contains", label: "\uD3EC\uD568" },
14589
+ { value: "exact", label: "\uC77C\uCE58" }
14590
+ ];
14591
+ var EXCLUDE_MATCH_MODE_OPTION = {
14592
+ value: "excludes",
14593
+ label: "\uBBF8\uD3EC\uD568"
14594
+ };
14595
+ function KeywordEditor({
14596
+ field,
14597
+ value,
14598
+ disabled,
14599
+ onValueChange
14600
+ }) {
14601
+ const entries3 = keywordEntries(value);
14602
+ const [draft, setDraft] = useState("");
14603
+ const [mode, setMode] = useState(
14604
+ isKeywordValue(value) ? value.mode : "contains"
14605
+ );
14606
+ const emit3 = (nextEntries) => {
14607
+ onValueChange(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
14608
+ };
14609
+ const addEntry = (raw) => {
14610
+ const next = raw.trim();
14611
+ if (next === "" || entries3.includes(next)) return;
14612
+ emit3([...entries3, next]);
14613
+ setDraft("");
14614
+ };
14615
+ const removeEntry = (entry) => {
14616
+ emit3(entries3.filter((e) => e !== entry));
14617
+ };
14618
+ const onKeyDown = (event) => {
14619
+ if (event.key === "Enter") {
14620
+ if (event.nativeEvent.isComposing) return;
14621
+ event.preventDefault();
14622
+ addEntry(draft);
14623
+ }
14624
+ };
14625
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
14626
+ /* @__PURE__ */ 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: [
14627
+ field.matchModes && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
14628
+ /* @__PURE__ */ jsx(
14629
+ "span",
14630
+ {
14631
+ className: "shrink-0 whitespace-nowrap text-[12px] font-medium leading-[20px]",
14632
+ style: { color: CHIPFILTER.listboxLabel },
14633
+ children: "\uAC80\uC0C9 \uC870\uAC74 :"
14634
+ }
14635
+ ),
14636
+ /* @__PURE__ */ jsx(
14637
+ SRadioButton,
14638
+ {
14639
+ size: "xs",
14640
+ style: { width: 150 },
14641
+ options: field.excludeMode ? [...MATCH_MODE_OPTIONS, EXCLUDE_MATCH_MODE_OPTION] : MATCH_MODE_OPTIONS,
14642
+ value: mode,
14643
+ disabled,
14644
+ onValueChange: (next) => setMode(next)
14645
+ }
14646
+ )
14647
+ ] }),
14648
+ /* @__PURE__ */ 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: [
14649
+ /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
14650
+ /* @__PURE__ */ jsx(
14651
+ "input",
14652
+ {
14653
+ value: draft,
14654
+ disabled,
14655
+ placeholder: field.placeholder ?? "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00",
14656
+ onChange: (event) => setDraft(event.target.value),
14657
+ onKeyDown,
14658
+ 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"
14659
+ }
14660
+ )
14661
+ ] })
14662
+ ] }),
14663
+ entries3.length > 0 && /* @__PURE__ */ jsx(
14664
+ "div",
14665
+ {
14666
+ className: "max-h-[220px] overflow-auto border-t border-solid py-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14667
+ style: { borderColor: CHIPFILTER.listboxBorder },
14668
+ children: entries3.map((entry) => /* @__PURE__ */ jsx(
14669
+ KeywordEntryRow,
14670
+ {
14671
+ label: optionLabel(field, entry),
14672
+ disabled,
14673
+ onRemove: () => removeEntry(entry)
14674
+ },
14675
+ entry
14676
+ ))
14677
+ }
14678
+ )
14679
+ ] });
14680
+ }
14681
+ function KeywordEntryRow({
14682
+ label,
14683
+ disabled,
14684
+ onRemove
14685
+ }) {
14686
+ return /* @__PURE__ */ jsxs(
14687
+ "div",
14688
+ {
14689
+ className: "group flex w-full items-center justify-between gap-[8px] text-[12px] leading-[20px]",
14690
+ style: {
14691
+ paddingTop: CHIPFILTER.itemPaddingY,
14692
+ paddingBottom: CHIPFILTER.itemPaddingY,
14693
+ paddingLeft: CHIPFILTER.itemPaddingLeft,
14694
+ paddingRight: CHIPFILTER.itemPaddingRight,
14695
+ color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.itemTextDefault
14696
+ },
14697
+ children: [
14698
+ /* @__PURE__ */ jsx("span", { className: cn("min-w-0 truncate", !disabled && "group-hover:font-bold"), children: label }),
14699
+ !disabled && /* @__PURE__ */ jsx(
14700
+ "button",
14701
+ {
14702
+ type: "button",
14703
+ "aria-label": `${label} \uC0AD\uC81C`,
14704
+ className: "invisible inline-flex size-[16px] shrink-0 cursor-pointer items-center justify-center group-hover:visible",
14705
+ onClick: onRemove,
14706
+ children: /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14707
+ }
14708
+ )
14709
+ ]
14710
+ }
14711
+ );
14712
+ }
14713
+ var OPTION_SEARCH_THRESHOLD = 11;
14714
+ function OptionList({
14715
+ field,
14716
+ value,
14717
+ multiple,
14718
+ disabled,
14719
+ onValueChange
14720
+ }) {
14721
+ const selected = multiple ? toArrayValue(value) : [];
14722
+ const singleValue = !multiple && (typeof value === "string" || typeof value === "number") ? value : null;
14723
+ const [search, setSearch] = useState("");
14724
+ const options = field.options ?? [];
14725
+ const showSearch = options.length >= OPTION_SEARCH_THRESHOLD;
14726
+ const filteredOptions = useMemo(() => {
14727
+ if (!showSearch || search.trim() === "") return options;
14728
+ const query = search.trim().toLowerCase();
14729
+ return options.filter((option) => option.label.toLowerCase().includes(query));
14730
+ }, [options, search, showSearch]);
14731
+ if (options.length === 0) return /* @__PURE__ */ jsx(EmptyItem, { children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
14732
+ return /* @__PURE__ */ jsxs(
14733
+ "div",
14734
+ {
14735
+ className: cn(
14736
+ "max-h-[240px] overflow-auto",
14737
+ !showSearch && "pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14738
+ "pb-[var(--cmp-chipFilter-listbox-list-paddingY)]"
14739
+ ),
14740
+ children: [
14741
+ showSearch && /* @__PURE__ */ jsx(
14742
+ "div",
14743
+ {
14744
+ className: "sticky top-0 z-[1] border-b border-solid px-[8px] pb-[4px] pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14745
+ style: { background: CHIPFILTER.listboxBg, borderColor: CHIPFILTER.listboxBorder },
14746
+ children: /* @__PURE__ */ 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: [
14747
+ /* @__PURE__ */ jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
14748
+ /* @__PURE__ */ jsx(
14749
+ "input",
14750
+ {
14751
+ value: search,
14752
+ disabled,
14753
+ placeholder: "\uAC80\uC0C9",
14754
+ onChange: (event) => setSearch(event.target.value),
14755
+ 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"
14756
+ }
14757
+ ),
14758
+ search !== "" && !disabled && /* @__PURE__ */ jsx(
14759
+ SGhostButton,
14760
+ {
14761
+ icon: "close",
14762
+ size: "xxs",
14763
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
14764
+ className: "flex-shrink-0",
14765
+ onClick: () => setSearch("")
14766
+ }
14767
+ )
14768
+ ] })
14769
+ }
14770
+ ),
14771
+ showSearch && filteredOptions.length === 0 && /* @__PURE__ */ jsx(EmptyItem, { children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
14772
+ filteredOptions.map((option) => {
14773
+ const checked = multiple ? selected.includes(option.value) : singleValue === option.value;
14774
+ return /* @__PURE__ */ jsxs(
14775
+ MenuItem,
14776
+ {
14777
+ selected: checked,
14778
+ disabled: disabled || option.disabled,
14779
+ multi: multiple,
14780
+ onClick: () => {
14781
+ if (multiple) {
14782
+ const next = checked ? selected.filter((v) => v !== option.value) : [...selected, option.value];
14783
+ onValueChange(next);
14784
+ } else {
14785
+ onValueChange(option.value);
14786
+ }
14787
+ },
14788
+ children: [
14789
+ multiple && /* @__PURE__ */ jsx(OptionCheckbox, { checked }),
14790
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: option.label })
14791
+ ]
14792
+ },
14793
+ String(option.value)
14794
+ );
14795
+ })
14796
+ ]
14797
+ }
14798
+ );
14799
+ }
14800
+ function OptionCheckbox({ checked }) {
14801
+ return /* @__PURE__ */ jsx(
14802
+ "span",
14803
+ {
14804
+ className: cn(
14805
+ "inline-flex size-[16px] shrink-0 items-center justify-center rounded-[2px] border border-solid",
14806
+ 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)]"
14807
+ ),
14808
+ children: checked && /* @__PURE__ */ jsx(SIcon, { name: "checkboxCheck", size: 12, color: "var(--cmp-checkbox-checked-icon-default)" })
14809
+ }
14810
+ );
14811
+ }
14812
+ function ListBox({ children }) {
14813
+ return /* @__PURE__ */ jsx(
14814
+ "div",
14815
+ {
14816
+ className: "overflow-hidden border border-solid shadow-[var(--shadow-overlay)]",
14817
+ style: {
14818
+ borderRadius: CHIPFILTER.listboxRadius,
14819
+ background: CHIPFILTER.listboxBg,
14820
+ borderColor: CHIPFILTER.listboxBorder
14821
+ },
14822
+ children
14823
+ }
14824
+ );
14825
+ }
14826
+ function MenuItem({
14827
+ children,
14828
+ selected,
14829
+ disabled,
14830
+ multi,
14831
+ onMouseEnter,
14832
+ onClick
14833
+ }) {
14834
+ return /* @__PURE__ */ jsx(
14835
+ "button",
14836
+ {
14837
+ type: "button",
14838
+ disabled,
14839
+ onMouseEnter,
14840
+ onClick,
14841
+ className: cn(
14842
+ "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",
14843
+ selected ? "font-bold" : "font-normal",
14844
+ !disabled && "hover:font-bold"
14845
+ ),
14846
+ style: {
14847
+ paddingTop: CHIPFILTER.itemPaddingY,
14848
+ paddingBottom: CHIPFILTER.itemPaddingY,
14849
+ paddingLeft: multi ? CHIPFILTER.itemMultiPaddingX : CHIPFILTER.itemPaddingLeft,
14850
+ paddingRight: multi ? CHIPFILTER.itemMultiPaddingX : CHIPFILTER.itemPaddingRight,
14851
+ color: disabled ? CHIPFILTER.chipDisabledText : selected ? CHIPFILTER.itemTextSelected : CHIPFILTER.itemTextDefault
14852
+ },
14853
+ children
14854
+ }
14855
+ );
14856
+ }
14857
+ function EmptyItem({ children }) {
14858
+ return /* @__PURE__ */ jsx(
14859
+ "div",
14860
+ {
14861
+ className: "text-[12px] leading-[20px]",
14862
+ style: {
14863
+ paddingTop: CHIPFILTER.itemPaddingY,
14864
+ paddingBottom: CHIPFILTER.itemPaddingY,
14865
+ paddingLeft: CHIPFILTER.itemPaddingLeft,
14866
+ paddingRight: CHIPFILTER.itemPaddingRight,
14867
+ color: CHIPFILTER.chipDisabledText
14868
+ },
14869
+ children
14870
+ }
14871
+ );
14872
+ }
14873
+ var SChipInput = /* @__PURE__ */ forwardRef(function SChipInput2({
14874
+ values = [],
14875
+ onValueChange,
14876
+ onInput,
14877
+ onFocus,
14878
+ onBlur,
14879
+ errors = [],
14880
+ disabledChips = [],
14881
+ size = "sm",
14882
+ disabled = false,
14883
+ placeholder = "\uD0DC\uADF8 \uC785\uB825 (Enter\uB85C \uB4F1\uB85D / \uCF64\uB9C8\uB85C \uAD6C\uBD84 / \uB744\uC5B4\uC4F0\uAE30 \uBD88\uAC00)",
14884
+ name,
14885
+ rules,
14886
+ error,
14887
+ useReset = false,
14888
+ maxCount,
14889
+ metaPlacement = "end",
14890
+ duplicateLabel,
14891
+ suggestions = [],
14892
+ loadingSuggestions = false,
14893
+ recommendedItems = [],
14894
+ loadingRecommendedItems = false,
14895
+ dropdownMinWidth = 200,
14896
+ label,
14897
+ labelWidth,
14898
+ hint,
14899
+ errorMessage,
14900
+ width,
14901
+ status,
14902
+ icon,
14903
+ iconColor,
14904
+ labelTooltip,
14905
+ labelTooltipProps,
14906
+ className,
14907
+ style
14908
+ }, ref) {
14909
+ const autoId = useId();
14910
+ const resolvedName = name ?? autoId;
14911
+ const [inputValue, setInputValue] = useState("");
14912
+ const [focused, setFocused] = useState(false);
14913
+ const [hovered, setHovered] = useState(false);
14914
+ const [activeIndex, setActiveIndex] = useState(-1);
14915
+ const [suppressSuggestions, setSuppressSuggestions] = useState(false);
14916
+ const [suppressRecommendations, setSuppressRecommendations] = useState(false);
14917
+ const inputRef = useRef(null);
14918
+ const containerRef = useRef(null);
14919
+ const inputShellRef = useRef(null);
14920
+ const isComposingRef = useRef(false);
14921
+ useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }), []);
14922
+ const chipError = (i) => {
14923
+ if (values.indexOf(values[i]) !== i) return true;
14924
+ if (typeof errors === "function") return errors(values[i] ?? "");
14925
+ return errors[i] ?? false;
14926
+ };
14927
+ const chipDisabled = (i) => {
14928
+ if (disabled) return true;
14929
+ if (typeof disabledChips === "function") return disabledChips(values[i] ?? "");
14930
+ return disabledChips[i] ?? false;
14931
+ };
14932
+ const duplicateMessage = useMemo(() => {
13423
14933
  const seen = /* @__PURE__ */ new Set();
13424
14934
  for (const v of values) {
13425
14935
  if (seen.has(v))
@@ -14083,7 +15593,7 @@ function TimeSelector({
14083
15593
  const renderColumn = (type) => {
14084
15594
  const isHour = type === "hour";
14085
15595
  const val = isHour ? displayHour : minute;
14086
- const displayValue = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
15596
+ const displayValue2 = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
14087
15597
  return /* @__PURE__ */ jsxs(
14088
15598
  "div",
14089
15599
  {
@@ -14104,7 +15614,7 @@ function TimeSelector({
14104
15614
  {
14105
15615
  "aria-label": isHour ? "\uC2DC" : "\uBD84",
14106
15616
  inputMode: "numeric",
14107
- value: displayValue,
15617
+ value: displayValue2,
14108
15618
  placeholder: "-",
14109
15619
  onFocus: () => beginEdit(type, val),
14110
15620
  onBlur: () => endEdit(type),
@@ -14299,8 +15809,8 @@ function STimePicker({
14299
15809
  disabled,
14300
15810
  className,
14301
15811
  style,
14302
- children: /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: commitOpen, children: [
14303
- /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
15812
+ children: /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: commitOpen, children: [
15813
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
14304
15814
  "div",
14305
15815
  {
14306
15816
  ref: triggerRef,
@@ -14360,14 +15870,17 @@ function STimePicker({
14360
15870
  ]
14361
15871
  }
14362
15872
  ) }),
14363
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14364
- RPopover2.Content,
15873
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
15874
+ RPopover9.Content,
14365
15875
  {
14366
15876
  side: "bottom",
14367
15877
  align: "start",
14368
15878
  sideOffset: 4,
14369
15879
  alignOffset: popoverAlignOffset,
14370
- className: cn("z-50", FLOATING_SLIDE_ANIM),
15880
+ className: cn(
15881
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
15882
+ FLOATING_SLIDE_ANIM
15883
+ ),
14371
15884
  children: /* @__PURE__ */ jsx(
14372
15885
  TimeSelector,
14373
15886
  {
@@ -14526,7 +16039,7 @@ function RangeTimeSelector({
14526
16039
  const { minute } = getParts(side);
14527
16040
  const val = isHour ? getDisplayHour(side) : minute;
14528
16041
  const currentEditingKey = getEditingKey(side, kind);
14529
- const displayValue = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
16042
+ const displayValue2 = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
14530
16043
  return /* @__PURE__ */ jsxs(
14531
16044
  "div",
14532
16045
  {
@@ -14547,7 +16060,7 @@ function RangeTimeSelector({
14547
16060
  {
14548
16061
  "aria-label": `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"}`,
14549
16062
  inputMode: "numeric",
14550
- value: displayValue,
16063
+ value: displayValue2,
14551
16064
  placeholder: "-",
14552
16065
  onFocus: () => beginEdit(side, kind, val),
14553
16066
  onBlur: () => endEdit(side, kind),
@@ -14758,8 +16271,8 @@ function STimeRangePicker({
14758
16271
  disabled,
14759
16272
  className,
14760
16273
  style,
14761
- children: /* @__PURE__ */ jsxs(RPopover2.Root, { open, onOpenChange: commitOpen, children: [
14762
- /* @__PURE__ */ jsx(RPopover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
16274
+ children: /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: commitOpen, children: [
16275
+ /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
14763
16276
  "div",
14764
16277
  {
14765
16278
  ref: triggerRef,
@@ -14819,14 +16332,17 @@ function STimeRangePicker({
14819
16332
  ]
14820
16333
  }
14821
16334
  ) }),
14822
- /* @__PURE__ */ jsx(RPopover2.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14823
- RPopover2.Content,
16335
+ /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
16336
+ RPopover9.Content,
14824
16337
  {
14825
16338
  side: "bottom",
14826
16339
  align: "start",
14827
16340
  sideOffset: 4,
14828
16341
  alignOffset: popoverAlignOffset,
14829
- className: cn("z-50", FLOATING_SLIDE_ANIM),
16342
+ className: cn(
16343
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
16344
+ FLOATING_SLIDE_ANIM
16345
+ ),
14830
16346
  children: /* @__PURE__ */ jsx(
14831
16347
  RangeTimeSelector,
14832
16348
  {
@@ -14844,6 +16360,6 @@ function STimeRangePicker({
14844
16360
  );
14845
16361
  }
14846
16362
 
14847
- export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_FULL_LOGO_WIDTH, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, PAGE_BACKGROUNDS, SActionModal, SBadge, SBarcodeInput, SButton, SCROLL_AREA_AXES, SCalendar, SCallout, SCard, SCheckbox, SChip2 as SChip, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDateRangePicker, SDivider, SDraggableGroup, SDraggableItem2 as SDraggableItem, SDraggableList, SDraggableProvider, SDrawer, SDropdownButton2 as SDropdownButton, SExpansionItem, SExpansionList2 as SExpansionList, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SInput, SKeyValueTable, SLAYOUT_MIN_WIDTH, SLayout, SLayoutContext, SLinearProgress, SList2 as SList, SListItem2 as SListItem, SLoadingContainer, SLoadingModal, SLoadingViewport, SModal, SModalOutlet, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSPLITTER_UNITS, SScrollArea, SSectionHeaderCard2 as SSectionHeaderCard, SSelect2 as SSelect, SSplitter2 as SSplitter, SStepper, SSwitch, STEPPER_SIZES, STable, STableBar, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip2 as STooltip, STree2 as STree, STreeItem2 as STreeItem, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
16363
+ export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_FULL_LOGO_WIDTH, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, PAGE_BACKGROUNDS, RangeCalendar, SActionModal, SBadge, SBarcodeInput, SButton, SCROLL_AREA_AXES, SCalendar, SCallout, SCard, SCheckbox, SChip2 as SChip, SChipFilter, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDateRangePicker, SDivider, SDraggableGroup, SDraggableItem2 as SDraggableItem, SDraggableList, SDraggableProvider, SDrawer, SDropdownButton2 as SDropdownButton, SExpansionItem, SExpansionList2 as SExpansionList, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SInput, SKeyValueTable, SLAYOUT_MIN_WIDTH, SLayout, SLayoutContext, SLinearProgress, SList2 as SList, SListItem2 as SListItem, SLoadingContainer, SLoadingModal, SLoadingViewport, SModal, SModalOutlet, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSPLITTER_UNITS, SScrollArea, SSectionHeaderCard2 as SSectionHeaderCard, SSelect2 as SSelect, SSplitter2 as SSplitter, SStepper, SSwitch, STEPPER_SIZES, STable, STableBar, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip2 as STooltip, STree2 as STree, STreeItem2 as STreeItem, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
14848
16364
  //# sourceMappingURL=index.js.map
14849
16365
  //# sourceMappingURL=index.js.map